tag:blogger.com,1999:blog-33675571807960131822024-03-13T08:26:51.310-04:00Here Dragons AboundExploring procedural generation and display of fantasy maps.Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.comBlogger201125tag:blogger.com,1999:blog-3367557180796013182.post-5080434511991478362022-06-11T14:18:00.001-04:002022-06-11T14:18:48.456-04:00Cleanup Time<p> With the map compass series done (at least for now) I'm going to take a break from dedicated development and spend some time on cleaning up the existing code. I'd like to get to the point where I have a number of styles I can use to generate a map that will almost always produce an acceptable result. There are a few major problems I need to address but most of the problems are fairly minor annoyances that just have to be beaten down one at a time like a whack-a-mole game.</p><p>For no particular reason I'll start with forests. One style of forest that DA uses is called “<a href="https://heredragonsabound.blogspot.com/2017/03/one-of-forest-masses.html" target="_blank">forest masses</a>" and draws forests as large clumps, as so:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhusij1pV3yLTO0A4hR1iqnvYbd6Tdjzwh-gMB9m7IXJEl1C8d80UXSvnpapycHjBO_JK1frxSdidux_9w51RDlz5ROrQmkIi0Sr-hhtgzdPPt21R68osHayfjncqLDRHGR8-F8k3ZQsNShVmAirCJVEPPJtjfEQ-nr2bsJmQUb1vhgJ9_Kt2Xa5sPuqQ" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="480" data-original-width="479" src="https://blogger.googleusercontent.com/img/a/AVvXsEhusij1pV3yLTO0A4hR1iqnvYbd6Tdjzwh-gMB9m7IXJEl1C8d80UXSvnpapycHjBO_JK1frxSdidux_9w51RDlz5ROrQmkIi0Sr-hhtgzdPPt21R68osHayfjncqLDRHGR8-F8k3ZQsNShVmAirCJVEPPJtjfEQ-nr2bsJmQUb1vhgJ9_Kt2Xa5sPuqQ=s16000" /></a></div><br />One problem with this can be seen in the individual trees around the Toplant River at the top of the map. These individual trees clumps shouldn't be used with the forest mass style; they're really intended to be outliers when the forest is drawn as individual trees. That's a fairly easy fix.<p></p><p>The second problem is the texture of bumps inside the forest mass. It has a couple of problems. The bumps tend to line up in rough columns, and there are bumps close to the edge of the mass where they get clipped. </p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjJv5an2bL07mAFTZh7Y0A8pi8zRBVobK6SJZkq_bvod_xeCd63pUBXXxFCDOKiS0YaSfMM44PKU1vLEVwlN0nNmcjZvDnR-3Vbh_RgLLUl7fsYbgNmKktRvgRTyrmOy-1WJjB6o0UqEEd0eTj4Ox9VzUKKORor97MnBRg8laBOYBi3ydhBcRvrjdj7aw" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="483" data-original-width="428" src="https://blogger.googleusercontent.com/img/a/AVvXsEjJv5an2bL07mAFTZh7Y0A8pi8zRBVobK6SJZkq_bvod_xeCd63pUBXXxFCDOKiS0YaSfMM44PKU1vLEVwlN0nNmcjZvDnR-3Vbh_RgLLUl7fsYbgNmKktRvgRTyrmOy-1WJjB6o0UqEEd0eTj4Ox9VzUKKORor97MnBRg8laBOYBi3ydhBcRvrjdj7aw=s16000" /></a></div><div><br /></div>I wrote this style of forest fairly early on, and the bump texture inside the forest masses is getting placed using a sort of perturbed grid. Since then I've realized that a Poisson disc sampling is a better approach for filling an area in this way, and in fact I can reuse the basic approach I used for the <a href="https://heredragonsabound.blogspot.com/2018/10/lord-of-rings-map-style.html" target="_blank">Tolkien forest style</a>.<br /><div><br /></div><div>Surprisingly, the borrowed code works on the first try. I just have to tweak the distance between the bumps some to get a decent range of textures.</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEj2Awtk0UURu22EF8DC4qHq3OBTZYakg05jDkAH9P2Fn1Hn5E1RISwBtD_MmJAC5yWMbj-jgSnm-M7oFi1FSa1W-_AHmKEAHzAQcMVa8_qO3KNJsa_15hGZiJEfeXrm6psXmmif6vUv0B9X6U5gJ6AB9-lQkOVDWTYl_mo6UqZ4MueX9zzOIR9l4gI7fA" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="566" data-original-width="583" src="https://blogger.googleusercontent.com/img/a/AVvXsEj2Awtk0UURu22EF8DC4qHq3OBTZYakg05jDkAH9P2Fn1Hn5E1RISwBtD_MmJAC5yWMbj-jgSnm-M7oFi1FSa1W-_AHmKEAHzAQcMVa8_qO3KNJsa_15hGZiJEfeXrm6psXmmif6vUv0B9X6U5gJ6AB9-lQkOVDWTYl_mo6UqZ4MueX9zzOIR9l4gI7fA=s16000" /></a></div>Pulling the bumps in from the edges of the woods is more problematic. To do this requires “shrinking" the polygon in which I'm drawing the bumps. Shrinking/expanding a polygon is a non-trivial problem, as I've discussed in these pages before. In no wise should you consider writing this code yourself. In this case, I'm going to try using the <a href="https://github.com/w8r/polygon-offset" target="_blank">polygon-offset library</a> from Alexander Milevski; it is partially based upon the <a href="https://github.com/w8r/martinez" target="_blank">Martinez library</a>, which I have also recommended. </div><div><br /></div><div>There are many ways for polygon offsetting to fail, or at least to produce difficult results, such as numerous tiny polygon “islands." Many of the failure modes have to do with sharp vertices close together -- like the bumps on the forest masses. So rather than shrink those outlines and let every bump invite trouble, I'll shrink the “unbumped" original forest outlines. This helps reduce the number of errors, but it doesn't entirely eliminate problems. So this is an area of the code where it is helpful to catch exceptions and route around the damage.</div><div><br /></div><div>With that and some tuning of the shrinking distance, the bumps are kept from running off the edge of the forest:</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjuRqqSULhOWvmmWt4d-JOxbzK7y3kGsIuICIqMxjxLG_7NIu0ypT85XAfqEeSYj9G-TXECi-F6i610UK0zzNQFDyv2CK9HwB-mgKI3nHYjQN6uCb1SDwYXX2PJVjcO_CORplgRCuKh2S0m4lTSwmkz7FQCKim9Q92bS7oo5mh1-Lp6ZTlN8O-xEVyrOA" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="427" data-original-width="627" src="https://blogger.googleusercontent.com/img/a/AVvXsEjuRqqSULhOWvmmWt4d-JOxbzK7y3kGsIuICIqMxjxLG_7NIu0ypT85XAfqEeSYj9G-TXECi-F6i610UK0zzNQFDyv2CK9HwB-mgKI3nHYjQN6uCb1SDwYXX2PJVjcO_CORplgRCuKh2S0m4lTSwmkz7FQCKim9Q92bS7oo5mh1-Lp6ZTlN8O-xEVyrOA=s16000" /></a></div>There are several other options that control the look of this style forest, but they all seem to work fine.<br /><br />I most often draw these sorts of forests pulled back to reveal the rivers, as above. There is an option to draw forests with “hidden" rivers (they're not really hidden, just displayed differently). Turning that on doesn't work. There turn out to be a number of problems, mostly “code rot" where I've made changes but older code hasn't been updated (or not properly) to use the new code. It takes quite a bit of time to run down all the problems, but eventually I have it working again: <br /><br /></div><div><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEirMTIMpR3lGlcGaU2LgNTaTYW00aNCTGd2rF1WWMQw2FrhrXoaX_VlSZgjRLA9kEWmflU1g9dKi_9qrc4dBo9pJFiEBuYuHCA6wm-BPhZbjPhs4Kp92cEYjB8xnfzatJTN4_UgGnvktIawo_nlZJFbvvQzDp3kG323nxOF5ooB04-kxzQ_SD4N5c76vg" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="317" data-original-width="558" src="https://blogger.googleusercontent.com/img/a/AVvXsEirMTIMpR3lGlcGaU2LgNTaTYW00aNCTGd2rF1WWMQw2FrhrXoaX_VlSZgjRLA9kEWmflU1g9dKi_9qrc4dBo9pJFiEBuYuHCA6wm-BPhZbjPhs4Kp92cEYjB8xnfzatJTN4_UgGnvktIawo_nlZJFbvvQzDp3kG323nxOF5ooB04-kxzQ_SD4N5c76vg=s16000" /></a></div><br /><div style="text-align: left;">It's kind of a nice effect, looks like a fold in the forest where the river runs.</div></div><div><br /></div>The ocean visualization is pretty solid, although there's a small bug that causes the ocean to vanish occasionally. That's pretty easy to fix, and along the way I realized that a map with the ocean as blank space is actually a nice variant.<br /><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbIaDO_71G5A2G2dYXkpocgcxjVu-v0ayNjfNl8AKTITkhEh4uDnnfg0t2Tzc3aAr5Lu8UwcTfiadaBv7zo7wuBdwxcS97Rlqf09Q8kFAswcL23mtXkgET7aMZl4W9m1S9HZh-bT04gwAU8BxgbePGY4F5jbS48hhCUXIcvZqVWPJcPLOGKMJDM50Syg/s600/Image1.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="533" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbIaDO_71G5A2G2dYXkpocgcxjVu-v0ayNjfNl8AKTITkhEh4uDnnfg0t2Tzc3aAr5Lu8UwcTfiadaBv7zo7wuBdwxcS97Rlqf09Q8kFAswcL23mtXkgET7aMZl4W9m1S9HZh-bT04gwAU8BxgbePGY4F5jbS48hhCUXIcvZqVWPJcPLOGKMJDM50Syg/s16000/Image1.jpg" /></a></div><br /><div>Here's an interesting problem:</div><div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjadOEY_CzZtVdLnRCl_VFztu4EtDtBvrs5LxlwLIImPPBV-t4F-R064k4Nedfk3cuQxysLgD_MRkTRlT-ndYPicJlnzntILEt8Ckj-VHmMgvZgYcfnqvpxlovv6J7BC6kysun7M-tebYGK8JL-RE9_fnBQhhX0EVDQFec0eBNKbtnJ0caJmkzIX8TSYw" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="344" data-original-width="424" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEjadOEY_CzZtVdLnRCl_VFztu4EtDtBvrs5LxlwLIImPPBV-t4F-R064k4Nedfk3cuQxysLgD_MRkTRlT-ndYPicJlnzntILEt8Ckj-VHmMgvZgYcfnqvpxlovv6J7BC6kysun7M-tebYGK8JL-RE9_fnBQhhX0EVDQFec0eBNKbtnJ0caJmkzIX8TSYw" width="296" /></a></div><div><br /></div>The hills are being drawn in green. At first it looks like hills are getting filled with the forest color instead of the land color, but on further investigation, that isn't the problem. The color is a hard-coded value hold over from the <a href="https://heredragonsabound.blogspot.com/2020/12/knurden-style-overview-part-1.html" target="_blank">Knurden-style map</a>, where the land color is green. It shouldn't be hard-coded in any case, so I'll make the color derive from the land color if it isn't otherwise specified.<br /><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiE0VoTm_GvGczkwJLR15ZfOIIb6UQOS5Hjm5iVcR2RtDP7cxOkj-usLP3ojtGEgiDXdDARWUBvc-O4cGlVg-iuN8hx6tr7NVgLrWVy48iTkse5Q-H_M0FNv4uLE3N3ky83P7_57GHYPz_x4clK_eqEwMzaHDNRv022w5fcitYGaprVaqOrah-hcXflaw" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="335" data-original-width="355" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEiE0VoTm_GvGczkwJLR15ZfOIIb6UQOS5Hjm5iVcR2RtDP7cxOkj-usLP3ojtGEgiDXdDARWUBvc-O4cGlVg-iuN8hx6tr7NVgLrWVy48iTkse5Q-H_M0FNv4uLE3N3ky83P7_57GHYPz_x4clK_eqEwMzaHDNRv022w5fcitYGaprVaqOrah-hcXflaw" width="254" /></a></div><br />That looks better. I like the subtle shading on these mountains.</div><div><br /></div><div>Dragons Abound also crashes fairly frequently when drawing mountains, so I decided to run down and correct some of these. </div><div><br /></div><div>The crash has something to do with the the “topline" of the mountain. The topline is the entire outline of the mountain, and there's a function that tries to clean up the topline if for some reason the topline dips below the baseline of the mountain. After some tracing, there seems to be a missing parameter in my configuration file, and that causes the topline to get created incorrectly in one case. I replaced the default value of missing parameter and that fixed the error. I didn't see any problems in the resulting mountains, so apparently the default value is good enough. I'm not sure why I had commented it out.</div><div><br /></div><div>Here's a problem that popped up while I was working on labels:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmQ09mHrS0B32o8p16l45EaeRoCiJJuYIDMD9P4gvdb5GvCrP3mYHTBQP7vuWTGSaAZ44gNM40fPp2ds2yioHKlYppgCa69mdhZXQh5O8IQ99sL-AxgIsXqzMzxF-DOGc_0bpt6ACI9v7XaUDP7HL-QVaPsCMT-tdf8iTdHx0IsgkMx-y1uLthY93qTg/s407/Image32.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="320" data-original-width="407" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmQ09mHrS0B32o8p16l45EaeRoCiJJuYIDMD9P4gvdb5GvCrP3mYHTBQP7vuWTGSaAZ44gNM40fPp2ds2yioHKlYppgCa69mdhZXQh5O8IQ99sL-AxgIsXqzMzxF-DOGc_0bpt6ACI9v7XaUDP7HL-QVaPsCMT-tdf8iTdHx0IsgkMx-y1uLthY93qTg/s16000/Image32.jpg" /></a></div>In the upper part of this image, you can see a bay outlined in gray and labeled “Turquoise Point." DA has mis-identified the bay as a point. The primary difference between a bay and a point is that a point has land between the two end points of the green line and a bay has water. Since there's clearly water between the end points of the green line, something has gone wrong.<div><br /></div><div>This turns out to take nearly a full day of debugging to fix. The details are complex, but the basic problem has to do with how to tell whether an arbitrary point on the map is in the ocean or in the land. What makes this complex is that although the general shape of the land is created with a procedural generation algorithm operating on an underlying Voronoi graph, the final coastlines can vary from that for any number of reasons. The two ends of Turquoise “Point" above are across a small bit of ocean that actually has some land in it -- you can see a small island. And this was confusing the algorithm.</div><div><br /></div><div>After no small amount of thrashing around I have a new algorithm that checks for land based upon the actual coastlines as drawn, and that fixes that particular problem:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgC5o2L9M3lqc2tVW-6P9m2SREPLjb7JAK_eH-48HF_MZw6e7aWdklRoq8zhFfd7p2zp5ofZAQW6ubdOu-_FeiWmEKS5ZcblbkEWfBkxmEDbl4ZlfjvViWr1g2-XDthyLhEnulMtMtYkMqnyH6wXAzT8kWakcQWGiZ3h3_Q0wT7UrGKoSFJy7G-TFLNdQ/s427/Image33.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="399" data-original-width="427" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgC5o2L9M3lqc2tVW-6P9m2SREPLjb7JAK_eH-48HF_MZw6e7aWdklRoq8zhFfd7p2zp5ofZAQW6ubdOu-_FeiWmEKS5ZcblbkEWfBkxmEDbl4ZlfjvViWr1g2-XDthyLhEnulMtMtYkMqnyH6wXAzT8kWakcQWGiZ3h3_Q0wT7UrGKoSFJy7G-TFLNdQ/s16000/Image33.jpg" /></a></div>Now there's no longer the spurious Turquoise Point. There's now a legitimate point on the far side of the bay -- Dusky Point -- but there's a problem with the label. That's discussed and fixed in the blog post on labels.<br /><div><br /><div>Another problem that popped up while working on labels:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrPrfUQMa8JKyhlgQLCwTEx2XtdrND96nWiszTi5Zx0jXUOZfu8s_RZw_b5OVUkz0xOEDYDWWQtsm3QMKFbDSsetllJL-tuJVkMWUI4WvvhuJysadOjXVAAw6Dz0WbT2YcpSdSHLsdv18bko--DTOaqvFv5HhXFBQYqPqw536wdAYhTX6AdRFVNPuLbg/s479/Image44.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="264" data-original-width="479" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrPrfUQMa8JKyhlgQLCwTEx2XtdrND96nWiszTi5Zx0jXUOZfu8s_RZw_b5OVUkz0xOEDYDWWQtsm3QMKFbDSsetllJL-tuJVkMWUI4WvvhuJysadOjXVAAw6Dz0WbT2YcpSdSHLsdv18bko--DTOaqvFv5HhXFBQYqPqw536wdAYhTX6AdRFVNPuLbg/s16000/Image44.jpg" /></a></div>This problem is somewhat similar to the problem with Turquoise Point. It happens because the Voronoi tiles that make up the map are considered land if any part of the tile is above sea level. In places like this little strait, most of the tiles underneath it have at least one corner on the land, so to the forest algorithm, it all looks like land. The solution is to use a stricter definition of land which requires the midpoint of the tile to be on land. </div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDE9Vnqu2mAdElYlbK2X0buJ7DWgJL6xtCbGcSktKroFsBE04OU7PqfYuiwQtETWAbRxwwaTdIj011chc-9ihTfnBgAnYmvGbmo3x0wMhlkI0ZvT5_ui4HYyjUcwTA-0pbvnJ91-6on6jHFf5rXhzGCgH6J8XYGCWWAj34rbVCr248qY0GB2x083yk-Q/s447/Image45.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="256" data-original-width="447" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDE9Vnqu2mAdElYlbK2X0buJ7DWgJL6xtCbGcSktKroFsBE04OU7PqfYuiwQtETWAbRxwwaTdIj011chc-9ihTfnBgAnYmvGbmo3x0wMhlkI0ZvT5_ui4HYyjUcwTA-0pbvnJ91-6on6jHFf5rXhzGCgH6J8XYGCWWAj34rbVCr248qY0GB2x083yk-Q/s16000/Image45.jpg" /></a></div>There's still some small overlaps where the rounded edges of the forest intrude on the ocean, but that's fine.<br /><div><br /></div><div>Then there's this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgT_SbKS8EJZVzBT9mOxyLC3qyJA2IAi-2-cJTYebT8YBwYY2GGaykRqATR5vRpVPadxrkvUz4-1-k16jKa4C9Edh-uS5rxtPIvhyd5XwN3qmfLsEUt_vKsPvz-LNvoziuHnY2msgDbgBY4Z6Nc2wGGLSxrvMKPLvJV79P9aNK5m8OI9uuI4z_ODNruAA/s600/Image46.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="327" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgT_SbKS8EJZVzBT9mOxyLC3qyJA2IAi-2-cJTYebT8YBwYY2GGaykRqATR5vRpVPadxrkvUz4-1-k16jKa4C9Edh-uS5rxtPIvhyd5XwN3qmfLsEUt_vKsPvz-LNvoziuHnY2msgDbgBY4Z6Nc2wGGLSxrvMKPLvJV79P9aNK5m8OI9uuI4z_ODNruAA/s16000/Image46.jpg" /></a></div>Bloody hell. I won't be using that random seed again! <div><br /></div><div>(More seriously, this is some sort of problem with the polygons that define the forest masses but it seems to be a rare error. I'll give it more serious consideration if it continues to pop up.)</div><div><br /></div><div>Next posting I'll switch over to labels as mentioned above.</div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com4tag:blogger.com,1999:blog-3367557180796013182.post-3019125931851842052022-06-05T20:03:00.000-04:002022-06-05T20:03:07.337-04:00Map Compasses Retrospective<p>I <a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">started the map compasses project</a> with two primary goals. First, to create a procedural map compass generator, and secondly to experiment with sharing my code.</p><p>Generating map compasses has been on my list since seeing <a href="https://watabou.itch.io/compass-rose-generator" target="_blank">Oleg's version</a> a couple of years ago. Oleg was kind enough to share his code with me, but I decided that the compass roses he generates were not usually suitable for my maps and decided to write my own. I also wanted to experiment with creating what I think of as a <i>conservative </i>generator.</p><p>There's usually a trade-off in procedural generation between creativity and acceptability. If you build a procedural generator that can be very creative and come up with unusual and unique solutions, it's usually the case that the generator will produce a substantial fraction of solutions that aren't acceptable. In the past I've leaned to the creative side of the spectrum, and (for example) my city icon generator can invent a wide variety of city icons, but there's a good chance that any particular icon won't be acceptable for one reason or another. Creative generators tend to be only lightly constrained. They're able to “break the rules" and sometimes that produces a wonderful result, but often it produces a poor result. That's why a lot of artistic endeavors have “rules of thumb" -- they outline the areas where good results heavily outweigh poor results -- and if you break those rules you can sometimes do something really new, but often at the cost of a lot of failures.</p><p>In general, you might not think it a problem to have a procedural generator that occasionally produces a bad result. Just generate a new result until you get one you like. And in isolation, that's fine. But problems arise when you have a program like <b style="font-variant-caps: small-caps;">Dragons Abound</b> which incorporates a number of separate procedural generation systems. Even though each PG system may only occasionally throw up a bad design, when you have several running in parallel, the chances of at least one of them providing a poor result goes up. And this is particularly annoying when your program can take several minutes to run. Having to run over and over trying to hit upon a map where everything is good is annoying. </p><p>And with creative generators, I always think that I'll look at the output and think about the good and bad results and then add those insights back into the generator to improve the good/bad balance. For example, I might see that “city walls" in city icons only turn out well in certain specific instances and then go back and tune the rule set accordingly. But in practice, I don't really ever do that. By the time I'm seeing the bad city icon results, I've moved on to other problems, and about all I ever do at that point is just turn off a rule entirely. No more city walls, that fixed the problem!</p><p>For those reasons, when I started the map compass generator I leaned in the other direction and tried to create rules that would almost always produce an acceptable result. Overall, I'm happy with how that decision turned out. The generator mostly creates acceptable (if “vanilla") results, and I don't usually have to re-run the generator to avoid a bad result. I also think that on a practical level, I'm more likely to see an example of a compass I like and then go back and add that to the generator. But we'll have to see how that works out.</p><p>One feature I thought about while creating of the compass map generator is a “temperature" control. The idea would be to have a dial (parameter) that I could turn during generation to be either conservative (cold) or more creative (hot). With rule-based systems, one way this could work would be to assign a temperature range to every option in a rule, e.g., </p><pre style="background: rgb(255, 255, 255); font-size: 15.4px;"><span style="color: #808030;"><</span>radialElement<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> {0} <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">> | </span>{10} <thickCircle> | {50} <weirdCircle></pre>Here the number in the curly brackets indicates the lowest temperature for this option, i.e., thinCircle could be used even at the coldest setting, but weirdCircle wouldn't be a possibility until you turned the temperature up to 50 or above. This would give me the option to play it safe or gamble, depending upon what I was trying to do. That's something I'll be thinking about for the future. <div><br /></div><div>The second goal for map compass generation was to experiment with sharing my code. I often get asked to share my code, and I usually feel a little guilty saying no. After all, I got started down the <b style="font-variant-caps: small-caps;">Dragons Abound</b> road because years ago Martin O'Leary was generous enough to share his <a href="http://mewo2.com/notes/terrain/" target="_blank">map generator</a>, and in a way I feel I owe the same grace to the community. And I often hear the benefits of open source lauded -- maybe I was missing out on the benefits of a thriving community around the <b style="font-variant-caps: small-caps;">Dragons Abound</b> code. But at the same time, I recognized that sharing my code would have some substantial costs -- not just eliminating any commercial value to the code, but also in terms of the time and energy it would take to share. I didn't really have any good way to judge whether that balance would be positive or negative for <b style="font-variant-caps: small-caps;">Dragons Abound</b>, so I decided to do an experiment with sharing some code to see how hard it was and how it was received. Map compasses was the first thing that came along that fit the bill.</div><div><br /><div>In terms of the mechanics of sharing, it proved to be about as difficult as I had expected. One of the challenges was in producing code as a companion to the blog entries. I wanted readers to be able to access and run the code as it was in each blog entry. I ended up doing this by using Git branches -- there's a separate Git branch in the repository for each of the 18 blog entries in the compass series. This turned out fine, but it was a bit of work. I had to get used to working in a separate branch for each blog entry, and remembering to start a new branch with each new entry, and so on. There were several occasions where I screwed this up and had to spend an evening fixing up the branches.</div><div><br /></div><div>I also didn't want to release branches ahead of the blog entries, and since Git doesn't offer a way to make private branches public (at least not at the free level), it meant I couldn't actually use Github until I was ready to release the code. </div><div><br /></div><div> I also wanted to keep the code self-contained to make it as easy as possible for people to grab the code and start using it. This involved including small web servers for Windows, Linux and Mac in the repositories, as well as providing a starting web page.</div><div><br /></div><div>Finally, I wanted to give people a way to run the code online if they just wanted to see the generator run. I did this using <a href="https://dragonsabound-part18.netlify.app/test.html" target="_blank">Netlify</a> and once I had established an account, set up a workflow and so on, this was fairly straightforward. For each new branch I created a new site, gave it a recognizable name, and Netlify handled the rest. This was probably my most pleasant tool interaction.</div><div><br /></div><div>There were also some costs in writing the code. First, I naturally took more care in writing code that I intended to share. I tried to keep the code straightforward and understandable, so I often avoid things like list comprehensions that less experienced programmers might find confusing. I also took care to comment anything that might need explanation, and to keep my formatting consistent. In my “regular" <b style="font-variant-caps: small-caps;">Dragons Abound</b> programming I often leave old versions of functions or temporary code in place as a reminder of the development process or in case I might need them again, but in the shared code I edited such things out.</div><div><br /></div><div>Another unexpected cost was that I had to copy or rewrite a lot of utility functionality. In <b style="font-variant-caps: small-caps;">Dragons Abound</b> I have a large library of utility functions to do things like manipulate polygons, work with the map, draw lines, and so on. Using those in the compass generation required either including lots of <b style="font-variant-caps: small-caps;">Dragons Abound </b>code, spending time adapting the code for the compass generator, or rewriting the functionality. I ended up mostly adapting/rewriting the code to keep the overall project as simple and understandable as possible.</div><div><br /></div><div>A final cost of sharing the code was that I took the time at the end of each blog posting to suggest ways in which people might extend or modify the code for that posting. Sometimes these ideas were extensions of what I'd done in that posting, and sometimes they were foreshadowing something coming in a later blog post.<br /><br /></div><div>Overall, the costs of sharing were considerable. I'd say it slowed down my development pace by 50% or perhaps more. On the other hand, did code sharing have any positive benefits?</div><div><br /></div><div>Well... A few people seemed to look at the code:</div></div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0UUCA9Z-JilwzFEnT_43qkJSYxL8ZdgYOgOgaLN2e0OBkbuolco50hySXEoerOxHHxwcRvPb0AnRJu4ZJJmfrmNd9OTcbyxst0h2UlknSzayn_lHadpOA5Z35ciWH7dWRBzG8Nzyigr3Ks7dQ1eQdHQRoe1WcpHcf2nOjT712pGE2TzZzHNAjTgd31g/s600/Image34.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="268" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0UUCA9Z-JilwzFEnT_43qkJSYxL8ZdgYOgOgaLN2e0OBkbuolco50hySXEoerOxHHxwcRvPb0AnRJu4ZJJmfrmNd9OTcbyxst0h2UlknSzayn_lHadpOA5Z35ciWH7dWRBzG8Nzyigr3Ks7dQ1eQdHQRoe1WcpHcf2nOjT712pGE2TzZzHNAjTgd31g/s16000/Image34.jpg" /></a><div><br /></div><div>I'd typically get a couple of visitors to the Github repository after every new blog post. 8 users “starred" the repository. Nobody forked the repository or tried to make a commit to any of the branches. Although I asked in most blog posts for people to share anything they'd done with the code, I got no comments or emails indicating that anyone had used the code. (I thought about leaving an obvious bug in the code to see if that generated any feedback but in the end decided against it.)<div><br /></div><div>One of the benefits often touted for open-source code is outside contributors, but my experience has been that few projects get significant outside contributions (and few projects are welcoming of outside contributions, but that's a different problem). That certainly seems to be the case here. Of course, there could be many reasons for that. Although I tried to make the code accessible, few people have the programming skills required to contribute. And it's not like map compass generation is a problem of wide interest. Also, my version of it might be poorly publicized or poorly executed. But whatever the reasons, there was no interest in contributing to the code.</div><div><br /></div><div>For another data point, we can look at Azgaar's <a href="https://azgaar.github.io/Fantasy-Map-Generator/" target="_blank">fantasy map generation project</a> which is obviously of broader interest. Azgaar makes his code available and does a lot of work to create and support an active community of users. Looking at his <a href="https://github.com/Azgaar/Fantasy-Map-Generator/graphs/contributors" target="_blank">code statistics</a>, it's clear that out of his thousands of users only a small handful have made any contribution to the code.</div><div><br /></div><div>I often get emails or messages asking me to share the <b style="font-variant-caps: small-caps;">Dragons Abound</b> code, and many of these messages claim to be interested in modifying or contributing to the code. I made sure to bring the map compass experiment to the attention of several people who had made those sorts of requests; none of them responded or engaged with the code.</div><div><br /></div><div>My conclusion is that there's virtually no software development benefit in sharing the code for this kind of project. It has significant costs, and seems unlikely to provide any return on the investment. If you have an interesting and well-crafted project, you'll likely get a fair number of people wanting access to the code, but it seems clear that almost all of these people want to use the code, not contribute to maintaining or improving it. Which is not to say you shouldn't share your code or make it public, just don't do it expecting to find an engaged and contributing development community.</div><div><br /></div><div>I've focused above on code contributions, but another possible benefit of the code sharing might be that blog readers get more out of postings that were paired with actual code. But I don't think this was the case. I don't have any hard statistics, but I get the sense that people engaged less with the code sharing postings. (Certainly I didn't get any feedback praising the addition of code.) I'd guess that most people prefer to read about the highlights and insights of the software development rather than the nitty-gritty details of the code itself. And that's understandable. It's a lot of work to read code and comprehend what it is doing. Unless you're planning on modifying the code yourself, there may not be much to get out of that.</div><div><br /></div><div>Overall I rate the map compass experiment a success. The whole process of writing the blog and the code intertwined was new and I think I got something out of it. I also enjoyed figuring out how to make the code public and accessible. And it did bring some clarity to my thinking about code sharing.</div><div><br /></div><div>And now onward to other things!</div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com14tag:blogger.com,1999:blog-3367557180796013182.post-57740380971201417142022-05-31T10:33:00.000-04:002022-05-31T10:33:16.087-04:00Adding Compasses to Dragons Abound<p> As I documented in a previous <a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">series of posts</a>, I recently wrote a procedural map compass generator. That was partly an experiment in making my code publicly available, so I wrote it as a standalone program separate from <b style="font-variant-caps: small-caps;">Dragons Abound</b> so that it could be more easily used by someone else. (You can play with it <a href="https://dragonsabound-part18.netlify.app/test.html" target="_blank">here</a>.) Now I'm going to integrate that code back into <b style="font-variant-caps: small-caps;">Dragons Abound</b>.</p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgAaci5NwWtrS2gfVKIpUP7dkcCGwnuQ1yAHLmWHTfNGsl-hyP5k7wHGuDJ5bCCZS-UtQG0AsAsDkuqaDEsLjlhsyA3_GXD39XZmEVd1n4a1jL296b5bGYklPf0k2LnV4SzAMNT7Rq5EFIHRuUl8QXVlF0qr8JcVZd_D6YzqfVj7DHh4WQ3PahxSKzG_w" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="326" data-original-width="380" src="https://blogger.googleusercontent.com/img/a/AVvXsEgAaci5NwWtrS2gfVKIpUP7dkcCGwnuQ1yAHLmWHTfNGsl-hyP5k7wHGuDJ5bCCZS-UtQG0AsAsDkuqaDEsLjlhsyA3_GXD39XZmEVd1n4a1jL296b5bGYklPf0k2LnV4SzAMNT7Rq5EFIHRuUl8QXVlF0qr8JcVZd_D6YzqfVj7DHh4WQ3PahxSKzG_w=s16000" /></a></div></div><p>The first step is to import the libraries used by the compass generator. A couple of these are already used by <b style="font-variant-caps: small-caps;">Dragons Abound</b>. One of these is <a href="https://nearley.js.org/" target="_blank">Nearley</a>, but <b style="font-variant-caps: small-caps;">Dragons Abound</b> is a couple of releases behind the compass generator. So the first step is to switch <b style="font-variant-caps: small-caps;">Dragons Abound</b> to the current version of Nearley and see if that breaks anything. It seems to work fine, at least for the first few test maps I generated.</p><p>The second step is to bring all the code into <b style="font-variant-caps: small-caps;">Dragons Abound </b>and see if it will run. In the compass generator, I had a simple function that ran the compass rules and then drew the compass at [100, 100] and size 75 on the test SVG. I need to tweak that function to use the correct location and size for the map. Otherwise the compass code “should" work as is.</p><p>But the code immediately dies with rather an odd error, which I trace down to a Compass Description Language (CDL) command that reads:<br /><br /><span> </span>SPACE(NaN)</p><p>A little more tracing and I eventually figure out that the problem is with a utility function called randIntRange(), which is supposed to generate a random integer in the given range. I borrowed that function from <b style="font-variant-caps: small-caps;">Dragons Abound</b>, but in the course of the Compass work, I beefed up that function a bit to take a wider variety of more convenient inputs, like so:</p><pre style="background: rgb(255, 255, 255);"><span style="color: dimgrey;"></span></pre><blockquote><pre style="background: rgb(255, 255, 255);"><span style="color: dimgrey;">// Random integer in a range.</span>
<span style="color: dimgrey;">// randIntRange(lo, hi)</span>
<span style="color: dimgrey;">// randIntRange([lo, hi])</span>
<span style="color: dimgrey;">// randIntRange(hi) lo == 0</span>
<span style="color: maroon; font-weight: bold;">function</span> randIntRange<span style="color: #808030;">(</span>lo<span style="color: #808030;">,</span> hi<span style="color: #808030;">=</span><span style="color: #0f4d75;">false</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span><span style="color: #808030;">!</span>hi<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span><span style="color: #797997;">Array</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">isArray</span><span style="color: #808030;">(</span>lo<span style="color: #808030;">)</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
hi <span style="color: #808030;">=</span> lo<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
lo <span style="color: #808030;">=</span> lo<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
hi <span style="color: #808030;">=</span> lo<span style="color: purple;">;</span>
lo <span style="color: #808030;">=</span> <span style="color: #008c00;">0</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">floor</span><span style="color: #808030;">(</span>randInt<span style="color: #808030;">(</span>hi<span style="color: #808030;">-</span>lo<span style="color: #808030;">+</span><span style="color: #008c00;">1</span><span style="color: #808030;">)</span><span style="color: #808030;">+</span>lo<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre><p></p></blockquote><p>The flexibility of Javascript isn't always a blessing, but here I'm using it to create a function that takes any sort of representation of a range and “does the right thing." This new version is compatible with the old version, so I just have to replace the version in <b style="font-variant-caps: small-caps;">Dragons Abound</b> with the one from the compass generator. </p><p>That fix in place, the next problem is an attempt to set the opacity of the compass. <b style="font-variant-caps: small-caps;">Dragons Abound</b> expects the compass generator to return an SVG element of the whole compass, but in the compass generator project I wasn't actually using the compass SVG for anything so I wasn't returning it. It's simple to tweak the generator to draw the compass in an SVG group element and return that.</p><p>At this point the code is completing, but still with errors. Some of these trace back to the size of the compass. The compass generator expects to make square compasses, but <b style="font-variant-caps: small-caps;">Dragons Abound</b> can handle non-square compasses. (Because some of the canned compasses it uses are not square.) This leads to a parameter mismatch, where the compass generator expects to get a single number as the size of the compass, and <b style="font-variant-caps: small-caps;">Dragons Abound</b> is supplying an array of width and height. But in fact, <b style="font-variant-caps: small-caps;">Dragons Abound</b> is providing square dimensions, so the generator can use either the width or the height as the size of the compass.</p><p>At that point, I'm finally getting a compass on the map:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxK_tjGkd55CMfytNXvndwP5wm0ROrVxnquPqDOg5xu65pgcx9OgbLwEiw-4bfimHRHhFwxlq7TjE2wXjtyNFaxxDjGKW1E2V4oAxjeuucghvLBdvU8moEzKxbFJlubGbcb8SR86LU4ihDtzcUDPnY04IhVNhqftlHxMnKRWrsxqbUx5J4fblKud0-6Q/s685/Image3.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="685" data-original-width="661" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxK_tjGkd55CMfytNXvndwP5wm0ROrVxnquPqDOg5xu65pgcx9OgbLwEiw-4bfimHRHhFwxlq7TjE2wXjtyNFaxxDjGKW1E2V4oAxjeuucghvLBdvU8moEzKxbFJlubGbcb8SR86LU4ihDtzcUDPnY04IhVNhqftlHxMnKRWrsxqbUx5J4fblKud0-6Q/s16000/Image3.png" /></a></div><p>The compass is a *little* too big. This is mostly another parameter mismatch; the compass generator is expecting the size to be the radius of the compass, and <b style="font-variant-caps: small-caps;">Dragons Abound</b> expects it to be the total size of the compass. So the compass is twice as big as it should be.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgPUiixTCAgzCONtPSGN4vDX9lUsptrcHlnWMRV2TS2Vb_ETO-JeM1l3gUGvzXM9bxIIvePgueA7kKMGqhYPzCncc0xgCwDn7yIdHx_naab-RZrBTcuqJhuTWTN5IfvQnff3ltNHQYTOmULj0fO4qVEAPiClfIacklC8epuVPIt0vF088xcT3BqDfMfAg" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="464" data-original-width="498" src="https://blogger.googleusercontent.com/img/a/AVvXsEgPUiixTCAgzCONtPSGN4vDX9lUsptrcHlnWMRV2TS2Vb_ETO-JeM1l3gUGvzXM9bxIIvePgueA7kKMGqhYPzCncc0xgCwDn7yIdHx_naab-RZrBTcuqJhuTWTN5IfvQnff3ltNHQYTOmULj0fO4qVEAPiClfIacklC8epuVPIt0vF088xcT3BqDfMfAg=s16000" /></a></div><br />That's better, although still a bit larger than it should be. This image also illustrates another problem -- the compass should be centered on the <a href="https://en.wikipedia.org/wiki/Rhumbline_network" target="_blank">windrose network</a>. If you look, you'll see that the center of the E and the S are centered on lines. This suggests that the bottom-right corner of the compass box is centered on the windrose network. And in fact, this is another mismatch between <b style="font-variant-caps: small-caps;">Dragons Abound</b> and the compass generator. The anchor point for images (like the canned compasses) in <b style="font-variant-caps: small-caps;">Dragons Abound</b> is usually the lower left corner of the image. For the compass generator, the anchor point is the center of the compass. So for a procedurally-generated compass, I need to shift the center accordingly.<div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUOrogLVl8rbVdmxyT0auox4rLPB-5z5B1T9W1GqBF2lL7B4xVp6AXm5SfNXbdrNN35sNQlAURhwUqeIWVAIxqUL36EXlH5VeKcKoYtNGIlM9pSuUEIjs_Qm0sNFLxpBFcqwb6x6cfI0Axu_7gaTLiK5S40anigv8X-itoPUpBtm_Qljq78azTlsSjSQ/s600/Image4.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="566" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUOrogLVl8rbVdmxyT0auox4rLPB-5z5B1T9W1GqBF2lL7B4xVp6AXm5SfNXbdrNN35sNQlAURhwUqeIWVAIxqUL36EXlH5VeKcKoYtNGIlM9pSuUEIjs_Qm0sNFLxpBFcqwb6x6cfI0Axu_7gaTLiK5S40anigv8X-itoPUpBtm_Qljq78azTlsSjSQ/s16000/Image4.png" /></a></div><br /><div>And with that the basic functionality is working. However, there are still several additional features to implement.</div><div><br /></div><div>The first and “easiest" feature is to set the font and size of the labels on the cardinal points. In the compass generator, these are specified in the <a href="https://heredragonsabound.blogspot.com/2021/12/map-compasses-part-8-radial-text.html" target="_blank">RTEXT</a> command. For <b style="font-variant-caps: small-caps;">Dragons Abound</b>, it's better to control that with the map parameters, so that the compass can use a specific font, or default to the same font as the rest of the map. So before I can fix the font problem, I first have to modify the compass generator code so that the map parameters structure gets passed down to RTEXT (and all the other CDL commands).</div><div><br /></div><div>RTEXT can be used to write text other than the cardinal point labels, so I want to be careful to maintain that capability as well. I decide that if the font specified in RTEXT is “cardinal" I'll then look up the compass font and use that instead:</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhNcl_w3Eh4ZWhUzjBcHYyOmp9lzhQVW8EurINgKR0cCywDTjKeSoa6c2M17W12ZUdkpHb9JSyfgG78gGkAO2fsoYAn1RrcNHbVFRvn27O76cqnq7Gh4zTiD8SFXCWB7FJ3mBXkhECxg1uSg92e_585Ww7XuWqwHXaWgpws7XO_hehIzBwNaa1j0N-YdQ" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="423" data-original-width="502" src="https://blogger.googleusercontent.com/img/a/AVvXsEhNcl_w3Eh4ZWhUzjBcHYyOmp9lzhQVW8EurINgKR0cCywDTjKeSoa6c2M17W12ZUdkpHb9JSyfgG78gGkAO2fsoYAn1RrcNHbVFRvn27O76cqnq7Gh4zTiD8SFXCWB7FJ3mBXkhECxg1uSg92e_585Ww7XuWqwHXaWgpws7XO_hehIzBwNaa1j0N-YdQ=s16000" /></a></div><div><br /></div><div>For the moment, that's all I'll do; I'll still take the font style and weight (e.g., bold, italic, etc.) from the RTEXT command but obviously I can pull them from the map parameters in the future if that seems desirable.</div><div><br /></div><div>The second and much more difficult feature is to swap out all the drawing routines for the corresponding <b style="font-variant-caps: small-caps;">Dragons Abound</b> routines. The compass generator uses straightforward SVG routines (such as 'circle' and 'path') to draw on the screen. This results in very precise, mechanical graphics. <b style="font-variant-caps: small-caps;">Dragons Abound</b> uses drawing routines that insert some of the imprecision of hand-drawing. By replacing the precise SVG routines with the imprecise <b style="font-variant-caps: small-caps;">Dragons Abound</b> routines I'll get something that looks a little more hand-drawn.</div><div><br /></div><div>Mostly this is straightforward. <b style="font-variant-caps: small-caps;">Dragons Abound</b> has routines to draw lines and polygons, so circles are drawn by constructing a circular polygon and then drawing the polygon. Here's an example with the “hand-drawn" parameters turned up artificially high:</div></div><div><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg6FLxiXjIHFTWVIMGyAJHczOLBQv9NoZZj90ThrAdM--zAMKuYRklX5nviAWF33nCebi8dvInmSXrk0-8jYDOvkUhkXcvDWZc0WErXsSuy7cVZqvlWqjqtX6-ZUaZJBl3Q1bTdR6Ble_rrX1Knqig7tdVh4fYf0irzgnF1KutMhjLALW-fYdae1EB1LA" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="275" data-original-width="312" src="https://blogger.googleusercontent.com/img/a/AVvXsEg6FLxiXjIHFTWVIMGyAJHczOLBQv9NoZZj90ThrAdM--zAMKuYRklX5nviAWF33nCebi8dvInmSXrk0-8jYDOvkUhkXcvDWZc0WErXsSuy7cVZqvlWqjqtX6-ZUaZJBl3Q1bTdR6Ble_rrX1Knqig7tdVh4fYf0irzgnF1KutMhjLALW-fYdae1EB1LA=s16000" /></a></div><div style="text-align: left;">Of course normally this is set to a more subtle level:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjdP60MaervaUilIV-V1f9oa80qzrwOqQ00OkuyGLKhHQ0elZqboHVOzdGIAnpA7dK-aE02kHxorrs_uvMcAXGLFKK5OgJskwMy0TrXxSihO2jHYdL4jakAysmsCxkloIPzGYAvODId9RxtG-13a1lOyzOh829GsIivOUY-iFipGlBUuL6hlXvJ7FvOkA" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="272" data-original-width="344" src="https://blogger.googleusercontent.com/img/a/AVvXsEjdP60MaervaUilIV-V1f9oa80qzrwOqQ00OkuyGLKhHQ0elZqboHVOzdGIAnpA7dK-aE02kHxorrs_uvMcAXGLFKK5OgJskwMy0TrXxSihO2jHYdL4jakAysmsCxkloIPzGYAvODId9RxtG-13a1lOyzOh829GsIivOUY-iFipGlBUuL6hlXvJ7FvOkA=s16000" /></a></div><br /><div style="text-align: left;">There are slight inconsistencies here that aren't immediately eye-catching (examine the inside circle in the NW quadrant) but still result in something that is more artistically appealing than mechanical precision. (Well, at least to me; your mileage may vary.)</div><div style="text-align: left;"><br /></div><div style="text-align: left;">A feature that <b style="font-variant-caps: small-caps;">Dragons Abound</b> has that isn't in the compass generator is to vary the width of drawn lines, again to simulate hand-drawing, or the imperfections of old printing presses on coarse paper. Here's an example turned up artificially high:</div><div style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiehiGz1Wp04V89r-8jMSKAKsGS6Ppkp-uz9lRYUFPoJhH7I2yhftW_w0WHVm4DshMLzeqm6SuTRCh3OPlX6SVzwElI47-JpT15xs1cK06Oi7q1cOD3B-Uo8Aq4a5-IBjB6JRr2FYjMv0bzaW5eol_cAK_wp9ib-RsPEocIDU2qkTvV20pYHnJI24vFcw" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="267" data-original-width="327" src="https://blogger.googleusercontent.com/img/a/AVvXsEiehiGz1Wp04V89r-8jMSKAKsGS6Ppkp-uz9lRYUFPoJhH7I2yhftW_w0WHVm4DshMLzeqm6SuTRCh3OPlX6SVzwElI47-JpT15xs1cK06Oi7q1cOD3B-Uo8Aq4a5-IBjB6JRr2FYjMv0bzaW5eol_cAK_wp9ib-RsPEocIDU2qkTvV20pYHnJI24vFcw=s16000" /></a></div>You can see that the circles behind the compass points vary quite noticeably from thick to thin. Some care has to be taken with both these effects. It's all too easy to run lines into each other or otherwise create a mess. But with the proper setting, this produces a pleasingly imperfect result: </div><div style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEj0J3t7e9iaaHH4shn1t9Biy1mgs0EZY2FugpgZIywoTF-I9aOp_WrCPAcyVnngMs7ACO9UZmyf5SD8Apzvl60FpZ4ro3x54ggVT0-ucxzBgxiwXNwYA6IGMDcSyOEMgMEbumBAA9ic34YSw4BptvCuvc3SRZYVjq8QBNHx-pTRiEXtR-OACuYvBU8tpw" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="266" data-original-width="301" src="https://blogger.googleusercontent.com/img/a/AVvXsEj0J3t7e9iaaHH4shn1t9Biy1mgs0EZY2FugpgZIywoTF-I9aOp_WrCPAcyVnngMs7ACO9UZmyf5SD8Apzvl60FpZ4ro3x54ggVT0-ucxzBgxiwXNwYA6IGMDcSyOEMgMEbumBAA9ic34YSw4BptvCuvc3SRZYVjq8QBNHx-pTRiEXtR-OACuYvBU8tpw=s16000" /></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiXIxmPiQrTNvFJDyER6G2UJ1w1LOL7OEcRdXxZGu-TgdgHpCWFGsUBENWsaz7YreIS-elikgr3hARsuK47oZdpcXcJuzvUrMZQWSMFKgnOfUFYtioFggU1T6bkbpqTsMqhKba3ARZveb7O1MIuQbARnSRWQVAXLWDhghvag8HdGprgKv-l-XAB63_xNA" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="285" data-original-width="331" src="https://blogger.googleusercontent.com/img/a/AVvXsEiXIxmPiQrTNvFJDyER6G2UJ1w1LOL7OEcRdXxZGu-TgdgHpCWFGsUBENWsaz7YreIS-elikgr3hARsuK47oZdpcXcJuzvUrMZQWSMFKgnOfUFYtioFggU1T6bkbpqTsMqhKba3ARZveb7O1MIuQbARnSRWQVAXLWDhghvag8HdGprgKv-l-XAB63_xNA=s16000" /></a></div>Of course, I can turn both these effects off if I want a very precise render. (Now go back and look at the first example compass in this posting. You probably didn't register it at the time, but if you examine it now you'll see these effects.)</div><div style="text-align: left;"><br />When I wrote the compass generator, I used black and white as the colors. In <b style="font-variant-caps: small-caps;">Dragons Abound</b>, I will also have a dark and light compass colors, but I might want to use some different colors than black and white to better match the map. To do this, I replace all the “black" and “white" color names in the compass rules with “line" for the line color, “dark" for the dark fill color, and “light" for the light fill color. Then before I render the compass, I can substitute whatever color I desire.</div></div><br />Here I replace the white with a subtle cream color to make it work better with the dominant land color:</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjBSSdgtLZipSql0fUiNdSS0UWZNkoUGbx2p8yBBv7eyqnTLjTvB4sEZ-eUgXU59sm9AOMi8mLYNDws_Yvrnz3wpN7Ebh7IMps5YTSC61VHNnUNFBUf9Xd5c3p4BbB7QC3o00RKZpJrNsvHzubgSQN02JVmXpYi38qrN-i7wERMf-zruHJh8nDwyc901Q" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="456" data-original-width="426" src="https://blogger.googleusercontent.com/img/a/AVvXsEjBSSdgtLZipSql0fUiNdSS0UWZNkoUGbx2p8yBBv7eyqnTLjTvB4sEZ-eUgXU59sm9AOMi8mLYNDws_Yvrnz3wpN7Ebh7IMps5YTSC61VHNnUNFBUf9Xd5c3p4BbB7QC3o00RKZpJrNsvHzubgSQN02JVmXpYi38qrN-i7wERMf-zruHJh8nDwyc901Q=s16000" /></a></div><div><br /></div>Lastly, I want to add the capability to use a saved CDL compass rather than generate a new one. That allows me to use a particular compass as part of a map style, or to select from a list of curated compasses. (I have something very similar for map borders.) I don't know if I'll have much use for this; almost all of the generated compasses are acceptable to my eye. But we'll see.</div><div><br /></div><div>And that's it. At some point I'm likely to revisit compasses to improve/extend the procedural generation, but for now I'm happy to have something solid working.</div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com2tag:blogger.com,1999:blog-3367557180796013182.post-77318087651254345332022-05-22T16:33:00.000-04:002022-05-22T16:33:42.293-04:00Map Compasses (Part 18): Center Ornament<p>Welcome back to my blog series on implementing procedurally-generated map compasses! Last post I looked at some elaborations of the compass points.</p><div class="separator" style="clear: both; text-align: center;"><div style="text-align: left;"><div style="text-align: center;"><img border="0" data-original-height="315" data-original-width="316" height="199" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvNMsT2b5IiTdEuDBVzU6_pLPIBN_5ZEUd6Yua4XyLqEB2xKB-9fBX85QhsaHzbcugzgR8Phw-iX7yAp_J02p07jwb3IRc2peHIKSa1BLe20YuNHTmYtvwoOvJuSfsXc0mt163GJtaqbpz/w200-h199/Image9.png" width="200" /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCyrllh4ZofpF3kTN9DAuL46NMJ3WNjptmb_KhoOYLscMbKJJ88AEeMpeweS_WCNt0oAfG8LWVhAm4_IVQ46XVEqlXs8O23lcy2LC-Ka_d7FfcGvrIuH0ECKOW6Qm8M5Su8LPn0lICM2pf/s316/Image10.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="315" data-original-width="316" height="199" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCyrllh4ZofpF3kTN9DAuL46NMJ3WNjptmb_KhoOYLscMbKJJ88AEeMpeweS_WCNt0oAfG8LWVhAm4_IVQ46XVEqlXs8O23lcy2LC-Ka_d7FfcGvrIuH0ECKOW6Qm8M5Su8LPn0lICM2pf/w200-h199/Image10.png" width="200" /></a></div>This time I want to look at adding a new feature to the compasses -- the center ornament.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">So far, all the compasses I have generated have been what I've called “two level" compasses. They comprise a back layer of various rings, and a front layer of points. Some compasses add to this a third layer that creates a center ornament on top of the rings, like this compass from the examples:<br /><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOyFSC-DN-eJGgqSdryOJsyNojdxZCiUIPXj3cgFqP27YogI9ik1XMsj4DBFDDo_vlPhmpOLSJJbNAOq2veqPzqrgkvtLV4ylCnY4sw6b5_tjtXCz6n2Pm_V9TsuorJIV6YrfxuwPh7QhG/s600/compass11.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="579" data-original-width="600" height="309" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOyFSC-DN-eJGgqSdryOJsyNojdxZCiUIPXj3cgFqP27YogI9ik1XMsj4DBFDDo_vlPhmpOLSJJbNAOq2veqPzqrgkvtLV4ylCnY4sw6b5_tjtXCz6n2Pm_V9TsuorJIV6YrfxuwPh7QhG/w320-h309/compass11.png" width="320" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div style="text-align: left;">Most often the center ornament is a filled circle of some sort that covers the center part of the compass where the primary points meet. Sometimes it is just a ring, as in this example:<br /><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5YmBwjzM66CUveeyypK8A93yaqcGDtp421OprnXIYu0BOy2StluZCUtesY-iBcJW8h8M_vGzQFN_myfPsPKe_6o1GypK2ojITgnRgSJEcXJ0Xppm1jJm_LbCILYbn7jOFDtgHDlQWsq6s/s646/Image26.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="639" data-original-width="646" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5YmBwjzM66CUveeyypK8A93yaqcGDtp421OprnXIYu0BOy2StluZCUtesY-iBcJW8h8M_vGzQFN_myfPsPKe_6o1GypK2ojITgnRgSJEcXJ0Xppm1jJm_LbCILYbn7jOFDtgHDlQWsq6s/s320/Image26.png" width="320" /></a></div></div>Sometimes the circle is filled with an illustration of some sort, as in this example:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgENvecN49zTZ-MIY_GOjyLfFUUyyNnfC_yqMClmk2Z-I3Ky0bq9blO769LGLVH667lMsK8wglstrhSr6jpoYE47-CRIbZl-OonRgRLRjs8n5rQXEYNSUV08rQNAuiENAMH86YUjaYCeYwh/s381/Image23.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="381" data-original-width="275" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgENvecN49zTZ-MIY_GOjyLfFUUyyNnfC_yqMClmk2Z-I3Ky0bq9blO769LGLVH667lMsK8wglstrhSr6jpoYE47-CRIbZl-OonRgRLRjs8n5rQXEYNSUV08rQNAuiENAMH86YUjaYCeYwh/s320/Image23.png" width="231" /></a></div><div class="separator" style="clear: both; text-align: left;">I don't have any way to draw illustrations (yet?) so I'll work on some circles and rings.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">To start with, a three layer compass is just a two layer compass with an added center decoration:</div><div class="separator" style="clear: both; text-align: left;"><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>compass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>threeLayerCompass<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>labels<span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">start</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>threeLayerCompass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">></span> <span style="color: #808030;"><</span>centerDecoration<span style="color: #808030;">></span></pre></div><div class="separator" style="clear: both; text-align: left;">For the center decoration, I'll start with a plain filled white circle. To place it, I need to move the “cursor" to the radius I want. Generally, I'd like the circle come out to about where the shoulders meet on the four cardinal pointers. This turns out to be somewhere in the range of 51-58 pixels in from the outside of the compass, but I'll add some to the range so that I can get circles that more than cover the center, or that reveal some of the center. To get to that radius, I'll use RECALL to get back to outside of the compass points, and then SPACE to move inward to around where the four cardinal points meet:</div><div class="separator" style="clear: both; text-align: left;"><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>centerDecoration<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RECALL<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">start</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randRange(48,63)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> CIRCLE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">1</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></div>Some examples:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhji4Cm5MVNXpEC_rGbSMdkr56yPsN6Pq57bSPWISFyuRyPkd82lKWNbFVWnq0a0AT21dLy0IxkXKXjNnxR6SwJemYRYIk9q0iepM16K4eVRsa7j1XPlV0Ns7gwZ4pUAQrsoQ-AV-8XN8cS4K2xcpkS4vLadZQ9ErExBb6lMrP4E0AVh1Mknpt3aj3ymg=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEhji4Cm5MVNXpEC_rGbSMdkr56yPsN6Pq57bSPWISFyuRyPkd82lKWNbFVWnq0a0AT21dLy0IxkXKXjNnxR6SwJemYRYIk9q0iepM16K4eVRsa7j1XPlV0Ns7gwZ4pUAQrsoQ-AV-8XN8cS4K2xcpkS4vLadZQ9ErExBb6lMrP4E0AVh1Mknpt3aj3ymg=s16000" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiyJIO4voxmuH4I5JnS2VbyoCVQVix2TDkOooeRmlAntjT09Wg68HdAXfCqEZy737V2U4fkZP-l19_JHUtZwtFXRCcsXVppDeQmi-0FTyX8MJfpUD7sXI_gvUF9h0zQrWkVIs1slXbUhffWRu5Q3yYm-rlGGezo_lHtUtjtLVe0qeBvr2mH_e09vnMHXw=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEiyJIO4voxmuH4I5JnS2VbyoCVQVix2TDkOooeRmlAntjT09Wg68HdAXfCqEZy737V2U4fkZP-l19_JHUtZwtFXRCcsXVppDeQmi-0FTyX8MJfpUD7sXI_gvUF9h0zQrWkVIs1slXbUhffWRu5Q3yYm-rlGGezo_lHtUtjtLVe0qeBvr2mH_e09vnMHXw" width="300" /></a></div><div class="separator" style="clear: both; text-align: left;">So that works, but I really don't like hardcoding the range for the center decoration into the rule. I'd rather figure out where the shoulder of the cardinal points is and work from there. Unfortunately as it stands that information isn't available. The rule that creates the cardinal points looks like this:</div><div class="separator" style="clear: both; text-align: left;"><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RECALL<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">start</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
RPOINT<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randRange(0.825,0.925)</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span>
<span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></div><div class="separator" style="clear: both; text-align: left;">The third parameter to the RPOINT command is what places the shoulders of the cardinal points. Since it is less than 1, it is treated as a percentage of the current radius, and the actual value is calculated in the implementation of RPOINT, so it isn't known until the CDL is actually executed.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">To work around this, I'll create a special RECALL point “shoulder" with the idea that this won't be set by a REMEMBER command but instead set within the execution of the CDL when an RPOINT is drawn. Since the cardinal points are always the last ones drawn, this will let me RECALL back to exactly the correct radius.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">To do this, I'll modify Draw.rpoint so that it returns the radius at the shoulder, and I'll modify Draw.repeat to return that as well. Then, in the CDL interpreter I can do this:</div><div class="separator" style="clear: both; text-align: left;"><pre style="background: rgb(255, 255, 255);"> } <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">RPOINT</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span><br /> <span style="color: dimgrey;">// Draw radial compass points</span>
<span style="color: maroon; font-weight: bold;">let</span> shoulder <span style="color: #808030;">=</span> Draw<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">repeat</span><span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>start<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>repeats<span style="color: #808030;">,</span> op<span style="color: #808030;">,</span> Draw<span style="color: #808030;">.</span>rpoint<span style="color: #808030;">)</span><span style="color: purple;">;</span>
memory<span style="color: #808030;">[</span><span style="color: maroon;">'</span><span style="color: #0000e6;">shoulder</span><span style="color: maroon;">'</span><span style="color: #808030;">]</span> <span style="color: #808030;">=</span> shoulder<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Draw radial compass points.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span>
</pre></div><div class="separator" style="clear: both; text-align: left;">and save that value into the memory under the name 'shoulder' so that I can then jump to that radius with a RECALL command. Now I can write:</div><div class="separator" style="clear: both; text-align: left;"><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>centerDecoration<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RECALL<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">shoulder</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> CIRCLE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">1</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></div><div class="separator" style="clear: both; text-align: left;">and the center decoration will be exactly the size to reach the shoulders of the cardinal points, regardless of the configuration.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiIdydEP5t8l-axfw7NFrg4qOF170JFpTdazyVd4RtBbc24r_QVGkEKG_hVxAi6WI6kI0c1joEhsGlDMAhWXCfoLU6CJoBQ5ZW49I5O71fNCT6LzizoE0g2c8wA--drao15afQBBUIi64p_ORc6DEK7inwTMgqvcN8-sQ5seite9bwv03AlCGcO3org7A=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEiIdydEP5t8l-axfw7NFrg4qOF170JFpTdazyVd4RtBbc24r_QVGkEKG_hVxAi6WI6kI0c1joEhsGlDMAhWXCfoLU6CJoBQ5ZW49I5O71fNCT6LzizoE0g2c8wA--drao15afQBBUIi64p_ORc6DEK7inwTMgqvcN8-sQ5seite9bwv03AlCGcO3org7A" width="300" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgJLoXytZn5wmAS0emTLEIWVgMxopQAbIAXaeqxR9CZKWeS2AY5ueDSySim8t7qJWIdIUJG89zN-BJvqLZut_F4N4gTVwMJzyBLaM66WwnqgUDeFr9I8rs0TVUjsnmZ2IqNBR_5KUTcK-6kk1IStxzoIZ5d1fOj5dnsRunLABgXSEK69Z8uWCfYtodWRg=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEgJLoXytZn5wmAS0emTLEIWVgMxopQAbIAXaeqxR9CZKWeS2AY5ueDSySim8t7qJWIdIUJG89zN-BJvqLZut_F4N4gTVwMJzyBLaM66WwnqgUDeFr9I8rs0TVUjsnmZ2IqNBR_5KUTcK-6kk1IStxzoIZ5d1fOj5dnsRunLABgXSEK69Z8uWCfYtodWRg" width="300" /></a></div><br /><div class="separator" style="clear: both; text-align: left;">It's nice to have a little variation on the size of the center decoration. I can do this by putting in a random SPACE to move the radius in or out a bit.</div><div class="separator" style="clear: both; text-align: left;"><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>centerDecoration<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RECALL<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">shoulder</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.rand(-2,2)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
CIRCLE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">1</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjAlyHCWPBRrx1EayHl8qMSvVllnrEg3KoeWt6OpYDKNxWGEehwNQ1ALPVp5xDIUbIVEXGjRZwPZardm1yWgEOiv91c2TXB5KZRpOGX3wkUb3-K14hfcKGqwyzc2xCdnmVMSrk74BOo2P5fdEQymORl7IZmAn4Q1t0M_FdyM_V3mqCjF96gRQDV3-UiNQ=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEjAlyHCWPBRrx1EayHl8qMSvVllnrEg3KoeWt6OpYDKNxWGEehwNQ1ALPVp5xDIUbIVEXGjRZwPZardm1yWgEOiv91c2TXB5KZRpOGX3wkUb3-K14hfcKGqwyzc2xCdnmVMSrk74BOo2P5fdEQymORl7IZmAn4Q1t0M_FdyM_V3mqCjF96gRQDV3-UiNQ" width="300" /></a></div><br /><div class="separator" style="clear: both; text-align: left;">With that in place, let me elaborate that a bit and do multiple circles, as I did with rings:</div><div class="separator" style="clear: both; text-align: left;"><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>centerDecoration<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RECALL<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">shoulder</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.rand(-2,2)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>circleFilled<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>circleFilled<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircleFilled<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thickCircleFilled<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thickThinFilled<span style="color: #808030;">></span> <span style="color: #808030;">|</span>
<span style="color: #808030;"><</span>thinThickFilled<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$circleSpacingFilled<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">1</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">2</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">2</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">3</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thickThinFilled<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thickCircleFilled<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$circleSpacingFilled<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircleFilled<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thinThickFilled<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircleFilled<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$circleSpacingFilled<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thickCircleFilled<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thinThinThinFilled<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircleFilled<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$circleSpacingFilled<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircleFilled<span style="color: #808030;">></span>
SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$circleSpacingFilled<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircleFilled<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thinThickThinFilled<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircleFilled<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$circleSpacingFilled<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thickCircleFilled<span style="color: #808030;">></span>
SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$circleSpacingFilled<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircleFilled<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$thickWidthFilled<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: green;">2.5</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">2</span> <span style="color: #808030;">|</span> <span style="color: green;">1.5</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thickCircleFilled<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> CIRCLE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$thickWidthFilled<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thinCircleFilled<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> CIRCLE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$thinWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></pre></pre></div><div>This is just the ring rules copied and slightly modified to suit the center decoration. It produces circles within circles, some with thick outlines. Some examples:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgYwSJq_hpKWdzcvZR400t3q72eAOAGOY3Y5xXrynsXH5M5pIOukHOICEKmD3RCPdjum_Cxq6p-SusqPISnFfyOVOTuuGSea5MGQG62luNlFE60Dsth0A3efDt7Z4IkifHKWsJeMHh4QHWNFEfsRR7FzCBCeAjBQ_-9-n4Lpmf85ZyAcaRIKn-iSttfrA=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEgYwSJq_hpKWdzcvZR400t3q72eAOAGOY3Y5xXrynsXH5M5pIOukHOICEKmD3RCPdjum_Cxq6p-SusqPISnFfyOVOTuuGSea5MGQG62luNlFE60Dsth0A3efDt7Z4IkifHKWsJeMHh4QHWNFEfsRR7FzCBCeAjBQ_-9-n4Lpmf85ZyAcaRIKn-iSttfrA" width="300" /></a></div><br /><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgCdXeT-BXAhFCLbUQFbLuDEZ0aEyWf7XtIp2UTO67SEyXlE9ryq4FjnLZQ7DteUSULqgFEqYKk1DrGm6WRuTy8fUA7Og6U0Icq06PPo5xs5A_akgWnZkWvahH5voy1QF_UYr7HfhhqIQrnE7TtsLaWGG5yGP0w_s2PSHMztO8YJP4e0oirrxsYbxPk9A=s374" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="374" data-original-width="374" height="320" src="https://blogger.googleusercontent.com/img/a/AVvXsEgCdXeT-BXAhFCLbUQFbLuDEZ0aEyWf7XtIp2UTO67SEyXlE9ryq4FjnLZQ7DteUSULqgFEqYKk1DrGm6WRuTy8fUA7Og6U0Icq06PPo5xs5A_akgWnZkWvahH5voy1QF_UYr7HfhhqIQrnE7TtsLaWGG5yGP0w_s2PSHMztO8YJP4e0oirrxsYbxPk9A=s320" width="320" /></a></div>I don't love duplicating a big chunk of rules this way, but this is an area where using a rule-based generator has shortcomings. There isn't really a notion of a “parameter" in these types of rules, so that limits how reusable the rules can be.<div><br /></div><div>A variant of this type of center decoration is to have the inner circle be filled with black (as in one of the example compasses above). That produces center decorations like this one:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhWQlEj2huc5PFet8b_caiSGwgcv24YC1xSn_GRjPwVFLwbqhYWyeO9fFO1Vhe3aqxL92PYDJgRuEAaAMSK5fosjEpnrUrAzYc5pRfqQM-ekWlZ-Tfs_vUYWJ-X5yYapKSHwYGR8HjbDPxNkqSaH_9LLWxUG4SMejefAv4TVGzBBeRZ6wLz9s2RjZL3qw=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEhWQlEj2huc5PFet8b_caiSGwgcv24YC1xSn_GRjPwVFLwbqhYWyeO9fFO1Vhe3aqxL92PYDJgRuEAaAMSK5fosjEpnrUrAzYc5pRfqQM-ekWlZ-Tfs_vUYWJ-X5yYapKSHwYGR8HjbDPxNkqSaH_9LLWxUG4SMejefAv4TVGzBBeRZ6wLz9s2RjZL3qw" width="300" /></a></div><div><br /></div><div>Here's an example of a different style of center decoration:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiKOr7f37x-4vNg5xrS5lNre8PU12FkYIycCrj2mOjL3xoNaWyhDUs_Cqy-OTD0pIyqGaXyPd0xL7hjnB2bfsK2_4Y4W5goU5N8LYYQtNgrgCMIc5aKRP6aBWmZjubwks1W3GrmKaElveW3HGAu0msJLKPZ3qHyvcbcIqAfDrzQmoGemQ_EfGs7-y7ybQ=s646" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="639" data-original-width="646" height="317" src="https://blogger.googleusercontent.com/img/a/AVvXsEiKOr7f37x-4vNg5xrS5lNre8PU12FkYIycCrj2mOjL3xoNaWyhDUs_Cqy-OTD0pIyqGaXyPd0xL7hjnB2bfsK2_4Y4W5goU5N8LYYQtNgrgCMIc5aKRP6aBWmZjubwks1W3GrmKaElveW3HGAu0msJLKPZ3qHyvcbcIqAfDrzQmoGemQ_EfGs7-y7ybQ=s320" width="320" /></a></div>Here the middle of the center decoration is empty, and let's the compass underneath show through. This has to be drawn in a slightly different way. It's really three lines: a thin black line, a thick white line, and another thin black line.<div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>whiteRing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> <span style="color: #808030;"><</span>thickWhiteCircle<span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thickWhiteCircle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> CIRCLE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>thickWhiteWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thickWhiteWidth<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">3</span> <span style="color: #808030;">|</span> <span style="color: green;">2.5</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">2</span><span style="color: purple;">;</span>
</pre></div><div>Here again there's a certain amount of redundancy with the similar rules for the first layer of the compass; since you can't parameterize rules for the fill color or the width, this is inevitable.<br /><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEj_lPFV45o-TNDNfRm94TCJlbS2bFaWL-O6F4_QHBdLUfPgjRMcxbSjOzmjpM1K6XxnUC9WWwyy9PaxkRWhRebuhNUDtUTJHLT-mXgw9nb7sNlI4lmrsMO3oSAddtX65NLA9fjd_Zo-AUHBqekvtAAWkZNaHACm6uNCy323KO4PIktmVnGksSOqOxtQJw=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEj_lPFV45o-TNDNfRm94TCJlbS2bFaWL-O6F4_QHBdLUfPgjRMcxbSjOzmjpM1K6XxnUC9WWwyy9PaxkRWhRebuhNUDtUTJHLT-mXgw9nb7sNlI4lmrsMO3oSAddtX65NLA9fjd_Zo-AUHBqekvtAAWkZNaHACm6uNCy323KO4PIktmVnGksSOqOxtQJw" width="300" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEj3x8Br_dsPQTON6nRTeMoM5uA980XVr7vzuwWphDE2Dfa055We-aveSw89SNeGTT32v37093T1_OmClmUA9Nbr4-b8Nazategd21fweKtiIqGrayCXaomW6L8FzbTf2xtrrnEZOOmgF9u7oDbF-MbR1s84KF4iz7mW3TCfX3EdAMgLM4m44SFGfJDR6g=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEj3x8Br_dsPQTON6nRTeMoM5uA980XVr7vzuwWphDE2Dfa055We-aveSw89SNeGTT32v37093T1_OmClmUA9Nbr4-b8Nazategd21fweKtiIqGrayCXaomW6L8FzbTf2xtrrnEZOOmgF9u7oDbF-MbR1s84KF4iz7mW3TCfX3EdAMgLM4m44SFGfJDR6g" width="300" /></a></div><br /><div>The second example here is on the edge of what I consider acceptable. Since the rules have no way of knowing the current radius, there's no way to change the style of the center decoration for the compasses with very narrow cardinal points.</div><div><br /><div>This compass from my examples has a scale as part of the center decoration:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgfDu_UNe4VMobr0zI4dWkZzZ-jZJ7K2MXASvMhg8D4Ch4TsuUfK6NE5IEtCOjSg1jAv3cpyQI4sIPKr2Cfk9wZIBlJJkArHjQFeEhomirxwwprJNeP8RnUoFDIHDPn9AUFE2E5VNN8kqofUuVh-S8gR9tXQ-EEuNCVDwl0fh3PDyg50N-gaQXREaoUKQ=s490" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="490" data-original-width="490" height="320" src="https://blogger.googleusercontent.com/img/a/AVvXsEgfDu_UNe4VMobr0zI4dWkZzZ-jZJ7K2MXASvMhg8D4Ch4TsuUfK6NE5IEtCOjSg1jAv3cpyQI4sIPKr2Cfk9wZIBlJJkArHjQFeEhomirxwwprJNeP8RnUoFDIHDPn9AUFE2E5VNN8kqofUuVh-S8gR9tXQ-EEuNCVDwl0fh3PDyg50N-gaQXREaoUKQ=s320" width="320" /></a></div>There are several differences between the scale I'll use here and the ones used further out in the compass. There isn't room for a lot of the elaboration I was able to use there, and likewise the lines have to be generally quite thin. Also, it's problematic to start this scale with 8 segments in synch with the cardinal points, because it “disappears" as in this example:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhhdifLKCYh-2aD4k6sifoXF33-hK0tHkmKwDTPkN6hiM3zLOffnsQJ3HbI2AdMvZfR0F9UkQmmA9AdQWf0OEZJJD7KcU08e3tjg2HSWl9kEkFjXf7C181Piu9oOrqse3Tmsd8d4Kti6rsyTK3DNQfCfYIROEiJL_VxWcp7HrIM-q23ne1pUTINAOamfQ=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEhhdifLKCYh-2aD4k6sifoXF33-hK0tHkmKwDTPkN6hiM3zLOffnsQJ3HbI2AdMvZfR0F9UkQmmA9AdQWf0OEZJJD7KcU08e3tjg2HSWl9kEkFjXf7C181Piu9oOrqse3Tmsd8d4Kti6rsyTK3DNQfCfYIROEiJL_VxWcp7HrIM-q23ne1pUTINAOamfQ" width="300" /></a></div>Using just the simplest form of the scale seems to work the best:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjOhJm9rZh_VjFtKHuhCfFIXJPw98xTcZHU25zGdDDawyu-sS_6qeETZkr_HycMZDdv4FjwQT3Y0FITHrSkd_E5vlf8W0Bqm09OfGj7hCEJItkbW_vhJSaiC-nvgxI3EEFbCTmjchRUX2YS3oxt_O5tdJFxBhR4b4dolDsJt6hBElP2vBJ3il-xkaDyTw=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEjOhJm9rZh_VjFtKHuhCfFIXJPw98xTcZHU25zGdDDawyu-sS_6qeETZkr_HycMZDdv4FjwQT3Y0FITHrSkd_E5vlf8W0Bqm09OfGj7hCEJItkbW_vhJSaiC-nvgxI3EEFbCTmjchRUX2YS3oxt_O5tdJFxBhR4b4dolDsJt6hBElP2vBJ3il-xkaDyTw" width="300" /></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg1lLcfGEj2kXo-bI_MOaLNT1Q4UxutKUW9VSX-uhrZVejUpWCL_fPddyBZYfvX3bQjLhl6-yYanJRqNNDmPLzfyAyISOhQSEKLofzO34-xhvpPADJWG7EDUKjK0AbIHGAUUUTn8Xkwa5rjIGXpp1nWthGmjfT4utG7IevpIjAv5tvXHMG_2WmxLmlxLw=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEg1lLcfGEj2kXo-bI_MOaLNT1Q4UxutKUW9VSX-uhrZVejUpWCL_fPddyBZYfvX3bQjLhl6-yYanJRqNNDmPLzfyAyISOhQSEKLofzO34-xhvpPADJWG7EDUKjK0AbIHGAUUUTn8Xkwa5rjIGXpp1nWthGmjfT4utG7IevpIjAv5tvXHMG_2WmxLmlxLw" width="300" /></a></div><br /><div>At it's smallest, it really just looks like a pattern or even something like a flower.</div><div><br /></div>Speaking of flowers, let's do a flower. This is something that's going to take some careful planning, so I'll implement some better mechanisms for knowing where the current radius is and moving to a specific point. To start with, I'll implement a one-time variable that will always be the current radius. I don't know if I'll really need this, but it seems useful and easy enough to do. I just add this line at the top of interpretCDL:<div><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="background-color: white;"> <span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> i<span style="color: #808030;">=</span><span style="color: #008c00;">0</span><span style="color: purple;">;</span>i<span style="color: #808030;"><</span>cdl<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span><span style="color: purple;">;</span>i<span style="color: #808030;">++</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> op <span style="color: #808030;">=</span> cdl<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: purple;">;</span>
</span><span style="background-color: #fcff01;">memory<span style="color: #808030;">[</span><span style="color: maroon;">'</span><span style="color: #0000e6;">radius</span><span style="color: maroon;">'</span><span style="color: #808030;">]</span> <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span></span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">if</span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">op</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">op </span><span style="background-color: white; color: #808030;">==</span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #0000e6;">CIRCLE</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> </span><span style="background-color: white; color: purple;">{</span></pre><div>Next I'll implement a command to move to a specific radius. The SPACE command moves relative to the current radius; the MOVE command will just jump to the given radius. In the CDL grammar, the command looks just like SPACE; in the interpreter it looks like this:</div><div><pre style="background: rgb(255, 255, 255);"> } <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">SPACE</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span><br /> <span style="color: dimgrey;">// Skip some space</span>
radius <span style="color: #808030;">-</span><span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>n<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Skip some space.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">MOVE</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Move to the specified radius</span>
radius <span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>n<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Move to a radius.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></div><div>So now I can just jump to a particular radius that works well for a flower. We'll start with just a blank canvas:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiUP85Id9uO0-NCrhFXVgKT_8PeWo0RFVYceokca0n71i_Fo-vH2oEkqUXTRJc9h1X0UZqZnERigUCqH_qNKDCgcOUqQ1Ip1ded-DKN-s9GXwcTNn7_3Z2DhhCtaY4SvQMKFn4HbrFmc0J58HinfrmXoCedRrLJL6WVi5uzmy0ZSrCxAJ_uLSays2Fr_g=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEiUP85Id9uO0-NCrhFXVgKT_8PeWo0RFVYceokca0n71i_Fo-vH2oEkqUXTRJc9h1X0UZqZnERigUCqH_qNKDCgcOUqQ1Ip1ded-DKN-s9GXwcTNn7_3Z2DhhCtaY4SvQMKFn4HbrFmc0J58HinfrmXoCedRrLJL6WVi5uzmy0ZSrCxAJ_uLSays2Fr_g" width="300" /></a></div>Now we'll add some radial circles.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgogQXlzqsWeLdmPFPJ5JQTR-Uj1D0Q4-T52JeuJc3cs8pStzzF6pg7H27QLrktU0xlzg1Wjjv5sDTB9ZNDYZUz4RCSgJ8JxXqs1BkcmEpQncy5t_-LdHhEXKpztbgUwvqBIi2z0gyhq0zY4HQNPH9NEP5SXNlyfzIh6YidSQJ99uUMDXl-HVECFjRSPg=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEgogQXlzqsWeLdmPFPJ5JQTR-Uj1D0Q4-T52JeuJc3cs8pStzzF6pg7H27QLrktU0xlzg1Wjjv5sDTB9ZNDYZUz4RCSgJ8JxXqs1BkcmEpQncy5t_-LdHhEXKpztbgUwvqBIi2z0gyhq0zY4HQNPH9NEP5SXNlyfzIh6YidSQJ99uUMDXl-HVECFjRSPg" width="300" /></a></div>Now I'll move back out to the center of the radial circles and draw a white circle.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg-Ohtu5VU_SShTY8WK1OUgdVLDIpkLdXZAZ_G9gbuv6FGp1ZZqHpoQCV0XPr_mlFGYl9wcI4CLFQtEjU3cXDMCcrr1X0CFche3Sm1R42y0r9YNkk1GpkvrT8xmyf7P4i9ukogDT4akd1gfw9EAcYfhi9O_oACUbDvhpVqlnxnsYh9rmZrYd1UoA1Mp8A=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEg-Ohtu5VU_SShTY8WK1OUgdVLDIpkLdXZAZ_G9gbuv6FGp1ZZqHpoQCV0XPr_mlFGYl9wcI4CLFQtEjU3cXDMCcrr1X0CFche3Sm1R42y0r9YNkk1GpkvrT8xmyf7P4i9ukogDT4akd1gfw9EAcYfhi9O_oACUbDvhpVqlnxnsYh9rmZrYd1UoA1Mp8A" width="300" /></a></div><br /><div>That looks pretty cool just like that, so I'm going to keep that as an option. But to carry on, I'll now draw radial lines to the center of the compass.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg1Rk707QpgIPgvk4tizztogPEZE2dcG0-GVcHV5nBzPG59nV8WEvb1i7VWMDJkr7_mMJmxEv4lLkH3BRwPSZmdHaVYFGYhUcL5DSwFn9OpTDwBwNfT2X0cBXpLx1Gn_w5-l4JFaSe096gFwescnUGYf0oGz5kk_g-1dEnKEDj4wcCPp7A8cyAfiDPtAg=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEg1Rk707QpgIPgvk4tizztogPEZE2dcG0-GVcHV5nBzPG59nV8WEvb1i7VWMDJkr7_mMJmxEv4lLkH3BRwPSZmdHaVYFGYhUcL5DSwFn9OpTDwBwNfT2X0cBXpLx1Gn_w5-l4JFaSe096gFwescnUGYf0oGz5kk_g-1dEnKEDj4wcCPp7A8cyAfiDPtAg" width="300" /></a></div>That isn't what I was intending to do but again it looks pretty good so I'll keep it. What I want is to shift the lines so they match up with the radial circles to form petals.<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhFRyvz7iykgVlaRjutG4ATZds6MFpTnSPTIVwf4ViA5WPfXEUPQcO3moCn8LVXBkNYjEvVKxq1-hzK0hEpSIQWAF9Raue7YriwON0CQFYJLSFMUCkGeKIT1LMIIcxs3z9Qn3ilB-4KKevHU9jp8zBcL5cm8wZPtpTjrJkCVo_K2nn-5HdUKKieXT0XlA=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEhFRyvz7iykgVlaRjutG4ATZds6MFpTnSPTIVwf4ViA5WPfXEUPQcO3moCn8LVXBkNYjEvVKxq1-hzK0hEpSIQWAF9Raue7YriwON0CQFYJLSFMUCkGeKIT1LMIIcxs3z9Qn3ilB-4KKevHU9jp8zBcL5cm8wZPtpTjrJkCVo_K2nn-5HdUKKieXT0XlA" width="300" /></a></div>Optionally, I want to add a white or black center disk:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgD6LdX8ahlr1_mLjmhpYpzlIfWPoW3VG6RAEiJX_XYaeOojsJS6QQTRw4K_NVbYoroRdrA-N8CdLhfkVamCa13yfDuoSctYlMeFCb-eXepFqqmpSaqoPyKix2Ts_iQ6OcQ5b7b43I5daLhxLhaW4xqgLIN_4nA7XFMMladfx4uBTbjfKAuwh5ft7V-0w=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEgD6LdX8ahlr1_mLjmhpYpzlIfWPoW3VG6RAEiJX_XYaeOojsJS6QQTRw4K_NVbYoroRdrA-N8CdLhfkVamCa13yfDuoSctYlMeFCb-eXepFqqmpSaqoPyKix2Ts_iQ6OcQ5b7b43I5daLhxLhaW4xqgLIN_4nA7XFMMladfx4uBTbjfKAuwh5ft7V-0w" width="300" /></a></div><div><div><div><div><div><div><br /></div></div></div></div></div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhaOvr2Tb86uVqpI3n8UvqZWwbnNPc2eeNVvfCqR154TVY0iVRvGR1sv4NGcpBCC1GOrXTpmOcODgC2nm5JLD_OOmUFqI0rAMds4aHHYmpo793wNtjEZMB53rHe7tCSjQI2aOfLcbfNTGYHQTkfHI4AWJDBXDAsCWMMzJR-XCljZ3_LI5Sv8GA83R58dw=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEhaOvr2Tb86uVqpI3n8UvqZWwbnNPc2eeNVvfCqR154TVY0iVRvGR1sv4NGcpBCC1GOrXTpmOcODgC2nm5JLD_OOmUFqI0rAMds4aHHYmpo793wNtjEZMB53rHe7tCSjQI2aOfLcbfNTGYHQTkfHI4AWJDBXDAsCWMMzJR-XCljZ3_LI5Sv8GA83R58dw" width="300" /></a></div>It can also look good to put the flower on a black background:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgQR_YgU7wkUNoQ0HeLUGWjfv48r5OVs4t4WeaM7OTWarthuIhLYGroV_nx9uCcTP19pxt15NVcWpoO8nMcLp_M1bnpXs24MBEwGgfBkGR4RMsaa0XS9AjoZ-UqCXchUYR6-xEn1qEvzG76nEM2lwGmPDZySUjiX0L2E4QQBT9jxtCXj4Kx6J_E5y2LmA=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEgQR_YgU7wkUNoQ0HeLUGWjfv48r5OVs4t4WeaM7OTWarthuIhLYGroV_nx9uCcTP19pxt15NVcWpoO8nMcLp_M1bnpXs24MBEwGgfBkGR4RMsaa0XS9AjoZ-UqCXchUYR6-xEn1qEvzG76nEM2lwGmPDZySUjiX0L2E4QQBT9jxtCXj4Kx6J_E5y2LmA" width="300" /></a></div><div>Here's another fun variant -- it should be obvious how it is drawn:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEikddHdDgnJscGmFSqgXTl-j_ZVQL2wqg-6HF6XSd4lq3H0WvEW0fshuP7dTlHCZ6ZBxDluKfSzI6luCaLJKZqR4CaY4R9HmILxR1DWXcJ7wwvLxUvLvfIwQ2hJ1lPpa9tRy2sdC0Z86t8-8Qr2q0vwLF5u2DN_ddtsJZSMJgWd5T9_6_lbQXLNxQFfWA=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEikddHdDgnJscGmFSqgXTl-j_ZVQL2wqg-6HF6XSd4lq3H0WvEW0fshuP7dTlHCZ6ZBxDluKfSzI6luCaLJKZqR4CaY4R9HmILxR1DWXcJ7wwvLxUvLvfIwQ2hJ1lPpa9tRy2sdC0Z86t8-8Qr2q0vwLF5u2DN_ddtsJZSMJgWd5T9_6_lbQXLNxQFfWA" width="300" /></a></div><div>You can see the rules for all of these in the compass.rules file.</div><div><br /></div><div>I can also do a center star of sorts:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEh5ic3DChgm3IxEAm1CC3QKRZ49P5724hq4nnzGjpeaA4Gii8mcEAs7bAWlUCDjERh3jG-GRsXYXQigrHMe97iDO-ve81R4B3Z0-qYGx8Oy_AtGFRLPetGrR0gZHZfr9avsA7sIjZ01QWNe6L459r5UwPYFA8Tk6iKDWh6Z3BaNYOG1kLTEkdRpHfU-jA=s300" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" height="300" src="https://blogger.googleusercontent.com/img/a/AVvXsEh5ic3DChgm3IxEAm1CC3QKRZ49P5724hq4nnzGjpeaA4Gii8mcEAs7bAWlUCDjERh3jG-GRsXYXQigrHMe97iDO-ve81R4B3Z0-qYGx8Oy_AtGFRLPetGrR0gZHZfr9avsA7sIjZ01QWNe6L459r5UwPYFA8Tk6iKDWh6Z3BaNYOG1kLTEkdRpHfU-jA" width="300" /></a></div>I'm reusing the compass points to draw this, and there are a couple of drawbacks to this approach. For one thing, I can only draw black stars, and secondly, because of a bug in Chrome there are some faint white lines visible.<div><br /></div><div>This will wrap up compasses for now, although I'll have at least one more posting where I integrate this into the Dragons Abound code. If you want the final version of the compasses code, you can <span style="background-color: white;">download the Part 18 branch from the </span><a href="https://github.com/srt19170/Procedural-Map-Compasses/tree/Part-17" style="background-color: white;" target="_blank">Procedural Map Compasses repository</a><span style="background-color: white;"> on Github. </span><span style="background-color: white;"> You can also try out this code on the web at </span><a href="https://dragonsabound-part18.netlify.app/test.html" style="background-color: white;">https://dragonsabound-part18.netlify.app/test.html</a><span style="background-color: white;"> although you'll only be able to run the code, not modify it.</span></div><div><span style="background-color: white;"><br /></span></div><div><span style="background-color: white;">Next time I'll write a short post-mortem on the Compasses project, what worked and what didn't, and what I learned from it.</span></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-7729069235602005252022-04-29T17:53:00.000-04:002022-04-29T17:53:16.193-04:00Map Compasses (Part 17): Compass Points and More<p>Welcome back to my blog series on implementing procedurally-generated map compasses! For the past few posts I've worked on elaborating the “rings" that appear behind the compass points in compasses like these:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhiTWDmGxTA2k0fA6eCGvKvFQQpE_u1AULuAxFA1JKnAJfXjxbrEOAan-r6uqyWuEiWFedxWj-fiqyO9lq4YVJQq4N_KDyQob8nowH5ksBHgvHpk2KUr2rL8bRkQrB4X9noSLveOyHZOtN/s600/Image47.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="335" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhiTWDmGxTA2k0fA6eCGvKvFQQpE_u1AULuAxFA1JKnAJfXjxbrEOAan-r6uqyWuEiWFedxWj-fiqyO9lq4YVJQq4N_KDyQob8nowH5ksBHgvHpk2KUr2rL8bRkQrB4X9noSLveOyHZOtN/s16000/Image47.png" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div>Now I want to spend a little time on the compass points.<div><br /></div><div>There isn't nearly as much variation in compass points as in rings. One common variation is to turn the compass points into some sort of arrowhead or other type of artistic pointer, as in this example:<br /><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjayRu6jNK6VXrED-bDYxK1b8bvhhxukxCZHMJY3uiMzjqlyOgdGrW3tiCA1sITcGSCn7yMK0uRXxlQWHl2PtTgW9ucupN0Yuf8ZVp3fYkLgUjsgcL93wqYkIWzlzVPqenBieWX9oKktt6L/s284/Image3.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="284" data-original-width="250" height="284" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjayRu6jNK6VXrED-bDYxK1b8bvhhxukxCZHMJY3uiMzjqlyOgdGrW3tiCA1sITcGSCn7yMK0uRXxlQWHl2PtTgW9ucupN0Yuf8ZVp3fYkLgUjsgcL93wqYkIWzlzVPqenBieWX9oKktt6L/s0/Image3.png" width="250" /></a></div><br /><div>I'm not going to try to replicate something like this. It's certainly possible to draw arrowhead like compass points, but I'm not sure I can do that with much variety and more importantly, I don't particularly like that style. If you love it, please have a go and let me know what you come up with.</div><div><br /></div><div>Sticking to the traditional style of compass point, the main variations are straight or wavy, and the number of points used. I covered how to draw wavy compass points back in <a href="https://heredragonsabound.blogspot.com/2022/01/map-compasses-part-10-triangles.html" target="_blank">Part 10</a>, so I won't go over that again. As far as the number of points go, so far the procedural generation rules always generate eight points. So let's see about varying that.</div><div><br /></div><div>To start with, I can modify the rule to create compasses which only have points for the four cardinal directions:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span><span style="color: purple;">;</span>
</pre></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0NJbZ9orILVV0FJ6fSVcx_1pmqWA9vFebdFL808Ut7U4Q8XdcsVlo2QNlf-9-LBKJWTqAEIwBhpwP8emrQXDFH14b0X-tQYCoyhVJ54-U4HNKSjjxFf-DbQW5S4UmswmNg0fXDBstSzAK/s307/Image49.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="307" data-original-width="307" height="307" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0NJbZ9orILVV0FJ6fSVcx_1pmqWA9vFebdFL808Ut7U4Q8XdcsVlo2QNlf-9-LBKJWTqAEIwBhpwP8emrQXDFH14b0X-tQYCoyhVJ54-U4HNKSjjxFf-DbQW5S4UmswmNg0fXDBstSzAK/s0/Image49.png" width="307" /></a></div>That works fine, and looking at a dozen examples, they all look acceptable, if a little plain in most cases. But that's not necessarily a bad thing.<div><br /></div><div>In the other direction, I can add the interordinal points. These are eight points that fall between the intercardinal (also called ordinal) points.</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">|</span>
<span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">|</span>
<span style="color: #808030;"><</span>interOrdinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">[</span><span style="color: #008c00;">100</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>interOrdinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RECALL<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">insideEdge</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(7,12)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>interOrdinalPoint<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>interOrdinalPoint<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RPOINT<span style="color: #808030;">(</span><span style="color: green;">0.39269908</span><span style="color: #808030;">,</span> <span style="color: #008c00;">8</span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randRange(0.80,0.925)</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
RWAVE<span style="color: #808030;">(</span><span style="color: green;">0.39269908</span><span style="color: #808030;">,</span> <span style="color: #008c00;">8</span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randRange(0.80,0.925)</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre><div>The interordinal points are also measured from the inside edge of the first ring, but they're 7-12 pixels closer to the center so that they'll never be longer than the intercardinal points. The interordinal points start at PI/8 (i.e., 0.39269908), which places the first one between N and NE.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhd_BJn20n_r5TrbUoOlcvPgnnsNQqTSrV-q9vOUysIlr-Zotqpk6OSSbRLDn6tpfCaYOoUwzM8BlBAMXfcfJ_NUvfIE_QgIGXQixOmfiZXsHVdM6yYBqDlzx0O3nI-NWmvVoPkehKGUR0/s307/Image50.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="307" data-original-width="307" height="307" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhd_BJn20n_r5TrbUoOlcvPgnnsNQqTSrV-q9vOUysIlr-Zotqpk6OSSbRLDn6tpfCaYOoUwzM8BlBAMXfcfJ_NUvfIE_QgIGXQixOmfiZXsHVdM6yYBqDlzx0O3nI-NWmvVoPkehKGUR0/s0/Image50.png" width="307" /></a></div><br /><div>Because there are 8 of these points instead of 4, if I use the span value as for the other points, these points will be half as wide. That's fine; the finer divisions are usually drawn with finer points.</div><div><br /></div><div>One question is whether the interordinal points should be wavy when the intercardinal points are wavy. To my eye, it seems fine to have straight points mixed with wavy points:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioPx4Emy0J5RoySVBtDSa_xDATpS7VGSJwnM7HQf7BYXKDVVpc0wEPwDtyKyrs6x3t5rpl4z0qZozcXyLGcnob4ONt-uIXewzOyL3wMhV2URgcZ9qm-2cjtnYIF44P7E_6-QADdsbQ8Msf/s307/Image51.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="307" data-original-width="307" height="307" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioPx4Emy0J5RoySVBtDSa_xDATpS7VGSJwnM7HQf7BYXKDVVpc0wEPwDtyKyrs6x3t5rpl4z0qZozcXyLGcnob4ONt-uIXewzOyL3wMhV2URgcZ9qm-2cjtnYIF44P7E_6-QADdsbQ8Msf/s0/Image51.png" width="307" /></a></div>One problem I'm noticing as I generate examples are compasses like this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGsPJ2yNuPsN7ss5sN1lmHA7YkRF_T32s65xF78e_H6xTOGhx87S01q3D_ljXLlF8QtPCkVt0JpitP6yScZmIf0qkluPJPh7hO5bFwcuJcZXgztGtpKcOeIjg6n-NvU-pfUypQ8m5lDyuw/s307/Image52.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="307" data-original-width="307" height="307" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGsPJ2yNuPsN7ss5sN1lmHA7YkRF_T32s65xF78e_H6xTOGhx87S01q3D_ljXLlF8QtPCkVt0JpitP6yScZmIf0qkluPJPh7hO5bFwcuJcZXgztGtpKcOeIjg6n-NvU-pfUypQ8m5lDyuw/s0/Image52.png" width="307" /></a></div>where the points are overlapping the inside ring in confusing ways. It's worse when the inner ring is a thick black band:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrQWUWsX4IKfYAwbhOyKWo1lewNXJk2HYbf3rYcoNwq5W8VjmPaN_40tfwvwaUicFPHmsX-vNGe4cfdnvljzEjBn8Kmamtjb7b_uL5Tk2xyQc9e6gUqf84F2sfbhcDTM9Njz-qyv6VYWqA/s310/Image53.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="310" data-original-width="310" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrQWUWsX4IKfYAwbhOyKWo1lewNXJk2HYbf3rYcoNwq5W8VjmPaN_40tfwvwaUicFPHmsX-vNGe4cfdnvljzEjBn8Kmamtjb7b_uL5Tk2xyQc9e6gUqf84F2sfbhcDTM9Njz-qyv6VYWqA/s0/Image53.png" width="310" /></a></div>Looking through the example compasses and giving this some thought, I think the problem is mostly that a compass point shouldn't end inside a ring; it should either end before the ring or cross completely over the ring. If I temporarily change the rules so that all of the pointers are as long as the cardinal pointers, that seems to bear out my hypothesis:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrBh7ph7GkP5oUKNthkJ5IuMzNZo4ZCiV8DRMZBpJxf7cgGM2UEytCVMZmAtwMQyznaE_FKUmlBjjtl2JGlLed8zYtiW5dzr_Ri0c2GmZJuttB3kgkxFIt74UkBSR2Klekzc5WFP25wN_I/s310/Image54.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="310" data-original-width="310" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrBh7ph7GkP5oUKNthkJ5IuMzNZo4ZCiV8DRMZBpJxf7cgGM2UEytCVMZmAtwMQyznaE_FKUmlBjjtl2JGlLed8zYtiW5dzr_Ri0c2GmZJuttB3kgkxFIt74UkBSR2Klekzc5WFP25wN_I/s0/Image54.png" width="310" /></a></div>That's not the prettiest of compasses, but at least the compass points are distinct. However, making the intercardinal and interordinal points cross completely over the inner ring (but not the outer ring) doesn't work as well:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLlpVOrX7WsV__09XCODghexIo3OdFLWF1yBQDLAAotTWDpMqXzaDrnmn-ICDDeC1Op8k_fDoXmQHwOB6JEASkKF2ZU6HBf-NX_OmIt5I6Oc9NxgJF9Lp8mScVo1CIP1Vw30JV_61U_eu8/s309/Image55.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="309" data-original-width="309" height="309" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLlpVOrX7WsV__09XCODghexIo3OdFLWF1yBQDLAAotTWDpMqXzaDrnmn-ICDDeC1Op8k_fDoXmQHwOB6JEASkKF2ZU6HBf-NX_OmIt5I6Oc9NxgJF9Lp8mScVo1CIP1Vw30JV_61U_eu8/s0/Image55.png" width="309" /></a></div><br /><div>That's at least partly because there isn't enough visual separation between the two rings to make the points stand out. I can adjust the ring spacing to help with this.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwDTWacjsL6FYXXBAEtGSU2uzegWxqT3383wNkNbHlO9gcvybp4QqXXIQss1ZsTW8uoBVLAdYTLPtRUeJJWEr_bYEuVHkeenuW6TbEdPmFZULsWIolFDgneK57U5rLi5AwR37-6yNTaz0O/s309/Image56.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="309" data-original-width="309" height="309" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwDTWacjsL6FYXXBAEtGSU2uzegWxqT3383wNkNbHlO9gcvybp4QqXXIQss1ZsTW8uoBVLAdYTLPtRUeJJWEr_bYEuVHkeenuW6TbEdPmFZULsWIolFDgneK57U5rLi5AwR37-6yNTaz0O/s0/Image56.png" width="309" /></a></div>That works much better, I think. But problems still remain when the inner ring has radial elements:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0rFNH4jrny7UGOmuKT2CNF8dfhkJMagmtNnxTKZ2JvOT1e-Yi44sej6o3FnoDFReoBbdOvoXiqEiFzUvxBHYDn37L5K59yVRB45F9-uBBkISKgdNs4LSCL64kBNgdU-4Ru898hPUhCyRi/s309/Image57.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="309" data-original-width="309" height="309" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0rFNH4jrny7UGOmuKT2CNF8dfhkJMagmtNnxTKZ2JvOT1e-Yi44sej6o3FnoDFReoBbdOvoXiqEiFzUvxBHYDn37L5K59yVRB45F9-uBBkISKgdNs4LSCL64kBNgdU-4Ru898hPUhCyRi/s0/Image57.png" width="309" /></a></div>Since the problem arises from the overlapping of the points and the radial elements, one solution might be to shift the elements so that they are between the points:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeyrIfKqvJFbSZwY-9lUkuBqj7JKJVaGz1NDhywJY-rXTdtZc9WJT4bhbavKLj-cOpksyK0XFHPN01U0OXWHOZi1hzyvlv58FyEKnSQcCSPmt58SkoMkxYsw5bSTXOCYXLUCqPCihbsNLo/s322/Image58.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="322" data-original-width="322" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeyrIfKqvJFbSZwY-9lUkuBqj7JKJVaGz1NDhywJY-rXTdtZc9WJT4bhbavKLj-cOpksyK0XFHPN01U0OXWHOZi1hzyvlv58FyEKnSQcCSPmt58SkoMkxYsw5bSTXOCYXLUCqPCihbsNLo/s320/Image58.png" width="320" /></a></div>That seems like it will work, but there are some complications. This works for a number of radial elements that is a multiple of the number of gaps between the compass points. When there are two levels of compass points, the number of gaps is 8, so 8, 16, 24, 32, etc. radial elements will work. But when there are three levels of compass points, the number of gaps is 16, so this will work with 16, 32, 48, etc. But it won't work with 8 or 24 because the elements will fall in strange places:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjt86UAeai3UfW3Dwk8D-kDWWmxiA0wpIvtiSGrvogttAA6ZSQZXGrBBUILB7x7joYxKj-8Yh0NVF1ylOGffO7nkeShwyQdlitSBTGP1QCrR5PMxCSEoOViSTmLxgjQ6k3KkXcf1RKO67mi/s318/Image59.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="318" data-original-width="318" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjt86UAeai3UfW3Dwk8D-kDWWmxiA0wpIvtiSGrvogttAA6ZSQZXGrBBUILB7x7joYxKj-8Yh0NVF1ylOGffO7nkeShwyQdlitSBTGP1QCrR5PMxCSEoOViSTmLxgjQ6k3KkXcf1RKO67mi/s0/Image59.png" width="318" /></a></div>There's no offset that results in a symmetrical arrangement. So this will require replicating all the radial element rules once for each case. Worse, this approach doesn't make sense for an outer ring of radial elements. The whole point of this is to move the radial elements off of the compass points, but in the outer ring that's desirable:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhK-LeO8TSb5MqJJ3fF6mlOjb-G7ro2HcEKfxPV9kMY9V-J0INBIuS8sGb-ooeT9iOhUKUwWnRYTfPIRl2C2rRYIw7v44W4ejtz8kkixU-VSlFdbQyWHVVVCQc6clHTHhdIqjRNZqhtVCnd/s318/Image60.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="318" data-original-width="318" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhK-LeO8TSb5MqJJ3fF6mlOjb-G7ro2HcEKfxPV9kMY9V-J0INBIuS8sGb-ooeT9iOhUKUwWnRYTfPIRl2C2rRYIw7v44W4ejtz8kkixU-VSlFdbQyWHVVVCQc6clHTHhdIqjRNZqhtVCnd/s0/Image60.png" width="318" /></a></div>so there will need to be another set of rules for the outer ring. Rules tend to proliferate in these sorts of systems, but this seems a bit excessive.<div><br /></div><div>What I need is way to remember a decision that was made in one rule (e.g., to show intercardinal compass points) and then use that decision in a later rule (e.g., to select the right number and offset for radial elements). Right now Lodestone doesn't have that capability, so I'll have to add it.</div><div><br /></div><div>The way I'll do this is to combine the embedded Javascript capability and the one-time rules along with some modifications to the rules engine. In the end, this will let me write rules like this:</div><div><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>rule1<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>first<span style="color: #808030;">></span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><@tmp>=6</span><span style="color: maroon;">`</span> <span style="color: #808030;"><</span>second<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;">[</span><span style="color: #808030;">.</span><span style="color: #808030;">.</span><span style="color: #808030;">.</span><span style="color: #808030;">]</span>
<span style="color: #808030;"><</span>rule3<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">The value is: </span><span style="color: maroon;">"</span> <$tmp>;</pre></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">The @ syntax lets me assign a value to a one-time rule within the embedded Javascript, and then I can use the new value later just as I would any one-time rule.</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">To start, I'll modify the Loadstone grammar to accept these kind of references. That starts with adding a new class to the lexer:</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: dimgrey;">// </span>
<span style="color: dimgrey;">// One-time non-terminal reference in the form <@test></span>
<span style="color: dimgrey;">//</span>
otntermref<span style="color: #808030;">:</span><span style="color: #0000e6;"> </span><span style="color: maroon;">/</span><span style="color: #0000e6;"><</span><span style="color: #0f69ff;">\@</span><span style="color: #808030;">[</span><span style="color: #808030;">^</span><span style="color: #0000e6;">></span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: #0000e6;">></span><span style="color: maroon;">/</span><span style="color: #808030;">,</span>
</pre></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">This looks exactly like a one-time non-terminal, except it expects an @ instead of a $. The second part is to allow these references to show up on the right-hand side of rules, which I can accomplish by adding them to the list of legal elements:</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">#
# An element can be a non<span style="color: #808030;">-</span>terminal <span style="color: #808030;">(</span>e<span style="color: #808030;">.</span>g<span style="color: #808030;">.</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>test<span style="color: #808030;">></span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> a one<span style="color: #808030;">-</span>time
# non<span style="color: #808030;">-</span>terminal <span style="color: #808030;">(</span>e<span style="color: #808030;">.</span>g<span style="color: #808030;">.</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$test<span style="color: #808030;">></span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> a reference to a one<span style="color: #808030;">-</span>time
# non<span style="color: #808030;">-</span>terminal <span style="color: #808030;">(</span>e<span style="color: #808030;">.</span>g<span style="color: #808030;">.</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>@test<span style="color: #808030;">></span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> a string of characters <span style="color: #808030;">(</span>e<span style="color: #808030;">.</span>g<span style="color: #808030;">.</span><span style="color: #808030;">,</span> SPACE<span style="color: #808030;">)</span><span style="color: #808030;">,</span>
# a quoted string of characters <span style="color: #808030;">(</span>e<span style="color: #808030;">.</span>g<span style="color: #808030;">.</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">this here</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> embedded
# Javascript <span style="color: #808030;">(</span>e<span style="color: #808030;">.</span>g<span style="color: #808030;">.</span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> a weight <span style="color: #808030;">(</span>e<span style="color: #808030;">.</span>g<span style="color: #808030;">.</span><span style="color: #808030;">,</span> <span style="color: #808030;">[</span><span style="color: #008c00;">50</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> or
# the character for an alternative <span style="color: #808030;">(</span>e<span style="color: #808030;">.</span>g<span style="color: #808030;">.</span><span style="color: #808030;">,</span> <span style="color: #808030;">|</span><span style="color: #808030;">)</span>
#
element <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: #808030;">%</span>nterm <span style="color: #808030;">|</span> <span style="color: #808030;">%</span>otnterm <span style="color: #808030;">|</span> <span style="color: #808030;">%</span>otntermref <span style="color: #808030;">|</span> <span style="color: #808030;">%</span>string <span style="color: #808030;">|</span>
<span style="color: #808030;">%</span>qstring <span style="color: #808030;">|</span> <span style="color: #808030;">%</span>jscript <span style="color: #808030;">|</span> <span style="color: #808030;">%</span>weight <span style="color: #808030;">|</span> <span style="color: #808030;">%</span>or
</pre></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">In truth, I only want them to appear inside embedded Javascript, and this will allow them to appear anywhere on the right-hand side, but I'll live with that for the moment.</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">Now I need to modify the Lodestone rules engine so that it knows how to deal with these references. That's done by adding this code to executeRules:</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: dimgrey;">// If it's a reference to a one-time terminal, then replace the</span>
<span style="color: dimgrey;">// reference with the Javascript to reference where the value</span>
<span style="color: dimgrey;">// of the one-time terminal is kept.</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>current<span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">otntermref</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
output <span style="color: #808030;">=</span> output<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">otValues["</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>current<span style="color: #808030;">.</span>value<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">replace</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">@</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span><span style="color: maroon;">'</span><span style="color: #0000e6;">$</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">"]</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">continue</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">What this does is replace a reference like “<@test>" with “otValues[''<$test>'']". The latter is the Javascript code for where the values of one-time non-terminals are stored. So now if I write embedded Javascript like this: </span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon;">`</span><span style="color: #0000e6;"><@test> = 15</span><span style="color: maroon;">`</span>
</pre></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">it will be translated into </span></span></pre><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon;">`</span><span style="color: #0000e6;">otValues["<$test>"] = 15</span><span style="color: maroon;">`</span></pre></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">before it is executed as Javascript. So 15 will get stored into <$test> -- or at least it will if otValues evaluates to the proper object when the Javascript is executed.</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">To ensure this is a little tricky. I want embedded Javascript to execute as if it were executing in the context (e.g., closure) of the function from where executeRules is called. This allows the user to do something like this to define a value:</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;"><span> </span>let</span> defaultWidth <span style="color: #808030;">=</span> <span style="color: #008c00;">1</span><span style="color: purple;">;</span>
</pre></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">And then reference that in the embedded Javacript in a rule. But otValues isn't in this context, so as it stands this reference will fail. To address this, I have to add otValues to the context for the embedded Javascript. That context is defined by the function the user provides, e.g., </span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: maroon; font-weight: bold;">const</span> cdl <span style="color: #808030;">=</span> executeRules<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;"><compass></span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> s <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon; font-weight: bold;">eval</span><span style="color: #808030;">(</span>s<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">In this case, the last argument that defines a function that calls eval. To add otValues to this context, I'll add another parameter to the context function which (critically!) I will also call otValues:</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: maroon; font-weight: bold;">const</span> cdl <span style="color: #808030;">=</span> executeRules<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;"><compass></span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> <span style="color: #808030;">(</span>s<span style="color: #808030;">,</span> otValues<span style="color: #808030;">)</span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon; font-weight: bold;">eval</span><span style="color: #808030;">(</span>s<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">(In fact, I could have called this parameter anything, as long as I used the same name in the @ translation above. But it's convenient to use the same name consistently.) The last step is to provide the real otValues as the value of that parameter. This happens when this function gets called in executeRules:</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: dimgrey;">// Execute in the provided context.</span>
<span style="color: maroon; font-weight: bold;">const</span> execVal <span style="color: #808030;">=</span> <span style="color: #797997;">String</span><span style="color: #808030;">(</span>context<span style="color: #808030;">(</span>expanded, otValues<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">Now the real otValues gets matched up with the parameter otValues and is available to eval when a one-time non-terminal references is used! (Whew! I know that's a little difficult to follow, but it's a complex idea and I've done the best I can.)</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">One trickiness remains. When you use embedded Javascript, the return value gets substituted back into the rule where the embedded Javascript was found. In a case like this where we're using the Javascript for a side effect such as setting a value:</span></pre><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>rule1<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>first<span style="color: #808030;">></span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><@tmp>=6</span><span style="color: maroon;">`</span> <span style="color: #808030;"><</span>second<span style="color: #808030;">></span><span style="color: purple;">;</span></pre></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">we probably won't want the value of the Javascript (6 in this case) to be inserted into the rule. To avoid this, I need to modify the Javascript to return an empty string by adding a second statement that will evaluate to the empty string, i.e., </span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="background-color: white; color: #808030;"><</span><span style="background-color: white;">rule1</span><span style="background-color: white; color: #808030;">></span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white; color: #808030;">></span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;"><</span><span style="background-color: white;">first</span><span style="background-color: white; color: #808030;">></span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon;">`</span><span style="background-color: white; color: #0000e6;">@tmp=6; ""</span><span style="background-color: white; color: maroon;">`</span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;"><</span><span style="background-color: white;">second</span><span style="background-color: white; color: #808030;">></span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white;">
</span><span style="background-color: white; color: #808030;"><</span><span style="background-color: white;">rule3</span><span style="background-color: white; color: #808030;">></span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white; color: #808030;">></span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">The value is: </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;"> $tmp;</span><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">(You can also use the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator" target="_blank">Javascript comma operator</a> here.) This is a little clumsy, but certainly workable.</span></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Now let's put this to work for our radial elements. Recall from above that different numbers of radial elements work for the inside rings depending upon whether or not the compass has two levels of pointers or three levels. The number of radial elements is controlled by $numRadialElements, so I can set then when the choice for levels of compass points is made:</span></span></pre><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span>
<span style="color: maroon;">`</span><span style="color: #0000e6;">@numRadialElements=Utils.randElement([4,8,12,16,20,24,28,32]),""</span><span style="color: maroon;">`</span> <span style="color: #808030;">|</span>
<span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span>
<span style="color: maroon;">`</span><span style="color: #0000e6;">@numRadialElements=Utils.randElement([8,16,24,32]),""</span><span style="color: maroon;">`</span> <span style="color: #808030;">|</span>
<span style="color: #808030;"><</span>interOrdinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span>
<span style="color: maroon;">`</span><span style="color: #0000e6;">@numRadialElements=Utils.randElement([16,32]),""</span><span style="color: maroon;">`</span><span style="color: purple;">;</span></pre></pre></pre></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Remember that inside the backticks is Javascript, so I'm using a utility function that picks a random element of an array to make the choice of how many radial elements to use.</span></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Sadly, this isn't quite right. The problem is that the bottom layer gets created first:</span></span>
<pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>labels<span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">start</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span><span style="color: purple;">;</span></pre></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">which means the radial elements have already been created before the <topLayer> rule decides how many points there will be. I need to modify the rules to choose the level of compass points before the bottom layer is drawn. That's easy enough to do with a one-time rule:</span></span></pre><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">$pointLevels <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">1</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">2</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">3</span><span style="color: purple;">;</span></pre></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">But how will I use that to control the <topLayer> rule that actually draws the compass points? The trick is to use the choice weightings.</span></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Recall that the choice weightings are numbers in square brackets that control how often a choice is selected in a rule. For example this rule:</span></span></pre><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>ringOrScale<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">></span> <span style="color: #808030;">[</span><span style="color: #008c00;">5</span><span style="color: #808030;">]</span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>scaleRing<span style="color: #808030;">></span><span style="color: purple;">;</span></pre></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">uses choice weightings so that rings are selected 5x as frequently as scales. Two features of choice weighting are useful to us here. First, choice weightings can use one-time rules or embedded Javascript to determine the weighting. Second, a weighting of zero means a choice will never be chosen. So to force a particular number of points to be drawn, I need to set the weights of the other choices to zero. For example, this rule:</span></span></pre><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">|</span>
<span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">|</span>
<span style="color: #808030;"><</span>interOrdinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span>
</pre></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">will always draw two levels of points, since the other two options are set to zero. Now I need to modify this rule so that a weight of zero or one is selected for each option based upon $pointLevels:</span></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span>
<span style="color: #808030;">[</span><span style="color: maroon;">`</span><span style="color: #0000e6;">$pointLevels == 1 ? 1 : 0</span><span style="color: maroon;">`</span><span style="color: #808030;">]</span> <span style="color: #808030;">|</span>
<span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span>
<span style="color: #808030;">[</span><span style="color: maroon;">`</span><span style="color: #0000e6;">$pointLevels == 2 ? 1 : 0</span><span style="color: maroon;">`</span><span style="color: #808030;">]</span> <span style="color: #808030;">|</span>
<span style="color: #808030;"><</span>interOrdinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span>
<span style="color: #808030;">[</span><span style="color: maroon;">`</span><span style="color: #0000e6;">$pointLevels == 3 ? 1 : 0</span><span style="color: maroon;">`</span><span style="color: #808030;">]</span><span style="color: purple;">;</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">The embedded Javascript uses the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator" target="_blank">ternary operator</a> to check if $pointLevels is set to the corresponding level, and returns 1 if so and otherwise 0. So the weight for the proper choice ends up being 1 and the other choices zero.</span></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">I also need to set up the proper number of radial elements based upon the points level. I'll do that at the same time I pick the point level:</span></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background: rgb(255, 255, 255);">$pointLevels <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">1</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">@numInsideRadialElements=Utils.randElement([4,8,12,16,20,24,28,32]),""</span><span style="color: maroon;">`</span> <span style="color: #808030;">|</span>
<span style="color: #008c00;">2</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">@numInsideRadialElements=Utils.randElement([8,16,24,32]),""</span><span style="color: maroon;">`</span> <span style="color: #808030;">|</span>
<span style="color: #008c00;">3</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">@numInsideRadialElements=Utils.randElement([16,32]),""</span><span style="color: maroon;">`</span> <span style="color: purple;">;</span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">I also need to set some default values for the number of radial elements and the offset of the elements. These are the values that will be used in any outer ring of radial elements, where we don't have to worry about clashing with the compass points:</span></span></pre><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;">$numRadialElements <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">8</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">12</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">16</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">20</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">24</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">28</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">32</span><span style="color: purple;">;</span>
$offsetRadialElements <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">0</span><span style="color: purple;">;</span>
</pre></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">The last part of the puzzle is to replace these default values before I create any inner ring of radial elements. </span></span></pre><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(10,30)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>ringOrScale<span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">insideEdge</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(5,10)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>ringOrScale<span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">insideEdge</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
<span style="color: maroon;">`</span><span style="color: #0000e6;">@numRadialElements = $numInsideRadialElements, ""</span><span style="color: maroon;">`</span>
<span style="color: maroon;">`</span><span style="color: #0000e6;">@offsetRadialElements = Math.PI/$numInsideRadialElements, ""</span><span style="color: maroon;">`</span>
SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">10</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">></span><span style="color: purple;">;</span>
</pre></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">This is the rule that decides whether the bottom layer will have only an outer ring (the first choice) or an outer and an inner ring (the second choice). In the second choice, the embedded Javascript sets the number of radial elements to the value that was selected back in the $pointLevels rule, and then uses that to calculate an offset.</span></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="background-color: white; font-family: Times New Roman;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRwHuEa3mrbgAnHru4EUnZu3xJcu2PBi8YdqxCvqi5zourR3I1GE-SgPMsfCJObQgYeG-kgSRPkKCCQsusv_aVm6DVMr9Kqv8Abp2RrKpX2d1I0vMwlNe47ZGC8Mc0ns0RUaqWQSPO-HaX/s318/Image61.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="318" data-original-width="318" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRwHuEa3mrbgAnHru4EUnZu3xJcu2PBi8YdqxCvqi5zourR3I1GE-SgPMsfCJObQgYeG-kgSRPkKCCQsusv_aVm6DVMr9Kqv8Abp2RrKpX2d1I0vMwlNe47ZGC8Mc0ns0RUaqWQSPO-HaX/s0/Image61.png" width="318" /></a></div></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Here's an example where there are 20 elements in the outside ring with no offset and eight elements in the inside ring with an offset that centers the first element between the first two compass points.</span></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><span style="white-space: normal;">I'm still not entirely happy with how compasses like the above example look where a point crosses a dark background element. One way to address this might be to add a white border around the point to create a visual break with the background. Here's an example of what this would look like:</span></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: Times New Roman;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguankDjRkxr87p7AdXGlXTyWnpaVE6W5Zkyu23yXkjvX1A_3OWMolZxFrlnyWqucX4pLUBnbk8Ydj1z3hv5h_QfXh6ACjMU2E47-kTVZpfrd9_VKDkVkSa3_tuMqxSxTc-ZRvhWn5kLxKu/s326/Image4.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="326" data-original-width="326" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguankDjRkxr87p7AdXGlXTyWnpaVE6W5Zkyu23yXkjvX1A_3OWMolZxFrlnyWqucX4pLUBnbk8Ydj1z3hv5h_QfXh6ACjMU2E47-kTVZpfrd9_VKDkVkSa3_tuMqxSxTc-ZRvhWn5kLxKu/s16000/Image4.png" /></a></div><div class="separator" style="clear: both; text-align: left;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">If you examine where the points cross background objects you'll see there's now a thin white line separating the point. This is done by drawing the outline of the point in white, using a thicker line, before drawing the point on top. The result is a white line around the point. However, if you draw this all the way around the point you get some unattractive artifacts where the points meet, as is evident in this example:</span></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLWposmnaGDeXO8EAhZhATJT5KoxIZ9BuXWLuiyc6WPpsRGDBSWTPJGxRPxkXNF-2hSrsFehZeM2KLyO6Kb1hCFAlI5KnwsKlcwEADhtR2pyyKB1fZoLHSroQFa4CSu2BYIBZA6kjkrSS4/s314/Image5.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="313" data-original-width="314" height="313" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLWposmnaGDeXO8EAhZhATJT5KoxIZ9BuXWLuiyc6WPpsRGDBSWTPJGxRPxkXNF-2hSrsFehZeM2KLyO6Kb1hCFAlI5KnwsKlcwEADhtR2pyyKB1fZoLHSroQFa4CSu2BYIBZA6kjkrSS4/s0/Image5.png" width="314" /></a></div>If you only draw to the widest parts of the point there is still a problem:</span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQsdniwWkU93Yhj0zPwL_cLlqe5HHc5TfUPywla6r0jTrFGeB_lxrlmk9Ql4ciYUcfwOfc-VQnZvYiMOp_OFb8Ka7QExm3DyDC4SozX5uR1XNdC8Teld0VzMERi56M9lVcGjFnXAunwXKq/s314/Image6.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="313" data-original-width="314" height="313" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQsdniwWkU93Yhj0zPwL_cLlqe5HHc5TfUPywla6r0jTrFGeB_lxrlmk9Ql4ciYUcfwOfc-VQnZvYiMOp_OFb8Ka7QExm3DyDC4SozX5uR1XNdC8Teld0VzMERi56M9lVcGjFnXAunwXKq/s0/Image6.png" width="314" /></a></div><span style="font-family: Times New Roman;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">The white line for the top points separates them in an awkward way from the lower points. The solution is to pull the midpoints in slightly for the white line, so that it tapers down to nothing at the midpoints. This is easier to see if I draw the lines in red:</span></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPuPabLr8CA5vVl1PLwL7lRCjilRtoTWjqXrCdEYPXa7mklcCqpldd8E68_9sk2X7YEmIwY6XtWPABB9_FTqZZzNHVCkf4c_iQuoE-HY3DGRNQBYRQD8SrqPztjLcc0K0mu03K9kF2Xce0/s304/Image7.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="303" data-original-width="304" height="303" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPuPabLr8CA5vVl1PLwL7lRCjilRtoTWjqXrCdEYPXa7mklcCqpldd8E68_9sk2X7YEmIwY6XtWPABB9_FTqZZzNHVCkf4c_iQuoE-HY3DGRNQBYRQD8SrqPztjLcc0K0mu03K9kF2Xce0/s0/Image7.png" width="304" /></a></div></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">The separation is fat out at the tip of the point and diminishes to nothing at the midpoint. A similar technique can be applied to wavy points as well. </span></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglvRXID-PNLIPwgD35a5mO3ADLgITqTxyMjGAVWUFUO6TEGMKrrQUUp_-HXzWQ8mbVJOnnlvLg1c0n1DkImM4JERvITKvNnKgmypRkupkr7ANBLVXbMR5eW99q4kx2Gn89XPrMDCBinI6j/s304/Image8.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="303" data-original-width="304" height="303" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglvRXID-PNLIPwgD35a5mO3ADLgITqTxyMjGAVWUFUO6TEGMKrrQUUp_-HXzWQ8mbVJOnnlvLg1c0n1DkImM4JERvITKvNnKgmypRkupkr7ANBLVXbMR5eW99q4kx2Gn89XPrMDCBinI6j/s0/Image8.png" width="304" /></a></div></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">I don't think this is a “traditional" compass-drawing technique, and it will be problematic once more colors are possible, so I'll leave it in for now as an option. (But see below for controlling the option from CDL.)</span></span></pre><span style="white-space: normal;">A reminder that if you want to follow along from home, you need to download the Part 17 branch from the <a href="https://github.com/srt19170/Procedural-Map-Compasses/tree/Part-17" target="_blank">Procedural Map Compasses repository</a> on Github and get the test web page open. <span><a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">Part 1 of this series</a> 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 17 directory, and then select the test.html link.</span> You can also try out this code on the web at <a href="https://dragonsabound-part17.netlify.app/test.html">https://dragonsabound-part17.netlify.app/test.html</a> although you'll only be able to run the code, not modify it.<br /><div><br /></div><div><span style="font-size: 18.72px; font-weight: 700;">Suggestions to Explore</span></div></span></span></pre></div></span></pre><pre style="background: rgb(255, 255, 255);"><ul style="text-align: left;"><li><span style="font-family: Times New Roman;"><span style="white-space: normal;">I noted above that one-time non-terminal references should only be allowed within embedded Javascript. Can you modify the Lodestone grammar to enforce that?<br /><br /></span></span></li><li><span style="font-family: Times New Roman;"><span style="white-space: normal;">Add a command to CDL to control the white border option, and more generally for any other options that might arise. OPTION(name, ON/OFF/value) might be a suitable format.</span></span></li></ul><div><br /></div></pre></pre></pre></pre></pre></pre></pre></pre></pre></pre></pre></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com1tag:blogger.com,1999:blog-3367557180796013182.post-75435602952831287862022-04-11T19:34:00.000-04:002022-04-11T19:34:02.567-04:00Map Compasses (Part 16): More Rings<p>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:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_Ygvc_iT9jqxFByQ9HG8W1tnyDAuTVJjnICdlzKoAR9IjVoJoVpJp8OS12jKCGdXLlZYfaOh_Vzhzk7wR4Faz_-wJlC07MehZlx4yLxEHNDNLAHYxmrA0c9qW-HHTboFaKDNXK2h2pFC7/s600/Image7.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="198" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_Ygvc_iT9jqxFByQ9HG8W1tnyDAuTVJjnICdlzKoAR9IjVoJoVpJp8OS12jKCGdXLlZYfaOh_Vzhzk7wR4Faz_-wJlC07MehZlx4yLxEHNDNLAHYxmrA0c9qW-HHTboFaKDNXK2h2pFC7/s16000/Image7.png" /></a></div><p>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:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4wlZPQIQRa3-S3fL6Q6dW_R4DE48aAGTA_2o2D8nEa4AUIow4u2Unek_MpLMbCeE-fHm_UjLIc80dKn27DETpdFdm-XlZEDZ6P45wC4delA7kZ5_-y7zme_vEb-p8OheP5QQoF1dT2FBk/s689/Image34.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="689" data-original-width="670" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4wlZPQIQRa3-S3fL6Q6dW_R4DE48aAGTA_2o2D8nEa4AUIow4u2Unek_MpLMbCeE-fHm_UjLIc80dKn27DETpdFdm-XlZEDZ6P45wC4delA7kZ5_-y7zme_vEb-p8OheP5QQoF1dT2FBk/w311-h320/Image34.png" width="311" /></a></div>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:<br /><pre style="background: rgb(255, 255, 255);"># Radial element rings
<span style="color: #808030;"><</span>radialRing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>radialElementsOnly<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>radialElementsOnly<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">32</span><span style="color: #808030;">,</span> <span style="color: #008c00;">2</span><span style="color: #808030;">,</span> <span style="color: green;">0.5</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre><div><p>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:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm_mlbZg4AxFSa-iDpj2FPos0HABodcTMIaetJy5kBZjzABFcdh3iJGpqSv-pkRb3WuqHT5ra5rfYjbjwS7bTcg8yMyAlS0ZfT9eFYrHMOqcN6rt2ehvrBran4iTAR9xMx5H7mY0Mm82tn/s212/Image22.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="212" data-original-width="212" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm_mlbZg4AxFSa-iDpj2FPos0HABodcTMIaetJy5kBZjzABFcdh3iJGpqSv-pkRb3WuqHT5ra5rfYjbjwS7bTcg8yMyAlS0ZfT9eFYrHMOqcN6rt2ehvrBran4iTAR9xMx5H7mY0Mm82tn/s0/Image22.png" width="212" /></a></div><br /><p>This looks fine, but I don't like the look when intercardinal points go over the ring:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtgdzDUT7ek8ssCnTleHBpIvFgo8RIKFCw72uWQLeaymkqLVp8tKpmb3fPlyLx1cTdhRLyHUf-LQfiY87Hiope5euLcnQF7WhEHrpOGfQtnQVYkG6SHmAGQigwL_pDp_-v63bnfEbzjFZX/s206/Image23.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtgdzDUT7ek8ssCnTleHBpIvFgo8RIKFCw72uWQLeaymkqLVp8tKpmb3fPlyLx1cTdhRLyHUf-LQfiY87Hiope5euLcnQF7WhEHrpOGfQtnQVYkG6SHmAGQigwL_pDp_-v63bnfEbzjFZX/s0/Image23.png" width="206" /></a></div>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:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3DNTrY4qa1fKxorZqO_ZNFXylUOgsxACBgBk5kxH0fFeXLr9W72n2BNHBCDSYG7XFdh9uaUZyj5uvWxPwX0_5tMKN_DqBO7PhxKDFEa8TBNgLSTn3JHpMx73gSkT6p-IUkV-LhVaxObNC/s206/Image24.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3DNTrY4qa1fKxorZqO_ZNFXylUOgsxACBgBk5kxH0fFeXLr9W72n2BNHBCDSYG7XFdh9uaUZyj5uvWxPwX0_5tMKN_DqBO7PhxKDFEa8TBNgLSTn3JHpMx73gSkT6p-IUkV-LhVaxObNC/s0/Image24.png" width="206" /></a></div>This happens because radial elements don't advance the cursor. I need to do that manually. <div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>radialElementsOnly<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">32</span><span style="color: #808030;">,</span> <span style="color: #008c00;">2</span><span style="color: #808030;">,</span> <span style="color: green;">0.5</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></pre><pre style="background: rgb(255, 255, 255);"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgio5te5XBYOf39PeGX6IqSvjafYc1dsb0cab8EKgF8VqUIufzpkD8v3yxh7Cmjw6wCYiznqeA6-RRd2pb09BYElDCejO26HE5YI7Wm70nvqzMw0QI9kNrPC9TEnLOljg4CTDdWwDQAAkoL/s206/Image25.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgio5te5XBYOf39PeGX6IqSvjafYc1dsb0cab8EKgF8VqUIufzpkD8v3yxh7Cmjw6wCYiznqeA6-RRd2pb09BYElDCejO26HE5YI7Wm70nvqzMw0QI9kNrPC9TEnLOljg4CTDdWwDQAAkoL/s0/Image25.png" width="206" /></a></div></pre><div>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:</div></div><div><pre style="background: rgb(255, 255, 255);"># Radial element rings
<span style="color: #808030;"><</span>radialRing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>radialElementsOnly<span style="color: #808030;">></span> <span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$relementSize<span style="color: #999999;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randRange(2,5)</span><span style="color: maroon;">`</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: green;">0.5</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">1</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">16</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">24</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">32</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">40</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>radialElementsOnly<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></div><div>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:</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>radialElementsOnly<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RTRI<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RTRI<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RDIAMOND<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RDIAMOND<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RTRI<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RDIAMOND<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></pre></div><div>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. </div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGwyN3P6KluFvolTdVvg31b9bUAz2exUMxE0NxrxwVCBOc4bBrOWySv-Utt9U303gdeSYbDuU53juG88ZgN3XVUavHy_dfjYsk_7Vk-f3PRhXu9DMCBnfxEe62yq8DYWoJhZwM-rbvHxvl/s206/Image26.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGwyN3P6KluFvolTdVvg31b9bUAz2exUMxE0NxrxwVCBOc4bBrOWySv-Utt9U303gdeSYbDuU53juG88ZgN3XVUavHy_dfjYsk_7Vk-f3PRhXu9DMCBnfxEe62yq8DYWoJhZwM-rbvHxvl/s0/Image26.png" width="206" /></a></div><div>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.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjstPXe8uF8YrpEH2WwsxbwVEeAM12rP3d1wr6iJNqWY_WPwN_z5j6v_zZQVBGbhXl2JXPFRyFG8SwFvRDaEXQw6C82Oks81-DPKQ66lts_Bqm3GDl2oDUrKmd3RITfqbpDXAlfMpQGd9SW/s317/Image37.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="317" data-original-width="317" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjstPXe8uF8YrpEH2WwsxbwVEeAM12rP3d1wr6iJNqWY_WPwN_z5j6v_zZQVBGbhXl2JXPFRyFG8SwFvRDaEXQw6C82Oks81-DPKQ66lts_Bqm3GDl2oDUrKmd3RITfqbpDXAlfMpQGd9SW/w200-h200/Image37.png" width="200" /></a></div><br /><div>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:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>radialElementsFramed<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>radialElementsOnly<span style="color: #808030;">></span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
</pre><div>That's just drawing the radial elements centered between two circles.</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCPRH167jiI6o525lNCj4IYsMGvvf-7x3IMjakM0hIJyoxBSNZhkp4DYN7cAk-GIJxC_nyS1cwevxMnzMCCyfxIrn10po190VtMXIJq72H9_yNq33dKih18DpA6v7tytWy4oUFGrvJO9Yo/s315/Image28.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="315" data-original-width="315" height="315" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCPRH167jiI6o525lNCj4IYsMGvvf-7x3IMjakM0hIJyoxBSNZhkp4DYN7cAk-GIJxC_nyS1cwevxMnzMCCyfxIrn10po190VtMXIJq72H9_yNq33dKih18DpA6v7tytWy4oUFGrvJO9Yo/s0/Image28.png" width="315" /></a></div><div>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.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFhsySzFN2Qjobz_dtHDEoGkvMMIfBSNQcJbSchxnK49Dh45PyhyphenhyphenJTlvyjGer7XHnBsXJtiF1bOlUrrxJT1WFtZfIoWVldKFCjp9bfgSSdDIKFV9lI_8BuNYKlIjSIg0XMD9fwLL4_zSN8/s326/Image33.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="326" data-original-width="326" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFhsySzFN2Qjobz_dtHDEoGkvMMIfBSNQcJbSchxnK49Dh45PyhyphenhyphenJTlvyjGer7XHnBsXJtiF1bOlUrrxJT1WFtZfIoWVldKFCjp9bfgSSdDIKFV9lI_8BuNYKlIjSIg0XMD9fwLL4_zSN8/s320/Image33.png" width="320" /></a></div>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:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1WZPw89J6qtga_T6jEPlvgCSJtYwwSKzm61r0uQIm3fRP5H7LLwBKlLh_2_dZeFbJ8rWe8yVwARaMT1rgGHiNHUYFX11nMxF3b8FaUigfL3hsMV2ltovX7ZMAIlmel31EhGLsUvkNI5tO/s318/Image34.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="318" data-original-width="318" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1WZPw89J6qtga_T6jEPlvgCSJtYwwSKzm61r0uQIm3fRP5H7LLwBKlLh_2_dZeFbJ8rWe8yVwARaMT1rgGHiNHUYFX11nMxF3b8FaUigfL3hsMV2ltovX7ZMAIlmel31EhGLsUvkNI5tO/s0/Image34.png" width="318" /></a></div>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.<br /><div><div><br /></div>And I might as well include doubled circles as I did for scales:<div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>$frameCircle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> <span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$thinWidth<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>radialElementsFramed<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>$frameCircle<span style="color: #808030;">></span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>radialElementsPlus<span style="color: #808030;">></span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>$frameCircle<span style="color: #808030;">></span><span style="color: purple;">;</span></pre></pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy1oKGFPEh2rBVZIl0tR-5essyLKZ-CEvVRZ4NEUc7HrdklB35DVeCFGZqK2GHFtXXaNVkBmOV5UqrZKlqs6kUi9ZjXCUTnPDGe4kOPyNz8Q4Aame4JsAIfb0YQxY_cHr8OYxQJauo49TE/s312/Image29.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="312" data-original-width="312" height="312" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy1oKGFPEh2rBVZIl0tR-5essyLKZ-CEvVRZ4NEUc7HrdklB35DVeCFGZqK2GHFtXXaNVkBmOV5UqrZKlqs6kUi9ZjXCUTnPDGe4kOPyNz8Q4Aame4JsAIfb0YQxY_cHr8OYxQJauo49TE/s0/Image29.png" width="312" /></a></div><div>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.</div><div><br /></div><div>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.</div><div><br /></div>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.<br /><div><br /></div><div>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.</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>radialElementsDark<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> CIRCLE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>*2</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">-3*<$relementSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>radialElementsReversed<span style="color: #808030;">></span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
<span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$thinWidth<span style="color: #808030;">></span><span style="color: #808030;">)</span>
CIRCLE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>*2</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$thinWidth<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">-3*<$relementSize>/2-<$thinWidth>*2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>radialElementsReversed<span style="color: #808030;">></span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2 + <$thinWidth>*2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>radialElementsReversed<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RTRI<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RTRI<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RTRI<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RDIAMOND<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RDIAMOND<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RDIAMOND<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$relementSize<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAlfG8QUMenQsZgakUFGC0EFhQMjhBIZTLosL-J4_ALmPgUPWrmotGW-UsZtlwhiNuEfcm1cHrWJNNLydcNWINVzvJmTA2voCebfw03ZfceBtuFeytuAZC4zshHmXq2jtZ5vAZuMWiyhz8/s302/Image29.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="302" data-original-width="302" height="302" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAlfG8QUMenQsZgakUFGC0EFhQMjhBIZTLosL-J4_ALmPgUPWrmotGW-UsZtlwhiNuEfcm1cHrWJNNLydcNWINVzvJmTA2voCebfw03ZfceBtuFeytuAZC4zshHmXq2jtZ5vAZuMWiyhz8/s0/Image29.png" width="302" /></a></div>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:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjl176XADZFBQnGvzhZRWUMOUTsLLwjMg-eA8YNMuB-7RtJuz_1meQAzEvB1z4XjemUjyG3VJH-GpLGb0LgAqUNGB4tebGqYil82BqCwfdm0rTqgmckwmtpFjfNswrxTtalVQzm6npfcbgN/s309/Image30.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="309" data-original-width="309" height="309" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjl176XADZFBQnGvzhZRWUMOUTsLLwjMg-eA8YNMuB-7RtJuz_1meQAzEvB1z4XjemUjyG3VJH-GpLGb0LgAqUNGB4tebGqYil82BqCwfdm0rTqgmckwmtpFjfNswrxTtalVQzm6npfcbgN/s0/Image30.png" width="309" /></a></div>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.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoQHzhb7QdDHjKdwGl9iC9LoV4lGzZGSEdNoEgMQmJ2HE22ywb95rED911tbIskN5eQJQTku8eAxEtHynYh-7mMO4_LfCU_rwnHt-EZld_sFoSFeA1TshOUrF7vAjxPvRZI0P2rzno43x9/s313/Image32.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="313" data-original-width="313" height="313" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoQHzhb7QdDHjKdwGl9iC9LoV4lGzZGSEdNoEgMQmJ2HE22ywb95rED911tbIskN5eQJQTku8eAxEtHynYh-7mMO4_LfCU_rwnHt-EZld_sFoSFeA1TshOUrF7vAjxPvRZI0P2rzno43x9/s0/Image32.png" width="313" /></a></div>You can see the rule for this in compass.rules. <br /><div class="separator" style="clear: both; text-align: center;"><br /></div>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:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0EaAXvTg4s0ZCDNB_pFG4fB7dFSEhbY3nleYCejV05rFnE_wHJEp87-SrlfkaA5TH2Z5TrIbHvHCM8-yWe18PualKy15xQyHC0-O7DwEJAKd77gVt2aovbKUZDb15OMvZYmYY9kngSppr/s317/Image36.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="317" data-original-width="317" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0EaAXvTg4s0ZCDNB_pFG4fB7dFSEhbY3nleYCejV05rFnE_wHJEp87-SrlfkaA5TH2Z5TrIbHvHCM8-yWe18PualKy15xQyHC0-O7DwEJAKd77gVt2aovbKUZDb15OMvZYmYY9kngSppr/s0/Image36.png" width="317" /></a></div>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.</div><div><br /></div><div>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:<br /><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>alternating<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>diamond<span style="color: #808030;">></span> <span style="color: #808030;"><</span>firstDiamond<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>circle<span style="color: #808030;">></span> <span style="color: #808030;"><</span>firstCircle<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>triangle<span style="color: #808030;">></span> <span style="color: #808030;"><</span>firstTriangle<span style="color: #808030;">></span>
<span style="color: #808030;"><</span>firstDiamond<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>circle<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>triangle<span style="color: #808030;">></span>
<span style="color: #808030;"><</span>firstCircle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>diamond<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>triangle<span style="color: #808030;">></span>
<span style="color: #808030;"><</span>firstTriangle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>circle<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>diamond<span style="color: #808030;">></span></pre></pre><div>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.</div><div><br /></div><div>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:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDvWqoh7TC4xK13H_yJ7X1iJQ2alOOuOUcwJSE-JdQ7Oa61adPl3A8dCkQw_sQpoNgowWHzXuWuOdLhoGdS_SgIPnSvNnZNtb_r5OFoKwHeYuwLIzIh_7nqeqbSmGBTG0YFm9bWIP8vJMp/s317/Image38.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="317" data-original-width="317" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDvWqoh7TC4xK13H_yJ7X1iJQ2alOOuOUcwJSE-JdQ7Oa61adPl3A8dCkQw_sQpoNgowWHzXuWuOdLhoGdS_SgIPnSvNnZNtb_r5OFoKwHeYuwLIzIh_7nqeqbSmGBTG0YFm9bWIP8vJMp/s0/Image38.png" width="317" /></a></div>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:</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>radialElementsAlternating<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>radialElementsFirst<span style="color: #808030;">></span> <span style="color: #808030;"><</span>radialElementsSecond<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$numAlternatingElements<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">8</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">12</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">16</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">20</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$relementFirstSize<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randRange(4,5.5)</span><span style="color: maroon;">`</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$relementSecondSize<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randRange(5.5,7)</span><span style="color: maroon;">`</span><span style="color: purple;">;</span>
# Needs to return to the start radius
# Firsts are smaller than seconds<span style="color: #808030;">,</span> so they need to center <span style="color: maroon; font-weight: bold;">in</span> <span style="color: #808030;"><</span>$relementSecondSize<span style="color: #808030;">></span>
<span style="color: #808030;"><</span>radialElementsFirst<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSecondSize>/2+<$relementFirstSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$numAlternatingElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementFirstSize></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: #808030;">-</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSecondSize>/2+<$relementFirstSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
<span style="color: #808030;">.</span><span style="color: #808030;">.</span><span style="color: #808030;">.</span> <span style="color: purple;">;</span>
<span style="color: #808030;"><</span>radialElementsSecond<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSecondSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RCIRCLE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/<$numAlternatingElements></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$numAlternatingElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSecondSize></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;">|</span>
<span style="color: #808030;">.</span><span style="color: #808030;">.</span><span style="color: #808030;">.</span> <span style="color: purple;">;</span></pre></pre><div>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.</div><div><br /></div><div>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.</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>radialElementsNecklace<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2+0.5</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
CIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">-(<$relementSize>/2+0.5)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>radialElementsPlus<span style="color: #808030;">></span>
</pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGrkOrwO67I2PcI45hfXRkEDsSWywlWe8fnXVPa5QbhER8GbCrnSzMcoDUqtR5ERXFjWbNUZpJYKEYOC9kZ_txqQWajvYjdQoMpkI8JebYG6dE_5-AmXkAYse5xOFADPIFxtGXlgwc-GlY/s317/Image43.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="317" data-original-width="317" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGrkOrwO67I2PcI45hfXRkEDsSWywlWe8fnXVPa5QbhER8GbCrnSzMcoDUqtR5ERXFjWbNUZpJYKEYOC9kZ_txqQWajvYjdQoMpkI8JebYG6dE_5-AmXkAYse5xOFADPIFxtGXlgwc-GlY/s0/Image43.png" width="317" /></a></div>And of course I might as well do this with alternating elements and the other variants as well, which gives me this lovely example:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZzSaOhnoIwj2tqO3JGVa4Qs89CT1HeAUmQJI-K6K7D1Z0NYDJnIco-4CPJITCbW0dKJBtEYiOhqKt77hEye5pQ-qFatsMtVMoAONbeCvGFeZx36AkbGRKhoM-c0tPu07p0dNXckf-hjeh/s315/Image44.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="315" data-original-width="315" height="315" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZzSaOhnoIwj2tqO3JGVa4Qs89CT1HeAUmQJI-K6K7D1Z0NYDJnIco-4CPJITCbW0dKJBtEYiOhqKt77hEye5pQ-qFatsMtVMoAONbeCvGFeZx36AkbGRKhoM-c0tPu07p0dNXckf-hjeh/s0/Image44.png" width="315" /></a></div>With shaded diamonds differing just a bit in size. And of course I will add my signature double line style:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdPA2SL66uxpyVeA-fxYnV8v4c-0kXwg1i8zrB4uakq583kRhf5tBnehDH07Xm1_BmCPK8wj9RmP1ocs5kilJ8yekPLhKwAguf5WaIqEuvYr5mepe-cZFU_eJ9ivcP1z8IU5UXMXPJUaoe/s315/Image46.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="315" data-original-width="315" height="315" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdPA2SL66uxpyVeA-fxYnV8v4c-0kXwg1i8zrB4uakq583kRhf5tBnehDH07Xm1_BmCPK8wj9RmP1ocs5kilJ8yekPLhKwAguf5WaIqEuvYr5mepe-cZFU_eJ9ivcP1z8IU5UXMXPJUaoe/s0/Image46.png" width="315" /></a></div><div><div>Lastly, inspired by the ornaments on the directional pointers on this compass:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_MGc_NR1htbVUsXF_YsMbsyzUX1XROYwXcFQFo_5EarxfCDg2q__OiMW-T-L1X2feY3Y33Wj68I1ZHbJrvWmIgRv5MLI5Rj1LhqW83vbfXKN-JxELCyWcstJfpURQv7vwR8MisUL2CPh-/s600/compass11.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="579" data-original-width="600" height="309" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_MGc_NR1htbVUsXF_YsMbsyzUX1XROYwXcFQFo_5EarxfCDg2q__OiMW-T-L1X2feY3Y33Wj68I1ZHbJrvWmIgRv5MLI5Rj1LhqW83vbfXKN-JxELCyWcstJfpURQv7vwR8MisUL2CPh-/s320/compass11.png" width="320" /></a></div>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.</div><div><br /></div><div>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:</div><div><pre style="background: rgb(255, 255, 255);"> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>numRadialElements<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>relementLineWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNS2u5l_XMX5dHnzKeoJq3d-dZpZy7j2gBCtR6ZLK4r-GarWmt0ivxDi-9dBugxHypcZNkJcDE28JVfn3bZ0to0Vt4XMMn0Ib0brGbCnsB9ORwmRMDxYcg-uSQwsY0WP67CQkmATT83ug6/s317/Image39.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="317" data-original-width="317" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNS2u5l_XMX5dHnzKeoJq3d-dZpZy7j2gBCtR6ZLK4r-GarWmt0ivxDi-9dBugxHypcZNkJcDE28JVfn3bZ0to0Vt4XMMn0Ib0brGbCnsB9ORwmRMDxYcg-uSQwsY0WP67CQkmATT83ug6/s0/Image39.png" width="317" /></a></div>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).</div><div><pre style="background: rgb(255, 255, 255);"> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">3*<$relementSize>/4</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RTRI<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> $numRadialElementsShared<span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: maroon;">`</span><span style="color: #0000e6;">3*<$relementSize><span style="color: #0000e6;">/4.1</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><$relementSize><span style="color: #0000e6;">/2.2</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span><br /></span> $relementLineWidthShared<span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/4</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> $numRadialElementsShared<span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
$relementLineWidthShared<span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvOp-7Ee-y07LKFG82jZY9HDPoNKsbp63dtTt9ojEmUHzCkQXy3Y64ZCGZD-L-fA1YRUl1CsTgWtBvPlnUSSEEfgQUSPxoWHGZUFdjyE8J3QA6GyA5e5W17h3y5XqEfCnOS6PsNjFBkAiK/s317/Image40.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="317" data-original-width="317" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvOp-7Ee-y07LKFG82jZY9HDPoNKsbp63dtTt9ojEmUHzCkQXy3Y64ZCGZD-L-fA1YRUl1CsTgWtBvPlnUSSEEfgQUSPxoWHGZUFdjyE8J3QA6GyA5e5W17h3y5XqEfCnOS6PsNjFBkAiK/s0/Image40.png" width="317" /></a></div></pre><div>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:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcA6p5uzKLM41-QiDnEymxBDOokqYOyvdTl8vK_5kP6mK32O4icdL4ykfM9JbUtcZnaRvpNh0FbFSLyeRkUJizuQnKqOy-0A9f4kFNCBwU9-Sj56R3GMJD-L5DqkgMDusOx_Nub_khFmhH/s317/Image41.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="317" data-original-width="317" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcA6p5uzKLM41-QiDnEymxBDOokqYOyvdTl8vK_5kP6mK32O4icdL4ykfM9JbUtcZnaRvpNh0FbFSLyeRkUJizuQnKqOy-0A9f4kFNCBwU9-Sj56R3GMJD-L5DqkgMDusOx_Nub_khFmhH/s0/Image41.png" width="317" /></a></div>So I'll just leave this black. Here's another multi-part radial element that works better at this scale:</div><div><pre style="background: rgb(255, 255, 255);"> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> $numRadialElementsShared<span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: green;">0.5</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
RLINE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> $numRadialElementsShared<span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">-<$relementSize></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: green;">0.5</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfTbp2ld46-7y_I8q-8zP7fIoDUb2HC4mxFNHRKVC8cWVqC_q4xbzLrs0Zz9sIUtbin_L1OqGMU-hT1nYb0Mre8_M22pw5bFadz1C859eNUnOlPE3lzakx2FAZiTaANzS8lIMagYYGvn4M/s317/Image42.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="317" data-original-width="317" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfTbp2ld46-7y_I8q-8zP7fIoDUb2HC4mxFNHRKVC8cWVqC_q4xbzLrs0Zz9sIUtbin_L1OqGMU-hT1nYb0Mre8_M22pw5bFadz1C859eNUnOlPE3lzakx2FAZiTaANzS8lIMagYYGvn4M/s0/Image42.png" width="317" /></a></div><div>A reminder that if you want to follow along from home, you need to download the Part 16 branch from the <a href="https://github.com/srt19170/Procedural-Map-Compasses/tree/Part-16" target="_blank">Procedural Map Compasses repository</a> on Github and get the test web page open. <span style="background-color: white;"><a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">Part 1 of this series</a> 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.</span> You can also try out this code on the web at <a href="https://dragonsabound-part16.netlify.app/test.html">https://dragonsabound-part16.netlify.app/test.html</a> 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!<br /><div><br /></div><div><div><h3>Suggestions to Explore</h3></div><div><ul><li>I've implemented two “pseudo" radial elements created by composing two of the existing elements. Invent some additional pseudo elements and implement them.<br /><br /></li><li>An interesting variant of a radial element is a circular repeating pattern as in this example:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMKHMWoUxrB2Z8poftxlbmuXkLfAxJz0bJugLjABiVeTX1fFdpYZajI6IcKVaKrMeu5Gq2F6aXtGe1aXxg3yjmo8PLW5FzQT-7UxoMqUEL9Bg8LvnGjQl60hSA3ek9iQUUB5Pae-wneAaJ/s422/compass1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="400" data-original-width="422" height="303" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMKHMWoUxrB2Z8poftxlbmuXkLfAxJz0bJugLjABiVeTX1fFdpYZajI6IcKVaKrMeu5Gq2F6aXtGe1aXxg3yjmo8PLW5FzQT-7UxoMqUEL9Bg8LvnGjQl60hSA3ek9iQUUB5Pae-wneAaJ/s320/compass1.png" width="320" /></a></div><br />Or more simply, a line that zig-zags up and down to make sawteeth. How would you implement these patterns?</li></ul></div></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-42733379274542052422022-03-27T11:45:00.000-04:002022-03-27T11:45:36.843-04:00Map Compasses (Part 15): Scales<p>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:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj00KdurUPXCtaQ88pbmslQ3XjLtajqUJ8Eb9pDsFoCKFKlw7h7Liax2JyMg8tskR5JMVC3vTRRdsmvcqqgxMQ4uCF4DgMS8Cd7j_ZMVAlkfyTyir_qxris7IxfadadksHtoEjX8KHBkczu/s202/Image13.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="202" data-original-width="202" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj00KdurUPXCtaQ88pbmslQ3XjLtajqUJ8Eb9pDsFoCKFKlw7h7Liax2JyMg8tskR5JMVC3vTRRdsmvcqqgxMQ4uCF4DgMS8Cd7j_ZMVAlkfyTyir_qxris7IxfadadksHtoEjX8KHBkczu/s0/Image13.png" width="202" /></a></div>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:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCju1VG0-cAyOZy0umL6x0OHeBA1NfpsbeVUObB6fht_9IJZsSaLSp10xA-5gX9d-LjpBUCJgOskmdJjy-aJqFy6uF8obm5Xf_DIy2XExriNDH_xzNvH2eyR-OAlrMeS44EizuAPUhUJ_l/s401/Image29.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="401" data-original-width="381" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCju1VG0-cAyOZy0umL6x0OHeBA1NfpsbeVUObB6fht_9IJZsSaLSp10xA-5gX9d-LjpBUCJgOskmdJjy-aJqFy6uF8obm5Xf_DIy2XExriNDH_xzNvH2eyR-OAlrMeS44EizuAPUhUJ_l/w190-h200/Image29.png" width="190" /></a></div>A scale is a radial arc sandwiched between two thin circles:<div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>scaleRing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> RARC<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">8</span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/8</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: #008c00;">5</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">5</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> <span style="color: purple;">;</span></pre><div><div>As usual, I'll get one example working and then generalize it. The above rule gives me this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4ECjeGu9Gyi0AE-YFKJ0JDZ6wbzn3O29IQnUIpoPeJgRi8T3erhtYMWyNM6uZedyAb3Qff5l7bxhNcRZq79RJ3aYW7dmnhon8MtG1IlX-He_-UEbYDwEjvxuYcOerX0pFzWU6TpKl58JX/s206/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4ECjeGu9Gyi0AE-YFKJ0JDZ6wbzn3O29IQnUIpoPeJgRi8T3erhtYMWyNM6uZedyAb3Qff5l7bxhNcRZq79RJ3aYW7dmnhon8MtG1IlX-He_-UEbYDwEjvxuYcOerX0pFzWU6TpKl58JX/s0/Image1.png" width="206" /></a></div><br /><div>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:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiufN4Razoy_w08usFQkJEirX8SQC0Xp90GCcpgF6sXncwgkEb8TT25uTCSP5qMmpwQZ0TJDNzXXdWo9f5OcDNqN_bC3PKiN4OJ6wtOwEVJ9kDCpwSXbA-jH6Gz2LZ2Dhc-JSTrWhhFTXOm/s206/Image2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiufN4Razoy_w08usFQkJEirX8SQC0Xp90GCcpgF6sXncwgkEb8TT25uTCSP5qMmpwQZ0TJDNzXXdWo9f5OcDNqN_bC3PKiN4OJ6wtOwEVJ9kDCpwSXbA-jH6Gz2LZ2Dhc-JSTrWhhFTXOm/s0/Image2.png" width="206" /></a></div>Although sometimes a fat compass point will completely cover a division of the scale and that looks off to my eye:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhh3w88vyAsg21rHRz1A49Ej8Jfy4h87Vt9bw4QJG3jk34uCkwuUqh7HdKciZHF4r9fDUxeLeKyQ-Qf3POfoiVSPEaezgfdSAobAu9vLTPqwhTwW85IPzoyL6DYS4sSxQk02IrZLUEEHlNc/s206/Image3.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhh3w88vyAsg21rHRz1A49Ej8Jfy4h87Vt9bw4QJG3jk34uCkwuUqh7HdKciZHF4r9fDUxeLeKyQ-Qf3POfoiVSPEaezgfdSAobAu9vLTPqwhTwW85IPzoyL6DYS4sSxQk02IrZLUEEHlNc/s0/Image3.png" width="206" /></a></div>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.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzQCyeZ24NKHkfl9MKZCUqWuqMTiWVzMVtocID3nB8VoiaHKwvd4bcwVW2pKXPTeyQrVsRbxg1St9ZWb0ifWYU6q5-vRj0mgZa7DE8x-nckV0eGecOzlJSXPe-6B7biYqpQAGrYAavgclU/s209/Image4.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="209" data-original-width="209" height="209" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzQCyeZ24NKHkfl9MKZCUqWuqMTiWVzMVtocID3nB8VoiaHKwvd4bcwVW2pKXPTeyQrVsRbxg1St9ZWb0ifWYU6q5-vRj0mgZa7DE8x-nckV0eGecOzlJSXPe-6B7biYqpQAGrYAavgclU/s0/Image4.png" width="209" /></a></div>A bit surprising to me, but 32 divisions look good as well. More surprising is that any even number of divisions looks okay:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqHxKZPY5GpdB11a6RXT-wrujzhzcT2sJV_gSc6TbqvbOBE1JzWKRQlpqJjuEFr0tImqDg-4Z39I5K7XBp9dzTkZ94YgLHDgr2bf5G5DQwHa_zqbTo46CpAenb9rIEtmL5JygufTd3otLa/s206/Image6.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqHxKZPY5GpdB11a6RXT-wrujzhzcT2sJV_gSc6TbqvbOBE1JzWKRQlpqJjuEFr0tImqDg-4Z39I5K7XBp9dzTkZ94YgLHDgr2bf5G5DQwHa_zqbTo46CpAenb9rIEtmL5JygufTd3otLa/s0/Image6.png" width="206" /></a></div>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.<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNjYmvUUEwTdgB8poVjVzP_IlLHyvH6Po1LxOLC8MCbC6WIJdeiOOEwpa7jaPH2Amo2QWY6SuO8ufA9ueOXflMSxZQArceMhEzIy2sBjVcJPt7056Gafdu-9IcS6bCuLH5JOpRVxnLYCku/s206/Image7.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNjYmvUUEwTdgB8poVjVzP_IlLHyvH6Po1LxOLC8MCbC6WIJdeiOOEwpa7jaPH2Amo2QWY6SuO8ufA9ueOXflMSxZQArceMhEzIy2sBjVcJPt7056Gafdu-9IcS6bCuLH5JOpRVxnLYCku/s0/Image7.png" width="206" /></a></div>Odd numbers of divisions really do look odd and I think I can safely avoid those.<br /><div><br /></div><div>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:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqfEIiZk7w3hg8gRjsx6ezTsn1xkNQFgBrFZV5HDDqepM0Tui65WL2j6Id7R9mG5MeyNUSgSWpFGuBk6TxohOqam5skSGukl9yYozaglVhjk692Q6nn6nxXo7x0cz40fDB5UZ3aspE4lXi/s206/Image8.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqfEIiZk7w3hg8gRjsx6ezTsn1xkNQFgBrFZV5HDDqepM0Tui65WL2j6Id7R9mG5MeyNUSgSWpFGuBk6TxohOqam5skSGukl9yYozaglVhjk692Q6nn6nxXo7x0cz40fDB5UZ3aspE4lXi/s0/Image8.png" width="206" /></a></div>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:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKBlwV_69nL9Ok9QButSUPQZQ3nLIS8xdmBvaSFbe6I9AvVCC6iXR_ohEvoRt8SKIF8O8ac3nBYKui133UVvK8d5UP8DnjduPH4VQNyDA5x5xU8e1t0EGAp8K3E8LuaNf4P2f_5bOKMIn6/s206/Image9.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKBlwV_69nL9Ok9QButSUPQZQ3nLIS8xdmBvaSFbe6I9AvVCC6iXR_ohEvoRt8SKIF8O8ac3nBYKui133UVvK8d5UP8DnjduPH4VQNyDA5x5xU8e1t0EGAp8K3E8LuaNf4P2f_5bOKMIn6/s0/Image9.png" width="206" /></a></div>That's about it for the scale itself. At this point I have these rules:<div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>$scaleDivisions<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(4,16)*2</span><span style="color: maroon;">`</span> <span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$scaleStart<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">0</span> <span style="color: #808030;">|</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/<$scaleDivisions>/2</span><span style="color: maroon;">`</span> <span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$scaleWidth<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randRange(2,6)</span><span style="color: maroon;">`</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$scaleCircle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$thinWidth<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
# A scale
<span style="color: #808030;"><</span>scaleRing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>$scaleCircle<span style="color: #808030;">></span>
RARC<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$scaleStart<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$scaleDivisions<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/<$scaleDivisions></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>$scaleWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$scaleWidth<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>$scaleCircle<span style="color: #808030;">></span><span style="color: purple;">;</span></pre></pre></div><div>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:</div><div><pre style="background: rgb(255, 255, 255);"># A scale
<span style="color: #808030;"><</span>scaleRing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>scaleCircle<span style="color: #808030;">></span>
RARC<span style="color: #808030;">(</span>$scaleStart<span style="color: #808030;">,</span> $scaleDivisions<span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/$scaleDivisions</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
$scaleWidth<span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span>$scaleWidth<span style="color: #808030;">)</span> <span style="color: #808030;"><</span>scaleCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>scaleCircle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(<</span>$thinWidth><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> <span style="color: purple;">;</span>
</pre><div>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:<br /><div><pre style="background: rgb(255, 255, 255);"># A scale
<span style="color: #808030;"><</span>scaleRing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <$scaleCircle>
RARC<span style="color: #808030;">(</span>$scaleStart<span style="color: #808030;">,</span> $scaleDivisions<span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/$scaleDivisions</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
$scaleWidth<span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span>$scaleWidth<span style="color: #808030;">)</span> $scaleCircle<span style="color: purple;">;</span>
<$scaleCircle> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span>$thinWidth<span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
</pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfPDPwPOETsCaZlvI3HM7O4KNn8BRamln0tyMtQsz6g1s4KaLlTSg-wweAamMnaFajkWCpBiwZ-GLDJc-UbkjXhArGgRnZg9BwJ51ruASgknPeHFwPGRDw6Mfpj75auu2kF7CLk2Ja9Vz3/s206/Image10.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="206" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfPDPwPOETsCaZlvI3HM7O4KNn8BRamln0tyMtQsz6g1s4KaLlTSg-wweAamMnaFajkWCpBiwZ-GLDJc-UbkjXhArGgRnZg9BwJ51ruASgknPeHFwPGRDw6Mfpj75auu2kF7CLk2Ja9Vz3/s0/Image10.png" width="206" /></a></div>I think the double circles look quite nice actually.</div><div><br /></div><div>As a last experiment, let me try a rule for a compass with two rings:<br /><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(10,30)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">insideEdge</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">3</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">></span><span style="color: purple;">;</span>
</pre><div><div>This is the same as the existing rule, just tacking on a little separation and then a second ring:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicW7MtYQeQxidExabXh7kPhMaNYGh44NSvWxYSFJphTp7g44PbKgh6VYPBRckLi_wOFmIIqCZGYaIV3p3luUuBGS_NUB7nN9fOUR9qSpqWITtVyxEcJ6JmfqUssifuwBfZm15jw2c3r4pC/s212/Image15.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="212" data-original-width="212" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicW7MtYQeQxidExabXh7kPhMaNYGh44NSvWxYSFJphTp7g44PbKgh6VYPBRckLi_wOFmIIqCZGYaIV3p3luUuBGS_NUB7nN9fOUR9qSpqWITtVyxEcJ6JmfqUssifuwBfZm15jw2c3r4pC/s0/Image15.png" width="212" /></a></div>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.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjh7ODtq4Io4S11aLohA5d_2A_KO13B8-RhiE00-M8PJaMEWKs-9rQoHmsXQCw45PEbJJvuhI7q-EP3ZXA_oZZJwnDnM9H1ocggH-tJUZpBNk4RnXKsNlQMRUbNt6kaX44lZar87L7FNX9e/s212/Image16.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="212" data-original-width="212" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjh7ODtq4Io4S11aLohA5d_2A_KO13B8-RhiE00-M8PJaMEWKs-9rQoHmsXQCw45PEbJJvuhI7q-EP3ZXA_oZZJwnDnM9H1ocggH-tJUZpBNk4RnXKsNlQMRUbNt6kaX44lZar87L7FNX9e/s0/Image16.png" width="212" /></a></div>Some examples don't look as good. Two scales looks odd:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaHP6jy5aC73_FPZ2yzvxruJKM7x8STpE6inJszeCicNuIo4sUXQaYlwqm8MfugMfSsNFU_4RqE1uQ-N4KJvFdx1l1VCw30WBaYFxX-XkRPX7tuMaR8c6KE2YWZDaP85ZMCT7FIPqbrjhJ/s204/Image17.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="204" data-original-width="204" height="204" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaHP6jy5aC73_FPZ2yzvxruJKM7x8STpE6inJszeCicNuIo4sUXQaYlwqm8MfugMfSsNFU_4RqE1uQ-N4KJvFdx1l1VCw30WBaYFxX-XkRPX7tuMaR8c6KE2YWZDaP85ZMCT7FIPqbrjhJ/s0/Image17.png" width="204" /></a></div>It also looks wrong to me to have the scale on the inner ring:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQfrBBBqencCpP28OiuDUiRGLx8Y_sKihCtr_odzK74RV__uap6GS65pXwEuWtsbLbBqA0aNRuNU2GvMieSWv6_yolCOw7l8m6Pww4bcjqr1g0oD466xUIRwJC24hTwxbxsXaZDGbeL_eL/s204/Image18.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="204" data-original-width="204" height="204" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQfrBBBqencCpP28OiuDUiRGLx8Y_sKihCtr_odzK74RV__uap6GS65pXwEuWtsbLbBqA0aNRuNU2GvMieSWv6_yolCOw7l8m6Pww4bcjqr1g0oD466xUIRwJC24hTwxbxsXaZDGbeL_eL/s0/Image18.png" width="204" /></a></div>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:<br /><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(5,20)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ringOrScale<span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">insideEdge</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">3</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>ring<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thickThin<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThick<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThinThin<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThickThin<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>ringOrScale<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thickThin<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThick<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThinThin<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThickThin<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>scaleRing<span style="color: #808030;">></span><span style="color: purple;">;</span></pre><div>In the course of writing the two ring rules, I accidentally put two scales on top of each other to interesting effect:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirnGZivYd51C50s3tB3jSnBNfD9JaEvfTIRz6B12H_cxOKQnxLw0bMCe1ZscGUiGjEYCShWLWqLyio1dqWnksWqqdzNCQTowvdrP50b7deWGXCqYRoD-of-Kqqa8u7WlxAzWRwT14J9ORD/s316/Image2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="316" data-original-width="316" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirnGZivYd51C50s3tB3jSnBNfD9JaEvfTIRz6B12H_cxOKQnxLw0bMCe1ZscGUiGjEYCShWLWqLyio1dqWnksWqqdzNCQTowvdrP50b7deWGXCqYRoD-of-Kqqa8u7WlxAzWRwT14J9ORD/s0/Image2.png" width="316" /></a></div>Intentionally, this is done by putting a second scale with more divisions on top of the original scale:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>scaleRing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>$scaleCircle<span style="color: #808030;">></span>
RARC<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/<$scaleDivisions>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$scaleDivisions<span style="color: #808030;">></span><span style="color: #808030;">,</span>
<span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/<$scaleDivisions></span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #808030;"><</span>$scaleWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$scaleWidth>/3</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RARC<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/<$scaleDivisions>/2</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$scaleDivisions>*2</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: maroon;">`</span><span style="color: #0000e6;">Math.PI/(2*<$scaleDivisions>)</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: maroon;">`</span><span style="color: #0000e6;"><$scaleWidth>/3</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">2*<$scaleWidth>/3</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>$scaleCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
</pre><div>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.</div><div><br /></div><div>There are some obvious variants:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3uxowpOUv6I5P8LoXgK39JjyZ2btgPNazCBcQko0Ld0J1Ver6iI8OXB_fDmwSaqDTiNfoeqrymwWHdZMti8Okw47pVU-zl3ZOO3InXPOA6lgTHlUIX5EnJtRMVNEbywVi6wAMcMSufsP6/s600/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3uxowpOUv6I5P8LoXgK39JjyZ2btgPNazCBcQko0Ld0J1Ver6iI8OXB_fDmwSaqDTiNfoeqrymwWHdZMti8Okw47pVU-zl3ZOO3InXPOA6lgTHlUIX5EnJtRMVNEbywVi6wAMcMSufsP6/s16000/Image1.png" /></a></div>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.</div><div><br /></div><div>Another kind of scale doesn't alternate black and white but just has empty boxes, like the outer rings on these examples:</div><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDf2-jVWl12XWGx5ZywCNaujvUetFbvrrlUiPARvF6iJUIoEDhcw-SoFWecYD9DmqLa8WmMQlufwfdlu8fAqLdEtFtn5iSHcQJAublxZpHefby-9EIRmdUSAAj4ajN8yU6HCg2GruJmZ-q/s491/Image38.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="491" data-original-width="488" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDf2-jVWl12XWGx5ZywCNaujvUetFbvrrlUiPARvF6iJUIoEDhcw-SoFWecYD9DmqLa8WmMQlufwfdlu8fAqLdEtFtn5iSHcQJAublxZpHefby-9EIRmdUSAAj4ajN8yU6HCg2GruJmZ-q/w199-h200/Image38.png" width="199" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-Mca2osbE0ObRFzcJ9Yoj9sDT9QUDWtmu1NB4XarLtnPMgQ7_LxU6xVLuJwiRyr2KoWDxA3km6CNU6UD5WyTd5bvsYhz98HXWGCIdC-zMr6G2V6mPt64owPPbyEHdM-yahYUVshaStXFd/s881/Image10.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="881" data-original-width="830" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-Mca2osbE0ObRFzcJ9Yoj9sDT9QUDWtmu1NB4XarLtnPMgQ7_LxU6xVLuJwiRyr2KoWDxA3km6CNU6UD5WyTd5bvsYhz98HXWGCIdC-zMr6G2V6mPt64owPPbyEHdM-yahYUVshaStXFd/w188-h200/Image10.png" width="188" /></a></div></div>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.<br /><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>scaleRing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>$scaleCircle<span style="color: #808030;">></span>
RLINE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$scaleDivisions<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$scaleWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$scaleWidth></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>$scaleCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
</pre></div><div>The third argument to RLINE is the length of the line, and that's the distance between the two circles.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8CN8VeKR35E9GcFSPmFGQphlQrIJ5r7IrgWMrGiKmhNCXvUxVyYCPIJIxDIUMX5Te8-aajUPDBuHcCXMFznTo-2DqRm9TeKGc7i3_vONjZdJuJ2Seh1y1X1DqLnjpvw6SjZ5h_tqLrVAr/s325/Image5.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="325" data-original-width="325" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8CN8VeKR35E9GcFSPmFGQphlQrIJ5r7IrgWMrGiKmhNCXvUxVyYCPIJIxDIUMX5Te8-aajUPDBuHcCXMFznTo-2DqRm9TeKGc7i3_vONjZdJuJ2Seh1y1X1DqLnjpvw6SjZ5h_tqLrVAr/s320/Image5.png" width="320" /></a></div>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:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>$scaleDivisions8<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(1,4)*8</span><span style="color: maroon;">`</span> <span style="color: purple;">;</span>
<span style="color: #808030;"><</span>scaleRing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>$scaleCircle<span style="color: #808030;">></span>
RLINE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$scaleDivisions8<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$scaleWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$thinWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$scaleWidth></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span>
RLINE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;"><$scaleDivisions8>*4</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$scaleWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$thinWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;"><$scaleWidth></span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
<span style="color: #808030;"><</span>$scaleCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
</pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHCYo2jHhXohTzv1OC76ULhz5lcCUY9XwJ02YT7oP6QktjDUMmo6tj7v7bCyBV7D0L1_sZ5554z8I0C_LQTTCfV1zn5F8VaFbwi9fEWLAns7-hv1cGDiAofKeciroGQ2rrBXkUlTLPOH0x/s316/Image6.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="316" data-original-width="316" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHCYo2jHhXohTzv1OC76ULhz5lcCUY9XwJ02YT7oP6QktjDUMmo6tj7v7bCyBV7D0L1_sZ5554z8I0C_LQTTCfV1zn5F8VaFbwi9fEWLAns7-hv1cGDiAofKeciroGQ2rrBXkUlTLPOH0x/s0/Image6.png" width="316" /></a></div><br /><div>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.</div><div><br /></div><div>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). <div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfIix1cn-FwictU4__Sx1R2tA_L84T_NT0jShAm47qCQiEk6bS1jaZeSUR3T2NOEAuhpj5TfoqDeXvj03DWIjkrMTQG3v-koliEXTT4M-cA5-901ixsR38gMCf35dQ9WZj-uW_pEeetv6m/s600/Image7.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="198" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfIix1cn-FwictU4__Sx1R2tA_L84T_NT0jShAm47qCQiEk6bS1jaZeSUR3T2NOEAuhpj5TfoqDeXvj03DWIjkrMTQG3v-koliEXTT4M-cA5-901ixsR38gMCf35dQ9WZj-uW_pEeetv6m/s16000/Image7.png" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div>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.</div><div><br /></div><div><span style="background-color: white;">A reminder that if you want to follow along from home, you need to download the Part 15 branch from the </span><a href="https://github.com/srt19170/Procedural-Map-Compasses" style="background-color: white;" target="_blank">Procedural Map Compasses repository</a><span style="background-color: white;"> 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. </span>You can also try out this code on the web at <a href="https://dragonsabound-part14.netlify.app/test.html">https://dragonsabound-part15.netlify.app/test.html</a> 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.<br /><div><h4>Suggestions to Explore</h4></div><div><ul style="text-align: left;"><li>In the example with two scales, both scales have the same number of divisions and width. Why is that?<br /><br /></li><li>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.<br /><br /></li><li>This compass has a number of interesting features:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCSqHg2Fi9Wy9I_ofv7drmEsXOxFIVuz4-FgtJHnepd05X0jzZM5JztjVUJxLBPlrBlB50uMX7xh8TLsLQbQTEeYua9-hBwdX0VkLs__-a42OviLysxfysiRtO-1aQ0h_Z8Q-NxALTZBoP/s689/Image34.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="689" data-original-width="670" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCSqHg2Fi9Wy9I_ofv7drmEsXOxFIVuz4-FgtJHnepd05X0jzZM5JztjVUJxLBPlrBlB50uMX7xh8TLsLQbQTEeYua9-hBwdX0VkLs__-a42OviLysxfysiRtO-1aQ0h_Z8Q-NxALTZBoP/w194-h200/Image34.png" width="194" /></a><br /><div style="text-align: left;">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.</div></div><br /></li></ul></div></div></div></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-4731577144786916812022-03-02T18:48:00.000-05:002022-03-02T18:48:48.680-05:00Map Compasses (Part 14): Lodestone Loader & First Compasses<p>Welcome back to my blog series on implementing procedurally-generated map compasses! Last time I completed the implementation of the Lodestone rules engine that is able to read an execute rules like these:</p><pre style="background: rgb(255, 255, 255);"><$pi> => `Math.PI`;
<start> => <first> [1] | <second> [`99+99`];
<first> => I'm surprised this was selected!;
<second> => The value of pi is <$pi>;
</pre><p>But entering these rules as a Javascript string is painful, so today I'm going to start off by building a little utility that will read in a file of rules and prepare them for use.</p><div>But there's one -- or really two -- complications when you try to read a file from the browser. For security reasons, Javascript running in the browser is not permitted to read in files. This makes sense -- you don't want some random website looking around on your computer for MyPasswords.txt or any other sensitive information. What the browser <b>is</b> allowed to do is read any file or web page it can reach with a URL. So the browser can't read a file of rules from the local directory, but it can read the same file from a web server. This is why this project is set up to use the <a href="https://mongoose.ws/" target="_blank">Mongoose</a> web server. The code can ask the web server to give it files that are located in the project directory, such as a file of rules or a local font file.</div><div><br /></div><div>So the first complication is that we have to get to the rules file through the web server rather than loading it directly. The second complication is that the browser mechanism for fetching a file this way works asynchronously, meaning it doesn't wait around for the file to be fetched, it just promises to let you know later when it's done. There are actually two asynchronous steps to this process. Let me start with the first and easiest step -- fetching the contents of the URL as a “blob."</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: dimgrey;">// Gets the response and returns it as a blob</span>
<span style="color: maroon; font-weight: bold;">let</span> blob <span style="color: #808030;">=</span> await fetch<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">compass.rules</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>then<span style="color: #808030;">(</span>res <span style="color: #808030;">=</span><span style="color: #808030;">></span> res<span style="color: #808030;">.</span>blob<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></pre>To do that, I use the browser's fetch() function, which takes a URL and fetches the contents of the URL. In this case, the URL is just the name of the file with the rules in it. Since this file is in the same directory as the file with the Javascript, I can use this sort of relative URL. The .then() method is executed when the fetch completes, and takes the result (which has a bunch of information) and pulls off the “blob." The details of the blob aren't important; it's just an internal format that could be text, an image, or anything else that could be fetched by URL.</div><div><br /></div><div>But since fetch is an asynchronous function, it returns immediately, before it has fetched anything and before the then() has pulled out the blob. The purpose of the await keyword in front of the fetch is to force the Javascript engine to stop and wait for the fetch (really the then) to complete. So in effect, the await keyword turns the asynchronous function into a synchronous function.</div><div><br /></div><div>Now I need to turn the blob into text. Javascript provides a mechanism to do this called a FileReader, which has a method readAsText that takes a blob and returns text. Or at least it eventually returns text, because like fetch, FileReader is asynchronous. You might think then that I could use await like this:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">const</span> compassRulesReader <span style="color: #808030;">=</span> <span style="color: maroon; font-weight: bold;">new</span> FileReader<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> text <span style="color: #808030;">=</span> await compassRulesReader<span style="color: #808030;">.</span>readAsText<span style="color: #808030;">(</span>blob<span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></div><div>But this would be too easy. You see, await is built on something called Promises, and FileReader is from before Promises were added to Javascript. So it uses an older and uglier way to accomplish the same thing, something called event handlers. Fortunately, it is possible to <a href="https://blog.shovonhasan.com/using-promises-with-filereader/" target="_blank">wrap FileReader in a Promise</a> so that it can be used with await. I won't show that code here -- you can see it at the link above or in the compass.js file. But with that wrapper in place I can write:</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: maroon; font-weight: bold;">const</span> blob <span style="color: #808030;">=</span> await fetch<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">compass.rules</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>then<span style="color: #808030;">(</span>res <span style="color: #808030;">=</span><span style="color: #808030;">></span> res<span style="color: #808030;">.</span>blob<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> text <span style="color: #808030;">=</span> await readText<span style="color: #808030;">(</span>blob<span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></div><div>And at the end of that, text holds the text of the compass.rules file. I can put these two awaits together and put this at the top level of the compass module as so:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">const</span> compassRulesText <span style="color: #808030;">=</span> await fetch<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">compass.rules</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>then<span style="color: #808030;">(</span>res <span style="color: #808030;">=</span><span style="color: #808030;">></span> res<span style="color: #808030;">.</span>blob<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>then<span style="color: #808030;">(</span>blob <span style="color: #808030;">=</span><span style="color: #808030;">></span> readText<span style="color: #808030;">(</span>blob<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></div><div>When you put an await at the top level of a module like this, you essentially pause the loading of the module until the await completes. In this case, that's fine, but in many cases you want to avoid doing that so that you can immediately use the parts of the module that aren't waiting.</div><div><br /></div><div>Now that I have the text of the compass.rules file, I can prepare those rules and then execute them to create a compass description in CDL and pass it to the interpreter:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> <span style="color: maroon; font-weight: bold;">test</span><span style="color: #808030;">(</span>svg<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> rules <span style="color: #808030;">=</span> prepareLodestone<span style="color: #808030;">(</span>compassRulesText<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> cdl <span style="color: #808030;">=</span> executeRules<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;"><compass></span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> s <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon; font-weight: bold;">eval</span><span style="color: #808030;">(</span>s<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> result <span style="color: #808030;">=</span> interpretCDL<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> parseCDL<span style="color: #808030;">(</span>cdl<span style="color: #808030;">,</span> <span style="color: #0f4d75;">true</span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> <span style="color: #808030;">[</span><span style="color: #008c00;">100</span><span style="color: #808030;">,</span><span style="color: #008c00;">100</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> <span style="color: #008c00;">75</span><span style="color: #808030;">,</span> <span style="color: #0f4d75;">true</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
</pre></div><div>Of course, I'll need to actually have some rules in compass.rules that will produce a compass :-) so let me start in on that. (Finally! We've been waiting!) In the past posts I've used these two compasses as examples:</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVPldPYKI-3jNU6dnuGp4eLQe7d5DPoU_3BIfZkeI8inmU4wgSvnKWBg0-f9aieitySmQbIg9hBHSbi_h08b97N8Qrvc1iUte6QW5zHpTvCYTiG4Y7-hzEeG_SZBfh1ScerinsqCEUHZP1/s500/Image72.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="251" data-original-width="500" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVPldPYKI-3jNU6dnuGp4eLQe7d5DPoU_3BIfZkeI8inmU4wgSvnKWBg0-f9aieitySmQbIg9hBHSbi_h08b97N8Qrvc1iUte6QW5zHpTvCYTiG4Y7-hzEeG_SZBfh1ScerinsqCEUHZP1/s16000/Image72.png" /></a></div>These are what I think of as “two-layer" compasses. They've got a bottom layer consisting of a circular decoration, and then a top layer of compass points. There are a few other two-layer compasses in the example compasses, but I'll start with these two.<div><br /></div><div>To begin with, I'll put together rules to produce the left-hand example. </div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>compass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;"><</span>topLayer<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">23</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>ring<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thickCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">3</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>thickCircle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> CIRCLE<span style="color: #808030;">(<$</span>thickWidth><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">);</span>
<span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> CIRCLE<span style="color: #808030;">(<</span>$thinWidth><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">);</span>
<$thickWidth> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">4</span> <span style="color: #808030;">|</span> <span style="color: green;">3.5</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">3;</span>
<$thinWidth> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: green;">1.5</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">1</span> <span style="color: #808030;">|</span> <span style="color: green;">0.5;</span>
<span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RPOINT<span style="color: #808030;">(</span><span style="color: green;">0.7854</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: green;">0.85</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">);</span>
<span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;">-</span><span style="color: #008c00;">22</span><span style="color: #808030;">)</span> RPOINT<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: green;">0.85</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: maroon;">;</span></pre><div>This reproduces something fairly close to the example compass above, but that's all it can do. It's stuck at <a href="https://heredragonsabound.blogspot.com/2022/02/map-compasses-part-11-philosophy-of.html" target="_blank">Level 0 of procedural generation</a>, only able to produce one object. But by individually generalizing the parameters in these rules I can transform this into a Level 1 procedural generator.</div><div><br /></div><div>Let's start with a single rule:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">23</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">>;</span>
</pre><p>How can I generalize this rule to increase the space of compasses it produces? The obvious first candidate is to turn the specific 23 pixel space into a range of possible spaces. It isn't immediately obvious what a reasonable range is, but I can write the rule and try it out to narrow down. To pick a random range of values I could write a rule like this:</p><pre style="background: rgb(255, 255, 255);">distance <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">17</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">18</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">19</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">20</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">21</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">22</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">23</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">24</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">25</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">26</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">27;</span>
</pre><p>But those sorts of rules get tedious very quickly, so I prefer to use the embedded Javascript capability to have Javascript generate a random number in a range:<br /></p><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Math.random()*40+10</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">>;</span></pre>In this case in the range from 10 to 50. It's handy to have some utility functions to deal with random numbers, so I've included some in utils.js and I'll use one here instead of Math.random:</div></div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(10,50)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">>;</span>
</pre>This function generates an random integer in the given range. But rather than moving the ring in closer or farther away from the center, this seems to grow or shrink the whole compass:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGd7EDKm8wfTAVs9wzRLxKojvRvXKWqa9mISdb0oEwHGohxCwECHRRwwW2gP16L_7jut-aaxnrKtR-TCamr8TOhhyphenhyphenlUL2tRqjKAQt5Im1gXj1Hc0Mkn4GH6UO9fTKnnSMZy05r9O6sP0SL/s314/Image5.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="208" data-original-width="314" height="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGd7EDKm8wfTAVs9wzRLxKojvRvXKWqa9mISdb0oEwHGohxCwECHRRwwW2gP16L_7jut-aaxnrKtR-TCamr8TOhhyphenhyphenlUL2tRqjKAQt5Im1gXj1Hc0Mkn4GH6UO9fTKnnSMZy05r9O6sP0SL/s0/Image5.png" width="314" /></a></div>So what's going on here? The problem is not in this rule but in a related rule:<div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;">-</span><span style="color: #008c00;">22</span><span style="color: #808030;">)</span> RPOINT<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: green;">0.85</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: maroon;">;</span>
</pre></div><div>The SPACE(-22) here is intended to undo the SPACE(23) in the <bottomLayer> rule. But when the value in that rule isn't 23, everything breaks.</div><div><br /></div><div>There are a number of ways you might fix this. But I think what's really needed here is a method in CDL to remember a location and return to it later. I implemented a version of this for <a href="https://heredragonsabound.blogspot.com/2019/03/map-borders-part-5.html" target="_blank">MBDL</a> called PUSH and POP that let you return to the last location you had remembered. But for CDL I think it will be better to have the capability to remember and return to multiple locations by name, so I'm going to implement two new commands. REMEMBER(name) will remember the current location as “name" and RECALL(name) will return to that location.</div><div><br /></div><div>Implementing this is not difficult. First I will add those commands to the Nearley parser:</div><div><pre style="background: rgb(255, 255, 255);"># Remember a location
rememberElement <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">REMEMBER</span><span style="color: maroon;">"</span>i WS <span style="color: maroon;">"</span><span style="color: #0000e6;">(</span><span style="color: maroon;">"</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">)</span><span style="color: maroon;">"</span> WS
<span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">(</span><span style="color: purple;">{</span>op<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">REMEMBER</span><span style="color: maroon;">",</span> name<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">4</span><span style="color: #808030;">]</span><span style="color: purple;">}</span><span style="color: #808030;">)</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span>
# Recall a location
recallElement <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RECALL</span><span style="color: maroon;">"</span>i WS <span style="color: maroon;">"</span><span style="color: #0000e6;">(</span><span style="color: maroon;">"</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">)</span><span style="color: maroon;">"</span> WS
<span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">(</span><span style="color: purple;">{</span>op<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RECALL</span><span style="color: maroon;">",</span> name<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">4</span><span style="color: #808030;">]</span><span style="color: purple;">}</span><span style="color: #808030;">)</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span>
</pre></div><div><pre style="background: rgb(255, 255, 255);"></pre></div><div>If you remember your <a href="https://nearley.js.org/" target="_blank">Nearley</a> this is pretty straightforward.</div><div><br /></div><div>Then I'll implement the two new operations in interpretCDL in compass.js:</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: maroon; font-weight: bold;">const</span> memory <span style="color: #808030;">=</span> <span style="color: purple;">{</span><span style="color: purple;">}</span><span style="color: purple;">;</span>
} <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">REMEMBER</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span><br /> memory<span style="color: #808030;">[</span>op<span style="color: #808030;">.</span>name<span style="color: #808030;">]</span> <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">RECALL</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
radius <span style="color: #808030;">=</span> memory<span style="color: #808030;">[</span>op<span style="color: #808030;">.</span>name<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span>
</pre></pre></div><div>memory is a Javascript dictionary that will keep track of the remembered locations. To REMEMBER a location, we set the name of the location in the dictionary to the current value of the radius. (Which is where we're drawing.) To RECALL a location, we get the remembered location out of the dictionary and set radius to that value.</div><div><br /></div><div>With that in place, I can now modify the compass rules as so:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">start</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;"><</span>topLayer<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(10,35)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">insideEdge</span><span style="color: maroon;">"</span><span style="color: #808030;">);</span>
<span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RECALL<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">insideEdge</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> RPOINT<span style="color: #808030;">(</span><span style="color: green;">0.7854</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: green;">0.85</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">);</span>
<span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RECALL<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">start</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> RPOINT<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: green;">0.85</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: maroon;">;</span>
</pre></div><div>I have to remember and return to two locations. The first is the start location, so I can go back there to draw the cardinal (N/S/E/W) points. I also need to remember where the inside edge of the bottom layer is, because I want the intercardinal (NE/SE/SW/NW) points to just reach the inside edge. </div><div><br /></div><div>And now even when the bottom layer ring is far out, or far in towards the center, the cardinal points still reach to the outside of the compass:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQuqLMwH5PgVr3MSgq7yeFAEUAkx6MSc8n7AjUHf2PHZPD9YmFkORGkQVzFQxaEXtIbsYr0Kxorohqug5-aPWhF4g3fKQhFGoW3rJ_mTisiZ_0w9mlBpx0yEa2k98RY9-9Eiq1YMCYvEjj/s400/Image7.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="194" data-original-width="400" height="155" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQuqLMwH5PgVr3MSgq7yeFAEUAkx6MSc8n7AjUHf2PHZPD9YmFkORGkQVzFQxaEXtIbsYr0Kxorohqug5-aPWhF4g3fKQhFGoW3rJ_mTisiZ_0w9mlBpx0yEa2k98RY9-9Eiq1YMCYvEjj/s320/Image7.png" width="320" /></a></div><div class="separator" style="clear: both;">So now I can play around a bit and pick a range for this parameter. 10 to 30 seems to be reasonable to me, but you can make your own decision. This process of selecting independent random ranges for the parameters of the individual elements I called Level 1 procedural generation in <a href="https://heredragonsabound.blogspot.com/2022/02/map-compasses-part-11-philosophy-of.html" target="_blank">a previous posting</a>.</div><div class="separator" style="clear: both;"><br /></div><div class="separator" style="clear: both;">Now that I'm repeatedly generating compasses to check the range on parameters, I'm running into a problem that I noted in the Suggestions to Explore last time -- if you hit the Test button more than once, the new compass gets drawn on top of the old compass:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisfwGkzcxFuLRdlcT8L3qY1z-6PZn_fxFCIvQ_ZukJUQ_92FmhSRDFJPejp0JsfT-ajdrwZsJipijG-QvXqiGOazrXyYurnWYvzcUHy9UnbzxIE_jDr0w9Wv9hIWzfeDNGUJxUUIK6citU/s194/Image8.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="194" data-original-width="194" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisfwGkzcxFuLRdlcT8L3qY1z-6PZn_fxFCIvQ_ZukJUQ_92FmhSRDFJPejp0JsfT-ajdrwZsJipijG-QvXqiGOazrXyYurnWYvzcUHy9UnbzxIE_jDr0w9Wv9hIWzfeDNGUJxUUIK6citU/s0/Image8.png" width="194" /></a></div>Which <a href="https://www.goodreads.com/quotes/10320-we-don-t-make-mistakes-just-happy-little-accidents" target="_blank">can look interesting sometimes</a> but does make it difficult to see what is going on. To fix this, before we draw a new compass we need to remove the old compass. Remember that the compass on the screen is just a number of SVG commands within the SVG element on the web page, so what we need to do is delete all the commands within that SVG element. In D3, this is easy:<div><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> <span style="color: maroon; font-weight: bold;">test</span><span style="color: #808030;">(</span>svg<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Remove any previous compass</span>
svg<span style="color: #808030;">.</span>selectAll<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">*</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>remove<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> rules <span style="color: #808030;">=</span> prepareLodestone<span style="color: #808030;">(</span>compassRulesText<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> cdl <span style="color: #808030;">=</span> executeRules<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;"><compass></span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> s <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon; font-weight: bold;">eval</span><span style="color: #808030;">(</span>s<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> result <span style="color: #808030;">=</span> interpretCDL<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> parseCDL<span style="color: #808030;">(</span>cdl<span style="color: #808030;">,</span> <span style="color: #0f4d75;">true</span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> <span style="color: #808030;">[</span><span style="color: #008c00;">100</span><span style="color: #808030;">,</span><span style="color: #008c00;">100</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> <span style="color: #008c00;">75</span><span style="color: #808030;">,</span> <span style="color: #0f4d75;">true</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre>selectAll is a D3 function that lets us act on all the elements that match an expression. In this case, the wildcard '*' matches all the elements within the SVG element, and then the remove() method removes those elements. This has the effect of clearing the SVG before the new compass is drawn.<div class="separator" style="clear: both;"><br /></div>Another parameter I can vary is the width of the compass points. Some experimentation suggests that a range of 0.80 to 0.925 works well. I also already have rules for the sizes of thick and thin lines, so that's about the range of Level 1 generation for this example.</div><div><br /></div><div>Level 2 of procedural generation is multi-part constraints. There are some of these already built into this compass -- namely that the intercardinal points are constrained to start at the inner edge of the ring. That's certainly something we could experiment with.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3KG-J02JAkqz4IbQDXCCODcEktd7jE3qW4R0NfUtQOU8T7IAZAhhOh3ZouQm07oilX_LryuwSrJKCa1aeJap40Yxv24dAOTVtJ0lVuMLPash4QsHtQKcPKZWJOSGstGJXOuKj4DdOXgr8/s189/Image9.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="189" data-original-width="189" height="189" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3KG-J02JAkqz4IbQDXCCODcEktd7jE3qW4R0NfUtQOU8T7IAZAhhOh3ZouQm07oilX_LryuwSrJKCa1aeJap40Yxv24dAOTVtJ0lVuMLPash4QsHtQKcPKZWJOSGstGJXOuKj4DdOXgr8/s0/Image9.png" width="189" /></a></div>That looks okay to me. A negative spacing from the inside ring is interesting if it happens to hit the thick ring:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieDrgLLcFog5IExo2OC3uPLkW87ZcbP0Sisy4RXESaLIo3YQsl0s3Ktv6kEQFyWhYFWEfCDXYHgf9Qh2VJs1C2wDA043iDCYOqKa5xdFCAzUP1q7IWKx8z0H-jpaQJ77pWmnWiDGscuXAn/s189/Image10.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="189" data-original-width="189" height="189" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieDrgLLcFog5IExo2OC3uPLkW87ZcbP0Sisy4RXESaLIo3YQsl0s3Ktv6kEQFyWhYFWEfCDXYHgf9Qh2VJs1C2wDA043iDCYOqKa5xdFCAzUP1q7IWKx8z0H-jpaQJ77pWmnWiDGscuXAn/s0/Image10.png" width="189" /></a></div>We don't have external constraints (Level 3) yet, so let's briefly move on to Level 4, independent subtypes. The obvious subtypes with these compasses are the design of the ring. Last time I did some ring subtypes to illustrate RiTa rules, so I can resurrect those here to add thin-thick and the other variants.<br /><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>ring<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thickThin<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThick<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThinThin<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThickThin<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>thickThin<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thickCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">3</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>thinThick<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">3</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thickCircle<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>thinThinThin<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">3</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">3</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>thinThickThin<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">3</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thickCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">3</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">>;</span></pre><div>When I added these rules I realized that I'd missed a Level 1 opportunity -- the space between the circles could be varied. I find this pretty common -- I'll work back and forth across the different levels of procedural generation, and making a change at one level will cause me to go back and add related changes at different levels.</div></div><div><br /></div><div>With those rules in place I'm now generating a wider variety of compasses, although they're all still obviously in the same family:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8-haqLmyoEk7ynsy4tKRddGP7ckTh6VF6HvMQT1XgllL91-YTvB1_wKN6cH-zmtWHIlGNBiVh6utyfFCjh9YofmBrRpcCQo2I_Yrm2SJSYEoDM-JhiwjHXP34_DiyZIOgBuQajd9dVnrT/s186/Image11.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="186" data-original-width="186" height="186" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8-haqLmyoEk7ynsy4tKRddGP7ckTh6VF6HvMQT1XgllL91-YTvB1_wKN6cH-zmtWHIlGNBiVh6utyfFCjh9YofmBrRpcCQo2I_Yrm2SJSYEoDM-JhiwjHXP34_DiyZIOgBuQajd9dVnrT/s0/Image11.png" width="186" /></a></div>As you add rules and complexity to the procedural generation and generate examples, you'll likely come across some results that you don't like.<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfzYjq_e1bMywihEsB8kTNHL-ujKrBl1NwIo7GN9DRUHciTBfIvrWNkxlXllnghSzl06hubPogMHVK6q7HAF2_ysEGk8jJ896qPrhopeUVzJAYyUURxNsYN8ljP7V_GYN5rmX-Iiv9y9A_/s186/Image12.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="186" data-original-width="186" height="186" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfzYjq_e1bMywihEsB8kTNHL-ujKrBl1NwIo7GN9DRUHciTBfIvrWNkxlXllnghSzl06hubPogMHVK6q7HAF2_ysEGk8jJ896qPrhopeUVzJAYyUURxNsYN8ljP7V_GYN5rmX-Iiv9y9A_/s0/Image12.png" width="186" /></a></div>When this happens, I like to do a little analysis to understand why I don't like the result. (Interestingly, in coming back to review this post weeks later, I think this compass looks fine. There's a lesson there!) In this case, I think the intercardinal pointers look wrong. Why is that? Perhaps because they're too narrow, which would direct me towards reconsidering the Level 1 constraint on that part of the generation. Or maybe they're only too narrow in comparison to the fairly thick cardinal pointers. That would suggest some kind of Level 2 constraint between those two parts of the compass. <div><br /></div><div>I also think about whether or not I should simply accept this compass, even if I don't like it a lot. There's a trade-off between getting a wide variety of interesting results from procedural generation and accepting some poor results. If you try to “clamp down" too much to avoid unhappy results you end up with something that can generate only in a limited area of the creative space and that's usually pretty boring (if competent). So it might be better to accept some level of bad results, particularly if you can toss those out and just use the good results. On the other hand, this can be a trap if you're building something like <b style="font-variant-caps: small-caps;">Dragons Abound</b> with many different procedural generation systems. If each system has only a 1% chance of getting a bad result, but you have 100 systems, then your chance of getting all 100 good results is only about 40% (!). (The power of combinatorics.) So you'll be throwing out a lot of bad results.<br /><div><br /></div><div>Let me finish off this compass by adding labels to the cardinal points. To do this I'll draw the labels first and then draw the compass inside the labels.</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>labels<span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">start</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;"><</span>topLayer<span style="color: #808030;">>;</span>
<span style="color: #808030;"><</span>labels<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RTEXT<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">Serif</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: #008c00;">16</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">bolder</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">vertical</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> '<span style="color: #808030;">[</span><span style="color: maroon;">"</span><span style="color: #0000e6;">N</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">E</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">S</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">W</span><span style="color: maroon;">"</span><span style="color: #808030;">]'</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">8</span><span style="color: #808030;">);</span>
</pre><div>(Note that I have to put the array of labels in single quotes. Otherwise Lodestone is going to interpret them as a weight -- Lodestone expects anything between square brackets to be a rule weight.)</div><div><br /></div><div>The labels are an example of where we might get a Level 3 external constraint in procedural compass generation. Rather than always use the same font, or pick a font randomly, we might want to use a font that matches (or complements) the fonts we're using in the rest of the map. But for now I don't have that constraint, so I'll modify this to add some Level 1 variation in the font, size, and type of the labels.</div></div></div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>labels<span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">start</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$labelFont<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">Serif</span><span style="color: maroon;">"</span> <span style="color: #808030;">|</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">Lobster</span><span style="color: maroon;">"</span> <span style="color: #808030;">|</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">IM Fell English</span><span style="color: maroon;">"</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$labelSize<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">14</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">16</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">18</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$labelStyle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">normal</span><span style="color: maroon;">"</span> <span style="color: #808030;">|</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">bold</span><span style="color: maroon;">"</span> <span style="color: #808030;">|</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">bolder</span><span style="color: maroon;">"</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>labels<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RTEXT<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$labelFont<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$labelSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$labelStyle<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">vertical</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">["N", "E", "S", "W"]</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">8</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></pre>I've added a couple of new font options here: Lobster and IM Fell English. These are not fonts that the browser knows about by default, so I need to add some code to the test.html web page to load these fonts. These are <a href="https://developers.google.com/fonts/docs/getting_started" target="_blank">Google Fonts</a>, meaning I can load them off of a Google server with this incantation that I've added near the top of test.html:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span><span style="color: maroon; font-weight: bold;">link</span> href<span style="color: #808030;">=</span><span style="color: maroon;">'</span><span style="color: #0000e6;">https://fonts.googleapis.com/css?family=Lobster</span><span style="color: maroon;">'</span> rel<span style="color: #808030;">=</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stylesheet</span><span style="color: maroon;">'</span> type<span style="color: #808030;">=</span><span style="color: maroon;">'</span><span style="color: #0000e6;">text/css</span><span style="color: maroon;">'</span><span style="color: #808030;">></span>
<span style="color: #808030;"><</span><span style="color: maroon; font-weight: bold;">link</span> href<span style="color: #808030;">=</span><span style="color: maroon;">'</span><span style="color: #0000e6;">https://fonts.googleapis.com/css?family=IM+Fell+English</span><span style="color: maroon;">'</span> rel<span style="color: #808030;">=</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stylesheet</span><span style="color: maroon;">'</span> type<span style="color: #808030;">=</span><span style="color: maroon;">'</span><span style="color: #0000e6;">text/css</span><span style="color: maroon;">'</span><span style="color: #808030;">></span>
</pre><div>(This is a partial answer to one of the Suggestions to Explore in <a href="https://heredragonsabound.blogspot.com/2021/12/map-compasses-part-8-radial-text.html" target="_blank">Part 8</a>.)</div><div><br /></div>Now I can now use those fonts in the labels:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwaYfn3rU-ce8nyUV2YI4beMSedrzvFem-E4_-ht5EHm2tuBN5H3Ueqis1At3XFaBZFqpLh2Hj7n6vJNIWRhr7NU7dmZCGasjgm_U9y0TerJbaDIvRYjiS90I8DOGfV7XZkDB37ddWwTVL/s202/Image13.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="202" data-original-width="202" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwaYfn3rU-ce8nyUV2YI4beMSedrzvFem-E4_-ht5EHm2tuBN5H3Ueqis1At3XFaBZFqpLh2Hj7n6vJNIWRhr7NU7dmZCGasjgm_U9y0TerJbaDIvRYjiS90I8DOGfV7XZkDB37ddWwTVL/s0/Image13.png" width="202" /></a></div><div><br /></div><div><div class="separator" style="clear: both;"><span style="background-color: white;">The complete rules for this compass now look like this:</span></div><div class="separator" style="clear: both;"><span style="background-color: white;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: #808030;"><</span>compass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>twoLayerCompass<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>labels<span style="color: #808030;">></span> REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">start</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$labelFont<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">Serif</span><span style="color: maroon;">"</span> <span style="color: #808030;">|</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">Lobster</span><span style="color: maroon;">"</span> <span style="color: #808030;">|</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">IM Fell English</span><span style="color: maroon;">"</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$labelSize<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">14</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">16</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">18</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$labelStyle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">normal</span><span style="color: maroon;">"</span> <span style="color: #808030;">|</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">bold</span><span style="color: maroon;">"</span> <span style="color: #808030;">|</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">bolder</span><span style="color: maroon;">"</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>labels<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RTEXT<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$labelFont<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$labelSize<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: #808030;"><</span>$labelStyle<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">vertical</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">["N", "E", "S", "W"]</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">8</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>bottomLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(10,30)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>ring<span style="color: #808030;">></span>
REMEMBER<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">insideEdge</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>ring<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thickThin<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThick<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThinThin<span style="color: #808030;">></span> <span style="color: #808030;">|</span> <span style="color: #808030;"><</span>thinThickThin<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$ringSpacing<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">1</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">2</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">2</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">3</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">3</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">4</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thickThin<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thickCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$ringSpacing<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thinThick<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$ringSpacing<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thickCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thinThinThin<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$ringSpacing<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span>
SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$ringSpacing<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thinThickThin<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$ringSpacing<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thickCircle<span style="color: #808030;">></span>
SPACE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$ringSpacing<span style="color: #808030;">></span><span style="color: #808030;">)</span> <span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$thickWidth<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #008c00;">4</span> <span style="color: #808030;">|</span> <span style="color: green;">3.5</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">3</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>$thinWidth<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: green;">1.5</span> <span style="color: #808030;">|</span> <span style="color: #008c00;">1</span> <span style="color: #808030;">|</span> <span style="color: green;">0.5</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thickCircle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> CIRCLE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$thickWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>thinCircle<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> CIRCLE<span style="color: #808030;">(</span><span style="color: #808030;"><</span>$thinWidth<span style="color: #808030;">></span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>topLayer<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>interCardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RECALL<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">insideEdge</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randIntRange(-5,5)</span><span style="color: maroon;">`</span><span style="color: #808030;">)</span>
RPOINT<span style="color: #808030;">(</span><span style="color: green;">0.7854</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randRange(0.80,0.925)</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span>
<span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span>cardinalPoints<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> RECALL<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">start</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
RPOINT<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: maroon;">`</span><span style="color: #0000e6;">Utils.randRange(0.80,0.925)</span><span style="color: maroon;">`</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span>
<span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></pre></span></div><div class="separator" style="clear: both;"><span style="background-color: white;">What's striking about this is that I already have 20 rules for generation, just for this one type of compass! Of course, some of these rules will likely be reused in other compass types, but in general I find that this sort of grammar-based procedural generation leads to many rules, and organizing and keeping track of them can sometimes be a challenge.</span></div><div class="separator" style="clear: both;"><span style="background-color: white;"><br /></span></div><div class="separator" style="clear: both;"><span style="background-color: white;">A reminder that if you want to follow along from home, you need to download the Part 14 branch from the </span><a href="https://github.com/srt19170/Procedural-Map-Compasses" style="background-color: white;" target="_blank">Procedural Map Compasses repository</a><span style="background-color: white;"> 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 14 directory, and then select the test.html link. You'll find the rules for the compass above in the compass.js file and you can experiment with changing and extending the rules. </span>You can also try out this code on the web at <a href="https://dragonsabound-part14.netlify.app/test.html">https://dragonsabound-part14.netlify.app/test.html</a> although you'll only be able to run the code, not modify it.</div><div><br /></div></div><div><span style="font-weight: 700;">Suggestions to Explore</span><br /><div><ul><li>Level 4 procedural generation is independent subtypes. A rule of this sort for the compass points would be to have wavy compass points for the intercardinal compass points. Implement this rule.<br /><br /></li><li>There are many more subtypes to explore for the <ring> in this compass style. Implement the scale from the other example at the top of this posting. Implement some rings that use radial circle, triangle or diamond elements.<br /><br /></li><li>Suppose you want to implement a meta-subtype for <ring> which would repeat the same ring twice:<br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg10eYTb0KM8XYkS1P05YoZEsXImT1kn3QTJXlowvwgrHqlaYqjZkNiJ3RW9XAmPU2-OGDcs28eaP9sDb_NmLflSL38yei1xWeU7SF3GFfQySwIEWyhw55yt-6lY2FL2VPSLTdBosxIjESb/s208/Image14.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="208" data-original-width="208" height="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg10eYTb0KM8XYkS1P05YoZEsXImT1kn3QTJXlowvwgrHqlaYqjZkNiJ3RW9XAmPU2-OGDcs28eaP9sDb_NmLflSL38yei1xWeU7SF3GFfQySwIEWyhw55yt-6lY2FL2VPSLTdBosxIjESb/s0/Image14.png" width="208" /></a><br />In general, is there any way to write rules which will repeat some previous rules? If not, how would you add that capability?</li></ul></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-28720720530869956172022-02-23T20:31:00.000-05:002022-02-23T20:31:55.123-05:00Map Compasses (Part 13): Lodestone<p>Welcome back to my blog series on implementing procedurally-generated map compasses! In the last posting, I started implementation of Lodestone, a tool to execute procedural generation grammars. So far I have designed the rule language and implemented a parser that outputs a dictionary of rules. In this posting I'll develop the code to execute the rules and create procedural output.</p><p>First, let me quickly review how a procedural generation grammar works and outline the algorithm for executing the rules. A grammar is a collection of rules which have non-terminals on their left-hand sides and an expansion on their right-hand sides:</p><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>name<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;"><</span>lost<span style="color: #808030;">></span> <span style="color: #808030;"><</span>coast<span style="color: #808030;">></span>
<span style="color: #808030;"><</span>coast<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> coast <span style="color: #808030;">|</span> shore <span style="color: #808030;">|</span> banks
<span style="color: #808030;"><</span>lost<span style="color: #808030;">></span> <span style="color: #808030;">=</span><span style="color: #808030;">></span> lost <span style="color: #808030;">|</span> forgotten <span style="color: #808030;">|</span> accursed</pre><p>So in this example, if we start from “<name>" the first rule would expand that into “<lost> <coast>" and the next two rules would expand <lost> and <coast> into a word choice, so we might end up with "Accursed Shore."</p><p>That explanation provides a general notion of the rule expansion algorithm, but let me describe it in more detailed terms before getting to the code.</p><p>The algorithm begins with the start non-terminal in a <a href="https://en.wikipedia.org/wiki/Stack_(abstract_data_type)" target="_blank">stack</a>. The (basic) algorithm then loops repeatedly over the following steps until the stack is empty:</p><p></p><ol style="text-align: left;"><li>Pop the top element of the stack.</li><li>If it's a non-terminal, find a rule that matches and push the right-hand side of the rule onto the stack.</li><li>If it's something else, add it to the end of the output.</li></ol><div>You can try this out in your head with the example above. Note that this algorithm works from left to right, always expanding the left-most terminal and building the output from left-to-right as well.</div><div><br /></div><div>The way the Lodestone dictionary is constructed, there is always only one rule that will match a non-terminal. But the rule can have multiple choices on the right-hand side, so we need a step to select the right-hand side choice.</div><div></div><p></p><ol style="text-align: left;"><li>Pop the top element of the stack</li><li>If it's a non-terminal, find a rule that matches and: </li><ul><li><span style="background-color: #fff2cc;">Randomly select one of the right-hand side choices for this rule</span></li><li><span style="background-color: #fff2cc;">Push the selected choice onto the stack</span></li></ul><li>If it's something else, add it to the end of the output</li></ol><div>By the time the rules are being executed, there are only two other elements that are non-terminals: strings and embedded Javascript. For a string, the action is to push it onto the output. For a Javascript element, we need to execute it:</div><div><ol style="text-align: left;"><li>Pop the top element of the stack.</li><li>If it's a non-terminal, find a rule that matches and: </li><ul><li>Randomly select one of the right-hand side choices for this rule</li><li>Push the selected choice onto the stack</li></ul><li><span style="background-color: #fff2cc;">If it's embedded Javascript, then:</span></li><ul><li><span style="background-color: #fff2cc;">Recursively expand the element</span></li><li><span style="background-color: #fff2cc;">Execute the expanded element in Javascript</span></li><li><span style="background-color: #fff2cc;">Add the return value to the end of the output</span></li></ul><li>If it's a string, add it to the end of the output.</li></ol><div>Note that an embedded Javascript element can itself look like the right-hand side of a rule, using non-terminals and choices and so on, so we need to recursively run it through the same algorithm to get it down to a string that can be executed.</div></div><div><br /></div><div>There are a couple of other nuances. First, one-time non-terminals should only be expanded once. We'll do this before starting the algorithm, and then use that value when we encounter a one-time non-terminal:</div><div><br /></div><div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div style="text-align: left;"><span style="background-color: #fff2cc;">Pre: Iterate through the dictionary, expand all one-time non-terminals and save their values. </span></div></blockquote><ol><li>Pop the top element of the stack.</li><li><span style="background-color: #fff2cc;">If it's a one-time non-terminal, add it's value to the end of the output.</span></li><li>If it's a non-terminal, find a rule that matches and: </li><ul><li>Randomly select one of the right-hand side choices for this rule</li><li>Push the selected choice onto the stack</li></ul><li>If it's embedded Javascript, then:</li><ul><li>Recursively expand the element</li><li>Execute the expanded element in Javascript</li><li>Add the return value to the end of the output</li></ul><li>If it's a string, add it to the end of the output.</li></ol><div>The last nuance has to do with selecting a right-hand side choice. This is done according to the relative weights of the choices. But weights (like embedded Javascript) can have values that look like the right-hand side of a rule. So to evaluate weights, we'll have to recursively run them through the expansion algorithm just as we do for Javascript.</div></div><div><br /></div><div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;">Pre: Iterate through the dictionary, expand all one-time non-terminals and save their values. </blockquote><ol><li>Pop the top element of the stack.</li><li>If it's a one-time non-terminal, add it's value to the end of the output.</li><li>If it's a non-terminal, find a rule that matches and: </li><ul><li><span style="background-color: #fff2cc;">Recursively expand all the choice weights</span></li><li>Randomly select one of the right-hand side choices for this rule</li><li>Push the selected choice onto the stack</li></ul><li>If it's embedded Javascript, then:</li><ul><li>Recursively expand the element</li><li>Execute the expanded element in Javascript</li><li>Add the return value to the end of the output</li></ul><li>If it's a string, add it to the end of the output.</li></ol><div>The code itself is somewhat lengthy, so I'll walk through it piece by piece.</div></div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> executeRules<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> otValues<span style="color: #808030;">=</span><span style="color: #0f4d75;">null</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// For convenience, if a single string gets passed in we'll convert it to</span>
<span style="color: dimgrey;">// a non-terminal. Otherwise the start should be an initial stack of elements.</span>
<span style="color: maroon; font-weight: bold;">let</span> stack <span style="color: #808030;">=</span> <span style="color: #797997;">Array</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">isArray</span><span style="color: #808030;">(</span>start<span style="color: #808030;">)</span> <span style="color: purple;">?</span> <span style="color: #808030;">[</span><span style="color: #808030;">.</span><span style="color: #808030;">.</span><span style="color: #808030;">.</span>start<span style="color: #808030;">]</span> <span style="color: purple;">:</span> <span style="color: #808030;">[</span><span style="color: purple;">{</span>type<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">nterm</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> value<span style="color: purple;">:</span> start<span style="color: purple;">}</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> output <span style="color: #808030;">=</span> <span style="color: maroon;">"</span><span style="color: maroon;">"</span><span style="color: purple;">;</span>
otValues <span style="color: #808030;">=</span> otValues <span style="color: #808030;">||</span> <span style="color: purple;">{</span><span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Expand any unexpanded one-time rules</span>
<span style="color: maroon; font-weight: bold;">for</span> <span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> nt <span style="color: maroon; font-weight: bold;">in</span> rules<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Is this a otnt with no value?</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>nt<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">$</span><span style="color: maroon;">"</span> <span style="color: #808030;">&&</span> <span style="color: #808030;">!</span><span style="color: #808030;">(</span>nt <span style="color: maroon; font-weight: bold;">in</span> otValues<span style="color: #808030;">)</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Put a placeholder in otValues so that we don't try to</span>
<span style="color: dimgrey;">// recursively evaluate this otnt. Real values for otnts</span>
<span style="color: dimgrey;">// will be strings, so any non-string number will do.</span>
otValues<span style="color: #808030;">[</span>nt<span style="color: #808030;">]</span> <span style="color: #808030;">=</span> <span style="color: #797997;">NaN</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Recursively evaluate nt. Mark it with an illegal value</span>
<span style="color: dimgrey;">// before starting so that </span>
<span style="color: maroon; font-weight: bold;">let</span> val <span style="color: #808030;">=</span> executeRules<span style="color: #808030;">(</span><span style="color: #808030;">[</span><span style="color: purple;">{</span>type<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">otnterm</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> value<span style="color: purple;">:</span>nt<span style="color: purple;">}</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> otValues<span style="color: #808030;">)</span><span style="color: purple;">;</span>
otValues<span style="color: #808030;">[</span>nt<span style="color: #808030;">]</span> <span style="color: #808030;">=</span> val<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre></div><div>The function takes an initial stack (or starting element), the rules dictionary, a context (which is used to evaluate embedded Javascript) and optionally a dictionary of values of the one-time non-terminals. The first step is to evaluate any unevaluated one-time non-terminals. To do this, I iterate through the rules dictionary looking for rules with a one-time non-terminal on the left-hand side that doesn't already have a value in the otValues dictionary. Since I'm going to call executeRules recursively to evaluate the right-hand side of this rule, I need to give the one-time non-terminal a value, otherwise the first thing the recursive call will try to do is evaluate the same one-time non-terminal. The value from the recursive call becomes the value of the one-time non-terminal.</div><div><br /></div><div>The next step is to start iterating through the stack.</div><div><pre style="background: rgb(255, 255, 255); color: black;"> <span style="color: dimgrey;">// Iterate the stack until it is empty or we hit a limit</span>
<span style="color: maroon; font-weight: bold;">let</span> count <span style="color: #808030;">=</span> <span style="color: #008c00;">0</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">while</span> <span style="color: #808030;">(</span>stack<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span> <span style="color: #808030;">></span> <span style="color: #008c00;">0</span> <span style="color: #808030;">&&</span> count <span style="color: #808030;"><</span> <span style="color: #008c00;">1000</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
count<span style="color: #808030;">++</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> current <span style="color: #808030;">=</span> stack<span style="color: #808030;">.</span>pop<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// If it's a one-time non-terminal that has been defined, add</span>
<span style="color: dimgrey;">// it's value to the end of the output. Otherwise it will fall</span>
<span style="color: dimgrey;">// through and get evaluated.</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>current<span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">otnterm</span><span style="color: maroon;">"</span> <span style="color: #808030;">&&</span> otValues<span style="color: #808030;">[</span>current<span style="color: #808030;">.</span>value<span style="color: #808030;">]</span> <span style="color: #808030;">&&</span>
otValues<span style="color: #808030;">[</span>current<span style="color: #808030;">.</span>value<span style="color: #808030;">]</span> <span style="color: #808030;">!=</span> <span style="color: #797997;">NaN</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
output <span style="color: #808030;">=</span> output<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>otValues<span style="color: #808030;">[</span>current<span style="color: #808030;">.</span>value<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">continue</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
</pre>I've implement a “guard" on this iteration that gives up after 1000 iterations. It's easy to accidentally write rules that will never terminate, so this provides a little safety if that occurs.</div><div><br /></div><div>The top element in the stack is popped off and then dealt with based upon its type. The first type to be handled is a one-time non-terminal. Assuming this has a value, that value will be pulled from the otValues dictionary and added to the output. If it doesn't have a value or has the NaN placeholder value, it falls through to get evaluated as if it was a regular non-terminal. (This is how one-time non-terminals get defined in the recursive call from earlier.)</div><div><pre style="background: rgb(255, 255, 255); color: black;"> <span style="color: dimgrey;">// If it's a non-terminal, find a rule that matches, select a choice,</span>
<span style="color: dimgrey;">// and push the choice on the stack.</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>current<span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">nterm</span><span style="color: maroon;">"</span> <span style="color: #808030;">||</span> current<span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">otnterm</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Select a choice from the available rules</span>
<span style="color: maroon; font-weight: bold;">let</span> choice <span style="color: #808030;">=</span> getChoice<span style="color: #808030;">(</span>current<span style="color: #808030;">.</span>value<span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> otValues<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>choice<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Push choice on the stack.</span>
stack <span style="color: #808030;">=</span> stack<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>choice<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">slice</span><span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>reverse<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// No choice was found, so treat the nterm as a string</span>
output <span style="color: #808030;">=</span> output<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>current<span style="color: #808030;">.</span>value<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">continue</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
</pre>The next part deals with non-terminals (and unevaluated one-time non-terminals). The getChoice function selects a right-hand side from among the choices for this non-terminal. The right-hand side is then pushed onto the top of the stack. Note that since stacks in Javascript actually push and pop from the end of the array, we need to reverse the right-hand side before adding it to the stack. We also copy the array with .slice() so that any changes we make don't inadvertently change the rule this choice was taken from. If for some reason we encounter a non-terminal that doesn't have a rule, we just treat it like a string and add it to the output.</div><div><br /></div><div>Before I go on to the rest of the engine, let's look at how getChoice works:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> getChoice<span style="color: #808030;">(</span>key<span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> preValues<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// If there's no rule for this key or no rhs, give up.</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span><span style="color: #808030;">!</span>rules<span style="color: #808030;">[</span>key<span style="color: #808030;">]</span> <span style="color: #808030;">||</span> <span style="color: #808030;">!</span> rules<span style="color: #808030;">[</span>key<span style="color: #808030;">]</span><span style="color: #808030;">.</span>rhs<span style="color: #808030;">)</span> <span style="color: maroon; font-weight: bold;">return</span> <span style="color: #0f4d75;">null</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> selectFromRHS<span style="color: #808030;">(</span>rules<span style="color: #808030;">[</span>key<span style="color: #808030;">]</span><span style="color: #808030;">.</span>rhs<span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> preValues<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
</pre></div><div>getChoice itself is just a front-end to selectFromRHS that checks to see that a rule actually exists for this non-terminal. selectFromRHS is more complex:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> selectFromRHS <span style="color: #808030;">(</span>rhs<span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> otValues<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// If there's only one choice, return that.</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>rhs<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span> <span style="color: #808030;">==</span> <span style="color: #008c00;">1</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">return</span> rhs<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">.</span>elements<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Otherwise we need to iterate through the possible choices</span>
<span style="color: dimgrey;">// and add up all the weights to decide which to pick.</span>
<span style="color: maroon; font-weight: bold;">let</span> weights <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> totalWeight <span style="color: #808030;">=</span> <span style="color: #008c00;">0</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">for</span> <span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> choice of rhs<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> weight<span style="color: purple;">;</span>
<span style="color: dimgrey;">// Simplification, weight might just be a string or a number</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">typeof</span> choice<span style="color: #808030;">.</span>weight <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">string</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
weight <span style="color: #808030;">=</span> <span style="color: maroon; font-weight: bold;">parseFloat</span><span style="color: #808030;">(</span>executeRules<span style="color: #808030;">(</span>choice<span style="color: #808030;">.</span>weight<span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> otValues<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// If weight isn't a number</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>weight <span style="color: #808030;">==</span> <span style="color: #797997;">NaN</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Weight is NaN: </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>weight<span style="color: #808030;">)</span><span style="color: purple;">;</span>
weight <span style="color: #808030;">=</span> <span style="color: #008c00;">1</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{ ...</span></pre></div><div>To start, for the common case where there's only one choice for the right-hand side of a rule, we return that choice. Otherwise we iterate through all the choices, evaluating the weight for each choice and keeping track of the total as we go. The weight might just be a string like “1" and in that case we can simply turn it into a number. But the value of the weight might itself be the right-hand side of a rule:</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Weight is itself a RHS, so we need to select from it and then</span>
<span style="color: dimgrey;">// expand the selection</span>
<span style="color: maroon; font-weight: bold;">const</span> select <span style="color: #808030;">=</span> selectFromRHS<span style="color: #808030;">(</span>choice<span style="color: #808030;">.</span>weight<span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> otValues<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>select<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> expanded <span style="color: #808030;">=</span> executeRules<span style="color: #808030;">(</span>select<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">slice</span><span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>reverse<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> otValues<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">typeof</span> expanded <span style="color: #808030;">===</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">string</span><span style="color: maroon;">'</span> <span style="color: #808030;">||</span> expanded <span style="color: maroon; font-weight: bold;">instanceof</span> <span style="color: #797997;">String</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// The string should parse to a number</span>
weight <span style="color: #808030;">=</span> <span style="color: maroon; font-weight: bold;">parseFloat</span><span style="color: #808030;">(</span>expanded<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// If weight isn't a number</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>weight <span style="color: #808030;">==</span> <span style="color: #797997;">NaN</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Weight evaluated to NaN: </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>choice<span style="color: #808030;">.</span>weight<span style="color: #808030;">)</span><span style="color: purple;">;</span>
weight <span style="color: #808030;">=</span> <span style="color: #008c00;">1</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Expansion did not return a string for </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>choice<span style="color: #808030;">.</span>weight<span style="color: #808030;">)</span><span style="color: purple;">;</span>
weight <span style="color: #808030;">=</span> <span style="color: #008c00;">1</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// selectFromRHS failed</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Unable to select from </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>choice<span style="color: #808030;">.</span>weight<span style="color: #808030;">)</span><span style="color: purple;">;</span>
weight <span style="color: #808030;">=</span> <span style="color: #008c00;">1</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></div><div>In that case we need to recursively select one of the choices, execute the choice, and then turn it into a number. If any of those steps goes wrong, we just set the weight to 1 and continue on.</div><div><br /></div><div>In any case, once we have a number for the weight of this choice, we save it and add it to the total:</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// Save weight so that we don't evaluate it again during selection.</span>
weights<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span>weight<span style="color: #808030;">)</span><span style="color: purple;">;</span>
totalWeight <span style="color: #808030;">+=</span> weight<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></div><div>Now that we have all the weights and the total of the weights, we can randomly select one of the choices based upon the weights and return that:</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// Now figure out our selection.</span>
<span style="color: maroon; font-weight: bold;">let</span> selection <span style="color: #808030;">=</span> Utils<span style="color: #808030;">.</span>rand<span style="color: #808030;">(</span>totalWeight<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">for</span> <span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> i<span style="color: #808030;">=</span><span style="color: #008c00;">0</span><span style="color: purple;">;</span>i<span style="color: #808030;"><</span>weights<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span><span style="color: purple;">;</span>i<span style="color: #808030;">++</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
selection <span style="color: #808030;">-</span><span style="color: #808030;">=</span> weights<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>selection <span style="color: #808030;"><=</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span> <span style="color: maroon; font-weight: bold;">return</span> rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>elements<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Something likely went wrong</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Fell out of selection loop in getChoice</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> rhs<span style="color: #808030;">[</span>rhs<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span><span style="color: #808030;">-</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">.</span>elements<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></div><div>If for some reason that didn't work, we'll return the last choice.</div><div><br /></div><div>Now back to the main execution loop. We've covered how non-terminals are evaluated. Now we'll cover embedded Javascript:</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// If it's embedded Javascript, then:</span>
<span style="color: dimgrey;">// Recursively expand the element</span>
<span style="color: dimgrey;">// Execute the expanded element in Javascript</span>
<span style="color: dimgrey;">// Add the return value to the end of the output</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>current<span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">jscript</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// The value of the jscript is a right-hand side, so make</span>
<span style="color: dimgrey;">// a choice from there.</span>
<span style="color: maroon; font-weight: bold;">let</span> choice <span style="color: #808030;">=</span> selectFromRHS<span style="color: #808030;">(</span>current<span style="color: #808030;">.</span>value<span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> otValues<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Copy the value before reversing it onto the stack.</span>
<span style="color: dimgrey;">// Reuse otValues so we continue to use the one-time values.</span>
<span style="color: maroon; font-weight: bold;">const</span> expanded <span style="color: #808030;">=</span> executeRules<span style="color: #808030;">(</span>choice<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">slice</span><span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>reverse<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> context<span style="color: #808030;">,</span> otValues<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">typeof</span> expanded <span style="color: #808030;">===</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">string</span><span style="color: maroon;">'</span> <span style="color: #808030;">||</span> expanded <span style="color: maroon; font-weight: bold;">instanceof</span> <span style="color: #797997;">String</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Execute in the provided context.</span>
<span style="color: maroon; font-weight: bold;">const</span> execVal <span style="color: #808030;">=</span> <span style="color: #797997;">String</span><span style="color: #808030;">(</span>context<span style="color: #808030;">(</span>expanded<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">typeof</span> execVal <span style="color: #808030;">===</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">string</span><span style="color: maroon;">'</span> <span style="color: #808030;">||</span> execVal <span style="color: maroon; font-weight: bold;">instanceof</span> <span style="color: #797997;">String</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
output <span style="color: #808030;">=</span> output<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>execVal<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Unable to coerce context value to String for </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>expanded<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Expansion did not return a string for </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>current<span style="color: #808030;">.</span>value<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">continue</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></div><div>The value of the embedded Javascript is a right-hand side, so this looks much like the handling above for a weight. We select a choice from the right-hand side, expand it to a string, then we evaluate it using the provided context function. Finally we force the result of the evaluation to be a string and append it to the output.</div><div><br /></div><div>The last possibility is a string element:</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// If it's a string, add it to the end of the output.</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>current<span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">string</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
output <span style="color: #808030;">=</span> output<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>current<span style="color: #808030;">.</span>value<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">continue</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></div><div>In that case we simply add the string to the output.</div><div><br /></div><div>If something falls through these cases (although nothing should!) then we'll simply treat it as a string and add it to the output.</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// Else complain but treat as a string</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Unknown element type "</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>current<span style="color: #808030;">.</span>type<span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">" in executeRules.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
output <span style="color: #808030;">=</span> output<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span><span style="color: #797997;">String</span><span style="color: #808030;">(</span>current<span style="color: #808030;">.</span>value<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>count <span style="color: #808030;">>=</span> <span style="color: #008c00;">1000</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">executeRules hit the iteration limit.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> output<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></div><div>That's the end of the iteration loop. When the stack has been emptied (or we've reached the 1000 iterations limit), we return the output.</div><div><br /></div><div>Here's a simple rule set that tests all the capabilities:</div><pre><$pi> => `Math.PI`;
<start> => <first> [1] | <second> [`99+99`];
<first> => I am surprised this was selected!;
<second> => The value of pi is <$pi>;
</pre><div>Running this almost always produces:</div><div><pre>The value of pi is 3.141592653589793</pre></div>If you run this in the code, you'll have to look for this output in the Javascript console. Speaking of which, the code to run this looks like this:<div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> <span style="color: maroon; font-weight: bold;">test</span><span style="color: #808030;">(</span>svg<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Create a Parser object from our grammar.</span>
<span style="color: maroon; font-weight: bold;">const</span> parser <span style="color: #808030;">=</span> <span style="color: maroon; font-weight: bold;">new</span> nearley<span style="color: #808030;">.</span>Parser<span style="color: #808030;">(</span>nearley<span style="color: #808030;">.</span>Grammar<span style="color: #808030;">.</span>fromCompiled<span style="color: #808030;">(</span>Lodestone<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Parse text</span>
parser<span style="color: #808030;">.</span>feed<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;"><$pi> => `Math.PI`;</span><span style="color: #0f69ff;">\n</span><span style="color: #0000e6;">\</span>
<span style="color: #0000e6;"> <start> => <first> [1] | <second> [`99+99`];</span><span style="color: #0f69ff;">\n</span><span style="color: #0000e6;">\</span>
<span style="color: #0000e6;"> <first> => I am surprised this was selected!;</span><span style="color: #0f69ff;">\n</span><span style="color: #0000e6;">\</span>
<span style="color: #0000e6;"> <second> => The value of pi is <$pi>;</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Parse is </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>parser<span style="color: #808030;">.</span>results<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> rules <span style="color: #808030;">=</span> processRules<span style="color: #808030;">(</span>parser<span style="color: #808030;">.</span>results<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">rules is </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>rules<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> result <span style="color: #808030;">=</span> executeRules<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;"><start></span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> rules<span style="color: #808030;">,</span> s <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon; font-weight: bold;">eval</span><span style="color: #808030;">(</span>s<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">result is </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>result<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
</pre></div><div>It's rather irritating to have to escape all the newlines to enter a rule set, so next time I'll work on some code to read in the rules from a separate rules file.</div><div><br /></div><div><div>Lodestone is now complete, and you can download it in the Part 13 branch from the <a href="https://github.com/srt19170/Procedural-Map-Compasses" target="_blank">Procedural Map Compasses repository </a> on Github to access all of this code. <span><a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">Part 1</a> 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 13 directory, and then select the test.html link. Obviously you can browse the code and other files, and make changes of your own. </span><span>You can also try out this code on the web at </span><a href="https://dragonsabound-part13.netlify.app/test.html">https://dragonsabound-part13.netlify.app/test.html</a><span> although you'll only be able to run the code, not modify it.</span><span style="background-color: white; white-space: pre;"> </span></div><div><span style="background-color: white; white-space: pre;"><br /></span></div><div><br /></div><div><span><span><b>Suggestions to Explore</b></span></span><div><ul><li><span>It's not currently possible to use a single quote in a Lodestone rule; the parser will always expect that to be the start of a quote string. Likewise for a < sign. How would you modify the Lodestone parser to allow those characters? </span></li></ul></div></div><div><br /></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-81210912049729819232022-02-10T19:47:00.000-05:002022-02-10T19:47:35.087-05:00Map Compasses (Part 12): An Excursion into Tool Building<p>Welcome back to my blog series on implementing procedurally-generated map compasses! After implementing the Compass Design Language (CDL) that will be used to draw the compasses, and ruminating last time on procedural generation, I'm now ready to move to implementation of the procedural generation part of map compasses. The procedural generation's output will be descriptions of compasses in CDL, which I'll then pass to my CDL interpreter to draw:</p>
<div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEif7xi2bAyv_z2-qyrOxKXIfqHeh-F67eF147iE-y8WrFXMIgbpm8ZeBazUQQa23UPycP7esGU8a24uYrJb6aJfg83zSyoiyE4jWBPx46226BZWO1-g3ypGwf9z_2fvsLD3INQyX1saUnj7/s600/Image1.png"><img alt="" border="0" data-original-height="300" data-original-width="600" height="160" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEif7xi2bAyv_z2-qyrOxKXIfqHeh-F67eF147iE-y8WrFXMIgbpm8ZeBazUQQa23UPycP7esGU8a24uYrJb6aJfg83zSyoiyE4jWBPx46226BZWO1-g3ypGwf9z_2fvsLD3INQyX1saUnj7/s320/Image1.png" width="320" /></a></div>
<p>For the generation of the CDL I will be using a generative grammar. Generative grammars look a lot like the grammar I wrote to parse CDL, but run “backwards" so that they generate output rather than parse input. You can think of generative grammars as replacement or expansion engines. Typically you start with a single token that represents what you want to generate, and the grammar expands that into the final product. For example, support you start with an input of <name> and have this set of rules:</p>
<blockquote>
<pre><name> => <lost> <coast>
<coast> => coast | shore | banks
<lost> => lost | forgotten | accursed</pre>
</blockquote>
<p>The grammar tries to find an expansion for “<name>" and finding the first rule, replaces <name> with “<lost> <coast>." The process then repeats. The vertical pipe symbol | in rules indicates random choices. So when the grammar tries to expand <lost> using the third rule, it chooses randomly from the three choices and (let's say) replaces <lost> with “forgotten" and similarly replaces <coast> with “banks" and at that point no further expansions are possible so the results is “forgotten banks."</p>
<p><b style="font-variant-caps: small-caps;">Dragons Abound</b> uses generative grammars in several places, such as generating place names as suggested above. I've written quite a bit about generative grammars previously, initially when I wrote about <a href="https://heredragonsabound.blogspot.com/2018/06/the-naming-of-places-part-4-using-tool.html" target="_blank">generating place names</a> and then in more detail later when I wrote about <a href="https://heredragonsabound.blogspot.com/2019/03/map-borders-part-8.html" target="_blank">generating map borders</a>. If you're not familiar with generative grammars those posts will give you a more thorough introduction.</p>
<p>In <b style="font-variant-caps: small-caps;">Dragons Abound</b> I've been using a tool called <a href="https://rednoise.org/rita/" target="_blank">RiTa</a> for generative grammars. I selected RiTa because it had several features I needed and I found it easy to add some that were missing. Since I've started using RiTa, version 2.0 has been released with some major new features. (Including some that I added to my version.) It's probably much better, but I have a considerable investment in my modified version, so I'm going to use that for map compasses.</p>
<p><b>UPDATE:</b> Or at least that was my intention. I've actually already written the next four blog posts, in which I develop 50 rules to generate two layer compasses like these:</p>
<div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNUHP17Zbk38AROv4xAFSegysHhlbXjODh5_soduww0iRBJxVtXiXhdQkyvvRSgLV2eR35cWpdCSzsGG_I9FL12YodlxTzQRFuhVdn9Y6cjTo5XmuVaajh-Up97xrzqWLeBR1TMVF3jaa_/s600/Image47.png"><img alt="" border="0" data-original-height="335" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNUHP17Zbk38AROv4xAFSegysHhlbXjODh5_soduww0iRBJxVtXiXhdQkyvvRSgLV2eR35cWpdCSzsGG_I9FL12YodlxTzQRFuhVdn9Y6cjTo5XmuVaajh-Up97xrzqWLeBR1TMVF3jaa_/s16000/Image47.png" /></a></div>
<p>But as I progressed along I found that I needed to add some additional features to my version of RiTa. First I wrote a parser that would take rules in a convenient text form (like the examples above) and turn them into RiTa rules. Then I added some capabilities that required rewriting a good chunk of the RiTa rules engine. But these fixes feel like they're “bolted on" and are just trying to compensate for a tool that isn't really fit for my purpose. I began to wonder if I wouldn't be better off re-writing the RiTa engine from scratch. I could incorporate my text-form parser and the modifications I've made as well as some additional features to make life a bit easier. In the end it seemed like a fun and intriguing side-project so <a href="https://heredragonsabound.blogspot.com/2020/02/the-forever-project.html" target="_blank">in keeping with my philosophy</a>, I'm going to make a bit of a right-turn here and write a procedural generation tool. Hopefully you'll find it fun and interesting as well.</p>
<div>Oh, and I need a name for the tool. Since I'm using it for procedural generation of map compasses, I'll call it Lodestone.</div><div><br /></div><div>(Incidentally, this whole “I'm going to build my own tool because the available tools aren't <i><u>exactly</u></i> right for me" is a nasty trap that has killed more than one project. If this were anything but a Forever Project I would not be doing this! But it is and so I am. And you'll end up with the code to a tool you can repurpose, so maybe it's a good thing from your viewpoint! But as they say on TV, don't try this at home.)</div><div><br />
<div>The first step of this sub-project will be to define the format of the procedural generation rules and build a parser for them. A basic rule looks like this:</div>
<div>
<pre><start> => <first> <second> <third>;
</pre>
</div>
<div>It has a single non-terminal enclosed in angle brackets on the left-hand side of the rule, an arrow operator =>, and then a sequence of non-terminals (or other elements) on the right-hand side of the rule, terminated by a semi-colon. </div><div><br /></div>
<div>There are a couple of difference here from RiTa and other tools. First, I'll require terminals to be enclosed in angle brackets. RiTa allows any sort of non-terminal, but delineating them this way makes parsing easier and (in my opinion) the rules easier to read. At any rate, I always write non-terminals this way so I'll require that in my tool. Second, I'll terminate a rule explicitly with a semi-colon. Rules tend to get long and complicated, and this will make it easy for me to split rules across lines and format them for easier reading.</div><div><br /></div>
<div>A special kind of non-terminal has a name that starts with a $. This indicates a one-time terminal:</div>
<div>
<pre><$otr> => <fourth> <fifth>;
</pre>
</div>
<div>Normally a non-terminal is evaluated every time it is encountered, and if it can take on multiple values, they'll be randomly chosen each time. But a one-time terminal is only evaluated once, and then has that same value whenever it is used.</div><div><br /></div>
<div>The right-hand side of the rule is a sequence of elements. In addition to non-terminals, the elements can include strings:</div>
<div>
<pre><first> => <second> and a string;</pre>
</div>
<div>Note that the string is not quoted. Lodestone will generally do what you expect in picking out the string, but you can also use a quoted string if necessary (e.g., you want the string to include something that Lodestone might interpret as a non-terminal):</div>
<div>
<pre><first> => <second> 'and a quoted <string>';</pre>
</div>
<div>Note the use of single quotes. Double quotes can be used without any special meaning.</div>
<div><br /></div><div>An element can also be a Javascript expression enclosed in backticks:</div>
<div>
<pre><second> => `Math.PI/2`;
</pre>
</div>
<div>The Javascript expression is evaluated and the result goes into the output. Importantly, the Javascript expression can contain other Lodestone elements like non-terminals:</div>
<div>
<pre><second> => `Math.PI/<numSides>`;
</pre>
</div>
<div>And this does what you'd expect -- divides PI by the value of the non-terminal <$divisor>. You can also set the values of one-time non-terminals in Javascript:</div>
<div>
<pre><second> => `<$angle> = Math.PI/<numSides>`;
</pre>
</div>
<div>You cannot do this with a regular non-terminal; since they're evaluated every time they are referenced, giving them a value won't do anything.<br /><br />If you want to evaluate multiple statements in the embedded Javascript you can do that by separating the statements with a semi-colon or the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator" target="_blank">comma operator</a>:</div>
<div>
<pre><second> => `<$divisor> = <numSides>/2; <$angle> = Math.PI/<$divisor>`;</pre>
</div>
<div>The value of the embedded Javascript will be the value of the last statement.</div>
<div>The right-hand side of a rule can also have multiple choices separated by the vertical bar character:</div>
<div>
<pre><fourth> => 1 | 2 | 3 | 4;
</pre>
</div>
<div>In this case, the value of the rule is one of the choices selected at random. Every time <fourth> is used, this rule could pick a different number. But if the right-hand side was a one-time terminal like this:</div>
<div>
<pre><$otr> => 1 | 2 | 3 | 4;
</pre>
</div>
<div>Then a random choice would be selected only the first time, and <$otr> would have that value every time it was referenced. (Although as noted above, you can change this value in Javascript.)</div><div><br /></div>
<div>Normally when a rule has multiple choices, they're all equally likely. To change that, a weight can provided in square brackets, like this:</div>
<div>
<pre><fifth> => cat [10] | dog;
</pre>
</div>
<div>Any choice that doesn't have a weight is given the default weight of 1. So in this case, the “cat" choice will be selected ten times as often as the “dog" choice. As with embedded Javascript, the value of the weight can be any Lodestone expression, so you could have these sorts of weights:</div>
<div>
<pre><fifth> => cat [<catweight>] | dog [`<catweight>/2`];</pre>
</div>
<div>Lastly, a comment is any line that starts with a #. The entire line will be ignored.</div>
<div>
<pre># Ignored completely
</pre>
</div>
<div>A comment does not have to end with a semi-colon.</div><div><br /></div>
<div>One last note: On the right-hand side of a Lodestone rule, whitespace is significant. For example:</div>
<div>
<pre><start> => <first> <second>;
<first> => cat;
<second> => dog;
</pre>
</div>
<div>These rules produce the string “cat dog" because the space between <first> and <second> is significant. On the other hand, these rules:</div>
<div>
<pre><start> => <first><second>;
<first> => cat;
<second> => dog;
</pre>
</div>
<div>Produce the string “catdog". </div><div><br /></div>
<div>Whitespace is significant, but all whitespace outside of a quoted string gets reduced down to a single space character. So you could write the first rules as:</div>
<div>
<pre><start> => <first>
<second>;
<first> => cat;
<second> => dog;
</pre>
</div>
<div>And the output would still be “cat dog". To get more whitespace, use a quoted string. But be careful! This rule with three spaces in the quoted string:</div>
<div>
<pre><start> => <first> ' ' <second>;
</pre>
</div>
<div>will actually produce five spaces between cat and dog, because there is whitespace on either side of the quoted string. This rule:</div>
<div>
<pre><start> => <first> ' ' <second>;
</pre>
</div>
<div>Would result in the expected three spaces between cat and dog.</div><div><br /></div>
<div>And that's it for the definition of the rule language! So now I'll work on writing a parser. </div><div><br /></div>
<div>Just as I did for CDL, I'll use <a href="https://nearley.js.org/" target="_blank">Nearley</a> for this parser. I'll also use a <a href="https://en.wikipedia.org/wiki/Lexical_analysis" target="_blank">lexer</a>. A lexer breaks a stream of characters into a stream of tokens, so that inside Nearley we won't have to do so much character-by-character processing. With careful definition of tokens, using a lexer can make a parser much easier to write and less likely to be ambiguous. (And in fact, I've defined the language with this in mind.) The Nearley guys recommend <a href="https://github.com/no-context/moo" target="_blank">Moo</a>, which looks straightforward, so I'll use that.</div><div><br /></div>
<div>Much of the work of parsing will actually be done in the lexer. Moo uses regular expressions to define tokens, so the main work here is to craft regular expressions for all of the Lodestone tokens. I'll start off with a simple example, the token for the arrow operator:</div>
<div>
<pre> //
// The arrow that separates lhs and rhs
//
arrow: /=>/,
</pre>
</div>
<div>Moo tokens are defined in a Javascript object. The key name is the type of the token, and the value is a regular expression. I'm not going to attempt to explain <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet" target="_blank">Javascript regular expressions</a> here, but they're defined inside slashes, and in this case the regular expression just matches the “=>" characters.</div>
<div><br />A slightly more complex token is the end-of-rule token:<br />
<pre> //
// End of rule
//
eor: /;\s*\r\n/,
</pre>
</div>
<div>There are two things to note here. First, the “\s" is a regular expression shorthand for “any whitespace character." Second, the asterisk after anything means “match zero or more" of the preceding thing. \r and \n match a carriage return and a newline, which is how the end of a line is defined on Windows. So in this case, the regular expression matches a semi-colon followed by any amount of whitespace followed by the end of the line.</div><div><br /></div>
<div>Here's the regular expression for a one-time non-terminal:</div>
<div>
<pre> //
// One-time non-terminal in the form <$test>
//
otnterm: /<\$[^>]+>/,
</pre>
There are a couple of new things here. First of all, I have to put a backslash before the $ in this pattern because the $ has a special meaning in regular expressions (it means the end of a line) and so if I want to match an actual $ character, I have to escape it with a backslash to let Javascript know that I don't mean the end of line. After that is a pattern in square brackets. The square brackets are called a character class, and match any character inside the brackets. But the caret (^) at the beginning of the list of characters negates the list. So [^>] means “any character except a right angle bracket." The plus immediately following this is like the asterisk but means “match one or more times." And finally, there is a right angle bracket. So in total, this pattern says “match a string that starts with a left angle bracket, followed by a dollar sign, followed by any number of non-right angle brackets, and ends with a right angle bracket."</div><div><br /></div>
<div>One more example that introduces a useful Moo feature:</div>
<div>
<pre> //
// Embedded Javascript enclosed in backticks
//
jscript: {match:/`[^`]+`/, value: s => s.slice(1, -1)},
</pre>
</div>
<div>Instead of just a regular expression, the value of a token type can also be an object with various parts. The “match" key is the regular expression. (This one matches everything between two backticks.) The “value" key is a function that will process the matched text to produce a value. In this case, the value function slices off the first and last characters of the matched text. (This removes the backticks.) </div><div><br /></div>
<div>Here's the complete code for the lexer:</div><div><pre><pre style="background: rgb(255, 255, 255);"><span style="color: dimgrey;">//</span>
<span style="color: dimgrey;">// Order matters in the lexer. Earlier</span>
<span style="color: dimgrey;">// rules take precedence.</span>
<span style="color: dimgrey;">// </span>
<span style="color: maroon; font-weight: bold;">const</span> lexer <span style="color: #808030;">=</span> moo<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">compile</span><span style="color: #808030;">(</span><span style="color: purple;">{</span>
<span style="color: dimgrey;">//</span>
<span style="color: dimgrey;">// Comment is an entire line starting with #</span>
<span style="color: dimgrey;">// </span>
comment<span style="color: purple;">:</span> <span style="color: purple;">{</span><span style="color: maroon; font-weight: bold;">match</span><span style="color: #808030;">:</span><span style="color: #0000e6;"> </span><span style="color: maroon;">/</span><span style="color: #808030;">^</span><span style="color: #797997;">\s</span><span style="color: #808030;">*</span><span style="color: #0f69ff;">\#</span><span style="color: #808030;">.</span><span style="color: #808030;">*</span><span style="color: #0f69ff;">\r</span><span style="color: #0f69ff;">\n</span><span style="color: maroon;">/</span><span style="color: #808030;">,</span> lineBreaks<span style="color: purple;">:</span> <span style="color: #0f4d75;">true</span><span style="color: purple;">}</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">//</span>
<span style="color: dimgrey;">// Or</span>
<span style="color: dimgrey;">//</span>
or<span style="color: #808030;">:</span><span style="color: #0000e6;"> </span><span style="color: maroon;">/</span><span style="color: #797997;">\s</span><span style="color: #808030;">+</span><span style="color: #0f69ff;">\|</span><span style="color: #797997;">\s</span><span style="color: #808030;">+</span><span style="color: maroon;">/</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">// </span>
<span style="color: dimgrey;">// White space, including newlines</span>
<span style="color: dimgrey;">// </span>
ws<span style="color: purple;">:</span> <span style="color: purple;">{</span> <span style="color: maroon; font-weight: bold;">match</span><span style="color: #808030;">:</span><span style="color: #0000e6;"> </span><span style="color: maroon;">/</span><span style="color: #797997;">\s</span><span style="color: #808030;">+</span><span style="color: maroon;">/</span><span style="color: #808030;">,</span> lineBreaks<span style="color: purple;">:</span> <span style="color: #0f4d75;">true</span><span style="color: #808030;">,</span> value<span style="color: purple;">:</span> s <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">'</span><span style="color: #0000e6;"> </span><span style="color: maroon;">'</span><span style="color: purple;">}</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">// </span>
<span style="color: dimgrey;">// One-time non-terminal reference in the form <@test></span>
<span style="color: dimgrey;">//</span>
otntermref<span style="color: #808030;">:</span><span style="color: #0000e6;"> </span><span style="color: maroon;">/</span><span style="color: #0000e6;"><</span><span style="color: #0f69ff;">\@</span><span style="color: #808030;">[</span><span style="color: #808030;">^</span><span style="color: #0000e6;">></span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: #0000e6;">></span><span style="color: maroon;">/</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">// </span>
<span style="color: dimgrey;">// One-time non-terminal in the form <$test></span>
<span style="color: dimgrey;">//</span>
otnterm<span style="color: #808030;">:</span><span style="color: #0000e6;"> </span><span style="color: maroon;">/</span><span style="color: #0000e6;"><</span><span style="color: #0f69ff;">\$</span><span style="color: #808030;">[</span><span style="color: #808030;">^</span><span style="color: #0000e6;">></span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: #0000e6;">></span><span style="color: maroon;">/</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">// </span>
<span style="color: dimgrey;">// Non-terminal in the form <test></span>
<span style="color: dimgrey;">//</span>
nterm<span style="color: #808030;">:</span><span style="color: #0000e6;"> </span><span style="color: maroon;">/</span><span style="color: #0000e6;"><</span><span style="color: #808030;">[</span><span style="color: #808030;">^</span><span style="color: #0f69ff;">\$</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #808030;">^</span><span style="color: #0000e6;">></span><span style="color: #808030;">]</span><span style="color: #808030;">*</span><span style="color: #0000e6;">></span><span style="color: maroon;">/</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">// </span>
<span style="color: dimgrey;">// Embedded Javascript enclosed in backticks</span>
<span style="color: dimgrey;">//</span>
jscript<span style="color: purple;">:</span> <span style="color: purple;">{</span><span style="color: maroon; font-weight: bold;">match</span><span style="color: #808030;">:</span><span style="color: maroon;">/</span><span style="color: #0000e6;">`</span><span style="color: #808030;">[</span><span style="color: #808030;">^</span><span style="color: #0000e6;">`</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: #0000e6;">`</span><span style="color: maroon;">/</span><span style="color: #808030;">,</span> value<span style="color: purple;">:</span> s <span style="color: #808030;">=</span><span style="color: #808030;">></span> s<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">slice</span><span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #008c00;">1</span><span style="color: #808030;">)</span><span style="color: purple;">}</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">//</span>
<span style="color: dimgrey;">// A weight in the form [50]</span>
<span style="color: dimgrey;">// </span>
weight<span style="color: purple;">:</span> <span style="color: purple;">{</span><span style="color: maroon; font-weight: bold;">match</span><span style="color: #808030;">:</span><span style="color: maroon;">/</span><span style="color: #0f69ff;">\[</span><span style="color: #808030;">[</span><span style="color: #808030;">^</span><span style="color: #0f69ff;">\]</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: #0f69ff;">\]</span><span style="color: maroon;">/</span><span style="color: #808030;">,</span> value<span style="color: purple;">:</span> s <span style="color: #808030;">=</span><span style="color: #808030;">></span> s<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">slice</span><span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #008c00;">1</span><span style="color: #808030;">)</span><span style="color: purple;">}</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">//</span>
<span style="color: dimgrey;">// The arrow that separates lhs and rhs</span>
<span style="color: dimgrey;">//</span>
arrow<span style="color: #808030;">:</span><span style="color: #0000e6;"> </span><span style="color: maroon;">/</span><span style="color: #0000e6;">=></span><span style="color: maroon;">/</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">//</span>
<span style="color: dimgrey;">// End of rule</span>
<span style="color: dimgrey;">//</span>
eor<span style="color: purple;">:</span> <span style="color: purple;">{</span><span style="color: maroon; font-weight: bold;">match</span><span style="color: #808030;">:</span><span style="color: maroon;">/</span><span style="color: #0000e6;">;</span><span style="color: #797997;">\s</span><span style="color: #808030;">*</span><span style="color: #0f69ff;">\r</span><span style="color: #0f69ff;">\n</span><span style="color: maroon;">/</span><span style="color: #808030;">,</span> lineBreaks<span style="color: purple;">:</span> <span style="color: #0f4d75;">true</span><span style="color: purple;">}</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">//</span>
<span style="color: dimgrey;">// A quoted string</span>
<span style="color: dimgrey;">//</span>
qstring<span style="color: purple;">:</span> <span style="color: purple;">{</span><span style="color: maroon; font-weight: bold;">match</span><span style="color: #808030;">:</span><span style="color: #0000e6;"> </span><span style="color: maroon;">/</span><span style="color: #0000e6;">'</span><span style="color: #808030;">[</span><span style="color: #808030;">^</span><span style="color: #0000e6;">'</span><span style="color: #808030;">]</span><span style="color: #808030;">*</span><span style="color: #808030;">?</span><span style="color: #0000e6;">'</span><span style="color: maroon;">/</span><span style="color: #808030;">,</span> value<span style="color: purple;">:</span> s <span style="color: #808030;">=</span><span style="color: #808030;">></span> s<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">slice</span><span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #008c00;">1</span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> type<span style="color: purple;">:</span> s <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">string</span><span style="color: maroon;">"</span><span style="color: purple;">}</span><span style="color: #808030;">,</span>
<span style="color: dimgrey;">//</span>
<span style="color: dimgrey;">// A string of characters</span>
<span style="color: dimgrey;">//</span>
string<span style="color: #808030;">:</span><span style="color: #0000e6;"> </span><span style="color: maroon;">/</span><span style="color: #808030;">[</span><span style="color: #808030;">^</span><span style="color: #0000e6;">`</span><span style="color: #0f69ff;">\'</span><span style="color: #0f69ff;">\<</span><span style="color: #0f69ff;">\[</span><span style="color: #0f69ff;">\]</span><span style="color: #0000e6;">; </span><span style="color: #0f69ff;">\t</span><span style="color: #0f69ff;">\n</span><span style="color: #0f69ff;">\r</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: maroon;">/</span><span style="color: #808030;">,</span>
<span style="color: purple;">}</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></pre>
The easiest way to use the lexer in parsing is to match against the token type. This is done by using a % and the token name. For example, in a Nearley parser rule, “%comment" would match a token with the comment type. Here's a simplified version of the Lodestone parser:</div>
<div>
<pre>rule -> ws:? (%nterm | %otnterm) ws:? %arrow ws:? rhs ws:? %eor
rhs -> element | element %ws:? rhs
element -> %nterm | %otnterm | %string | %qstring | %jscript | %weight | %or
</pre>
</div>
<div>In Nearly, the “:?" suffix means to match zero or one. So this grammar says that a rule is possibly some initial whitespace, followed by a non-terminal or one-time non-terminal, the arrow, and then right-hand side of the rule followed by the end-of-rule. (With optional whitespace in various spots.) The right-hand side is then either a single element or an element followed by optional whitespace and a right-hand-side. This is a typical recursive grammar rule to describe one or more elements possibly separated by whitespace. Finally, an element is one of the tokens defined in the lexer -- the non-terminals, strings, quoted strings, embedded Javascript, weights, or the vertical bar.</div><div><br /></div>
<div>The actual grammar is slightly more complex because it includes some post-processing so that the output of the parser is similar to the output of the CDL parser -- an array of structures with an opcode and a value. Here's an example of a simple rule and the result of parsing the rule:</div>
<div>
<pre><$test> => <rule>;</pre><pre>{
op: 'rule',
lhs: {type: 'otnterm',value: '<$test>'},
rhs: [{type: 'nterm', value: '<rule>'}]
}
</pre>
</div>
<div>The rule is turned into a Javascript structure with an opcode of 'rule' to indicate that it is a rule, and then structures or lists representing the left and right-hand sides. The objects with type and value are coming straight from the lexer. The parse of a whole grammar is simply an array of these rule structures.</div><div><br /></div>
<div>When the parse is complete there is still some additional processing required to get the rules ready for use in a rules engine. </div><div><br /></div>
<div>First, any Javascript and weight elements need to be further parsed. I'll do Javascript first since that's the simpler case. After the initial parsing above, the value of a Javascript or weight element is a string:</div>
<div>
<pre>{type: 'jscript', value: '5'}
</pre>
</div>
<div>That's okay if the value is just a number as above. But the value of a Javascript can be anything that could be a right-hand side, as in this example:</div>
</div>
<div>
<pre>{type: 'jscript', value: '<$totalweight>/3'}</pre>
</div>
<div>Here the value of the Javascript element contains a non-terminal. Since the value of a Javascript element can be anything that would be on the right-hand side of a rule, I'll use the same rules to parse the value, by calling the parser again. This shows an example rule and the result of parsing the rule and the parsing the value of the Javascript element:</div>
<div>
<pre><$test> => <rule> `50`;</pre><pre>{
type: 'rule'
lhs: {type: 'otnterm', value: '<$test>'...}
rhs:
{type: 'nterm', value: '<rule>', text: '<rule>', offset: 32, toString: …}
{type: 'string', value: ' '}
{type: "jscript" value: <span style="background-color: #fff2cc;">{type: 'string', value: '50' ...}</span>
} </pre>
</div>
<div>I've highlighted the relevant portion. The value of the jscript element has been parsed to a string element with the value 50. Here's what an initial version of the Javaascript processing looks like:</div>
<div>
<pre><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> processJscript<span style="color: #808030;">(</span>rhs<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// For each element in the rhs of the rule</span>
<span style="color: maroon; font-weight: bold;">for</span> <span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> i<span style="color: #808030;">=</span><span style="color: #008c00;">0</span><span style="color: purple;">;</span>i<span style="color: #808030;"><</span>rhs<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span><span style="color: purple;">;</span>i<span style="color: #808030;">++</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">jscript</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Each time we parse we need to create a new parser, otherwise</span>
<span style="color: dimgrey;">// it just continues on from where it ended.</span>
<span style="color: dimgrey;">// const parser = new nearley.Parser(Lodestone,'rhs');</span>
<span style="color: maroon; font-weight: bold;">const</span> parser <span style="color: #808030;">=</span> <span style="color: maroon; font-weight: bold;">new</span> nearley<span style="color: #808030;">.</span>Parser<span style="color: #808030;">(</span>nearley<span style="color: #808030;">.</span>Grammar<span style="color: #808030;">.</span>fromCompiled<span style="color: #808030;">(</span>Lodestone<span style="color: #808030;">)</span><span style="color: #808030;">,</span><span style="color: maroon;">'</span><span style="color: #0000e6;">rhs</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Run the value through the initial parser</span>
parser<span style="color: #808030;">.</span>feed<span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: #808030;">)</span><span style="color: purple;">;</span>
rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value <span style="color: #808030;">=</span> parser<span style="color: #808030;">.</span>results<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> rhs<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
</pre></pre></div><div>As you can see, I'm feeding the value of the Javascript element into a parser with the 'rhs' starting point.</div><div><br /></div><div>However, there's some legitimate Javascript that the parser will mistake for Lodestone. For example, in Javascript [50] would be an array subscript, but the parser will mistake it for a rule weight. There's a similar problem with the pipe symbol. So after I've run the parser, I'll add a step turn those back into strings:</div><div><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="background-color: white;"> </span><span style="background-color: white; color: dimgrey;">// </span><span style="background: rgb(255, 255, 255);">Fix or and weight tokens</span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">for</span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white; color: maroon; font-weight: bold;">let</span><span style="background-color: white;"> j</span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white; color: #008c00;">0</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">j</span><span style="background-color: white; color: #808030;"><</span><span style="background-color: white;">rhs</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white;">i</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">value</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white; color: maroon; font-weight: bold;">length</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">j</span><span style="background-color: white; color: #808030;">++</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> </span><span style="background-color: white; color: purple;">{</span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">let</span><span style="background-color: white;"> token </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white;"> rhs</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white;">i</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">value</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white;">j</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">if</span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">token</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">type </span><span style="background-color: white; color: #808030;">==</span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #0000e6;">or</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">||</span><span style="background-color: white;"> token</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">type </span><span style="background-color: white; color: #808030;">==</span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #0000e6;">weight</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> </span><span style="background-color: white; color: purple;">{</span><span style="background-color: white;">
token</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">type </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #0000e6;">string</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
token</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">value </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white;"> token</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">text</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: purple;">}</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: purple;">}</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span></pre></div><div>And now the value of a Javascript element will include things like “<foo>" and “<$foo>" which I want to be interpreted as Lodestone, but won't treat something like “[50]" as Lodestone.</div><div><br /></div><div>The processing of weight elements looks much the same, with one additional wrinkle. Weight elements can contain embedded Javascript. When that happens, I need to call processJscript to take care of the embedded Javascript. Since processJscript is expecting to get a list of elements to process, I can simply give it the result of the parse:</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// Run the value through the initial parser</span>
parser<span style="color: #808030;">.</span>feed<span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Now process any Jscript in the RHS</span>
rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value <span style="color: #808030;">=</span> processJscript<span style="color: #808030;">(</span>parser<span style="color: #808030;">.</span>results<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></div><div>(At this point, you may be wondering why I didn't take care of properly parsing embedded Javascript in the original lodestone.ne parser. The answer is that (1) it would have made the parser much more complex and difficult to understand, and (2) creating a parser that allows Javascript within weights would have been tricky. I thought it was easier to understand and more straightforward to handle it as I have shown above. But see the Suggestions below if you want to tackle this!)</div><div><br /></div>
<div>The next processing is a bit of bookkeeping. When two string elements are next to each other, they can be merged into a single element to simplify the rule.</div><div><pre><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> processStrings<span style="color: #808030;">(</span>rhs<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> newRHS <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> currentString <span style="color: #808030;">=</span> <span style="color: #0f4d75;">null</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> i<span style="color: #808030;">=</span><span style="color: #008c00;">0</span><span style="color: purple;">;</span>i<span style="color: #808030;"><</span>rhs<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span><span style="color: purple;">;</span>i<span style="color: #808030;">++</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Current element is not a string</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>type <span style="color: #808030;">!=</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">string</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// If we've been building up a string</span>
<span style="color: dimgrey;">// element, release it.</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>currentString <span style="color: #808030;">!=</span> <span style="color: #0f4d75;">null</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
newRHS<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span>currentString<span style="color: #808030;">)</span><span style="color: purple;">;</span>
currentString <span style="color: #808030;">=</span> <span style="color: #0f4d75;">null</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// If this is a Jscript or weight it needs</span>
<span style="color: dimgrey;">// to be recursively processed.</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">jscript</span><span style="color: maroon;">"</span> <span style="color: #808030;">||</span> rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">weight</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value <span style="color: #808030;">=</span> processStrings<span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// And save this element</span>
newRHS<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>currentString <span style="color: #808030;">==</span> <span style="color: #0f4d75;">null</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// If not building a string, start one.</span>
currentString <span style="color: #808030;">=</span> rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Add this string to the currentString.</span>
currentString<span style="color: #808030;">.</span>value <span style="color: #808030;">=</span>
currentString<span style="color: #808030;">.</span>value<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>currentString <span style="color: #808030;">!=</span> <span style="color: #0f4d75;">null</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
newRHS<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span>currentString<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span>
<span style="color: maroon; font-weight: bold;">return</span> newRHS<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre>
</div>
<div>The basic idea here is to walk through the right-hand side elements, pushing them into newRHS. But when a string element is encountered, it is set aside (in currentString) without pushing into newRHS. If it is followed by other string elements, they're concatenated one by one onto currentString. When a non-string element is encountered (or when the end of the right-hand side is encountered) whatever has been gathered up in currentString is added to the newRHS. One thing to watch out for in implementing this pattern is the final if that releases currentString at the end of the right-hand side. It's very easy to forget that. The only other tricky bit is the recursion in the middle of the function. As with the previous processing, since Javascript and weight elements contain right-hand sides, we must recurse down into those values and process them as well.</div><div><br /></div>
<div>There's one more bit of processing to do before building the rule dictionary. That has to do with the possible random choices on the right-hand side of a rule. A rule like this:</div>
<div>
<pre><start> => <a> | <b> | <c> [50] | <d>;</pre>
</div>
<div>is executed by selecting one of the right-hand side choices at random, based upon the weights. One thing that may not be immediately apparent is that the above rule is the same as these rules:</div>
<div>
<pre><start> => <a> | <b>;
<start> => <c> [50];
<start> => <d>;
</pre>
</div>
<div>When the rule engine is executing, it will be useful to have all the options gathered up in one place with their associated weights. The first step of that is to break any rule that has multiple choices on the right-hand side into multiple right-hand sides.</div><div><br /></div>
<div>Processing the choices is similar in some ways to processing the strings. We walk through the elements in a right-hand side, collecting them up until we hit a vertical bar (an “or" token) or the end of the rule. The elements we've gathered so far constitute one choice for this rule, so we save them in a simple choice structure that has the elements in the choice and the weight for the choice. The result when we're done is a list of choices [{weight: 1, elements: [...]}, {weight: 1, elements: [...]}]:</div><div><pre><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> processChoices<span style="color: #808030;">(</span>rhs<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> rhsList <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> currentRHS <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> currentWeight <span style="color: #808030;">=</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">1</span><span style="color: maroon;">'</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> i<span style="color: #808030;">=</span><span style="color: #008c00;">0</span><span style="color: purple;">;</span>i<span style="color: #808030;"><</span>rhs<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span><span style="color: purple;">;</span>i<span style="color: #808030;">++</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Starting a new choice</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">or</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
rhsList<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span><span style="color: purple;">{</span>weight<span style="color: purple;">:</span> currentWeight<span style="color: #808030;">,</span> elements<span style="color: purple;">:</span> currentRHS<span style="color: purple;">}</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Reset the currentRHS</span>
currentRHS <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
currentWeight <span style="color: #808030;">=</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">1</span><span style="color: maroon;">'</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">continue</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Recursive processing of Javascript and weights</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">jscript</span><span style="color: maroon;">"</span> <span style="color: #808030;">||</span> rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">weight</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value <span style="color: #808030;">=</span> processChoices<span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Saving the weight of the current choice</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">weight</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Simplification if weight is just a string</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span> <span style="color: #808030;">==</span> <span style="color: #008c00;">1</span> <span style="color: #808030;">&&</span>
rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">.</span>elements<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span> <span style="color: #808030;">==</span> <span style="color: #008c00;">1</span> <span style="color: #808030;">&&</span>
rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">.</span>elements<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">string</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
currentWeight <span style="color: #808030;">=</span> rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">.</span>elements<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
currentWeight <span style="color: #808030;">=</span> rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>value<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">continue</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Otherwise put the element in currentRHS</span>
currentRHS<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span>rhs<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Pick up the last choice</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>currentRHS <span style="color: #808030;">!=</span> <span style="color: #808030;">[</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Put the previous choice in the rhsList</span>
rhsList<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span><span style="color: purple;">{</span>weight<span style="color: purple;">:</span> currentWeight<span style="color: #808030;">,</span> elements<span style="color: purple;">:</span> currentRHS<span style="color: purple;">}</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> rhsList<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre>
</div>
<div>There's a simplification that saves the weight as a simple string if that's possible. Otherwise, the weight is itself a right-hand side that has been processed into choices. (As is any Javascript element.)</div><div><br /></div>
<div>The last step is to store all the rules into a dictionary and to coalesce any rules that have the same non-terminal on the left-hand side.</div><div><pre><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> processRules<span style="color: #808030;">(</span>rules<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> dict <span style="color: #808030;">=</span> <span style="color: purple;">{</span><span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// For each rule in the ruleset</span>
<span style="color: maroon; font-weight: bold;">for</span> <span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> rule of rules<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>rule<span style="color: #808030;">.</span>type <span style="color: #808030;">==</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">rule</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
rule<span style="color: #808030;">.</span>rhs <span style="color: #808030;">=</span> processWeights<span style="color: #808030;">(</span>rule<span style="color: #808030;">.</span>rhs<span style="color: #808030;">)</span><span style="color: purple;">;</span>
rule<span style="color: #808030;">.</span>rhs <span style="color: #808030;">=</span> processJscript<span style="color: #808030;">(</span>rule<span style="color: #808030;">.</span>rhs<span style="color: #808030;">)</span><span style="color: purple;">;</span>
rule<span style="color: #808030;">.</span>rhs <span style="color: #808030;">=</span> processStrings<span style="color: #808030;">(</span>rule<span style="color: #808030;">.</span>rhs<span style="color: #808030;">)</span><span style="color: purple;">;</span>
rule<span style="color: #808030;">.</span>rhs <span style="color: #808030;">=</span> processChoices<span style="color: #808030;">(</span>rule<span style="color: #808030;">.</span>rhs<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Now add it to the dictionary</span>
<span style="color: maroon; font-weight: bold;">const</span> key <span style="color: #808030;">=</span> rule<span style="color: #808030;">.</span>lhs<span style="color: #808030;">.</span>value<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>key <span style="color: maroon; font-weight: bold;">in</span> dict<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Already here, so add the rhs to the existing rhs</span>
dict<span style="color: #808030;">[</span>key<span style="color: #808030;">]</span><span style="color: #808030;">.</span>rhs <span style="color: #808030;">=</span> dict<span style="color: #808030;">[</span>key<span style="color: #808030;">]</span><span style="color: #808030;">.</span>rhs<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>rule<span style="color: #808030;">.</span>rhs<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// New key; add to dict</span>
dict<span style="color: #808030;">[</span>key<span style="color: #808030;">]</span> <span style="color: #808030;">=</span> <span style="color: purple;">{</span>rhs<span style="color: purple;">:</span> rule<span style="color: #808030;">.</span>rhs<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> dict<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre>
</div>
<div>We call the four processing steps on each right-hand side, and then index the result into the dictionary based on the left-hand side. (Note that comments never get processed here, so they just silently disappear.)</div><div><br /></div>
<div>And that's it! A bit of a whirlwind, but that's the complete definition of a generative grammar tool and and an implementation of its parser in a single blog post. If some parts remain mysterious, please grab the code and play with it until you understand what is going on.</div><div><br /></div><div>Unfortunately there's not much you can do with Lodestone yet, but you can download the Part 12 branch from the <a href="https://github.com/srt19170/Procedural-Map-Compasses" target="_blank">Procedural Map Compasses repository </a> on Github to access all of this code. <span><a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">Part 1</a> 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 12 directory, and then select the test.html link. Obviously you can browse the code and other files, and make changes of your own. </span><span>You can also try out this code on the web at </span><a href="https://dragonsabound-part12.netlify.app/test.html">https://dragonsabound-part12.netlify.app/test.html</a><span> although you'll only be able to run the code, not modify it.</span><span style="background-color: white; white-space: pre;"> </span></div><div><span style="background-color: white; white-space: pre;"><br /></span></div>
<div>Next time I'll write the engine that will execute the rules in the dictionary.</div><div><br /></div>
<div><strong>Suggestions to Explore</strong>
<div>
<ul>
<li>As suggested above, try writing a more complex grammar that parses the embedded Javascript and weight elements correctly so that no post-processing is required.<br /><br /></li><li>The simplest part of the rule engine will take non-terminals and expand them into other non-terminals based upon the rules. Create a simple rule set with all non-terminals:<br />
<pre><start> => <a> <b>;
<a> => <e>;
<b> => <c>;
<c> => <d>;
</pre>
Now create an interpreter that starts with “<start>" and repeatedly expands the terminal based on the rule set. When you can't expand the first non-terminal any more, “output" it and move on to expanding the next non-terminal. Expand the engine to include strings; these just get output as soon as they are encountered.<br />
</li>
</ul>
</div>
</div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-16130224482154713482022-02-03T20:47:00.000-05:002022-02-03T20:47:10.622-05:00Map Compasses (Part 11): Philosophy of Design<p>Welcome back to my blog series on implementing procedurally-generated map compasses! So far I've implemented a good bit of Compass Design Language (CDL), a parser and an interpreter that can draw compasses like these examples:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdcfvWhoR1EYgBLvWqMHYwza4grUsdjsMNKnskNLoXUcMqDYEkiIoblJG0XX4rwPVIR1HQKJNA5GqXrGB8s6Egh9vl4CRUw6VpAm0aBrc6v1H-ygHxN2q1E1FCArR-5noPhVpH1jqzknQP/s446/tmp.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="240" data-original-width="446" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdcfvWhoR1EYgBLvWqMHYwza4grUsdjsMNKnskNLoXUcMqDYEkiIoblJG0XX4rwPVIR1HQKJNA5GqXrGB8s6Egh9vl4CRUw6VpAm0aBrc6v1H-ygHxN2q1E1FCArR-5noPhVpH1jqzknQP/s16000/tmp.png" /></a></div>I'm sure I'll have more work to do on CDL, but for now I want to start work on the procedural generation part of this project. I'll start that implementation in the next posting, but first I want to explore some topics that will help inform the process of building the procedural generation.<div><br /></div><div><div>To start with, I want to suggest some ways to think about procedural generation. In my opinion, “procedural generation" is a bit of a misnomer. It implies rote, mechanistic construction, like a passphrase generator (see, that word again!) that churns out NERVOUS RED MUSKOX and so on. But in fact when we work in procedural generation we're trying for just the opposite -- to build something that is creative and can author works that are novel and interesting. Of course, at some level computer algorithms really *are* rote and mechanical. But then again, so is an individual brain cell! Mind and creativity are phenomena that emerge from the complex interactions of many rote and mechanical processes. (Or at least so I believe.) So without going off the deep end about consciousness and creativity and so on, I want to talk about some of the mechanics that make up “individual brain cells" in procedural generation. And since these get generally more complex, I think of them as different levels of procedural generation.</div><div><br /></div><div><b>Level 0 -- Unconstrained Random Generation / Completely Constrained Generation</b></div><div><br /></div><div>I'll start at a level that definitely isn't procedural generation -- and that's a system that simply makes random choices throughout the creation process -- or a system that makes no choices. Random choice is of course a fundamental mechanism in computer gaming, and it's used constantly to pick the next monster to battle or the color of the gem you found in the chest. But it's hard to consider that any real sort of procedural generation. The converse of completely random choices is no choices at all. In this case you have a process that can only produce one object. And that object might be very good, but again it's hard to consider that procedural generation.</div><div><br /></div><div><b>Level 1 -- Constrained Random Generation</b></div><div><br /></div><div>This level of PG places simple constraints on each individual choice in the creation process. Each choice is constrained to a range that “usually" produces an acceptable result. You can imagine a space of all possible trees like this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqUL4SFXEj6ZpG3i3T4h-dfUu-x4AjRTQZOTQNjpY3cIcOsdLkob8fITVXVV3k-QNSA8FD4rn8WCxAW_hcWjW-JUuHUwIJ_2LZiIIFyTJux3doon6w0CE-AK98YYevxdkIGNAMddZvMimV/s400/Image71.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqUL4SFXEj6ZpG3i3T4h-dfUu-x4AjRTQZOTQNjpY3cIcOsdLkob8fITVXVV3k-QNSA8FD4rn8WCxAW_hcWjW-JUuHUwIJ_2LZiIIFyTJux3doon6w0CE-AK98YYevxdkIGNAMddZvMimV/s16000/Image71.png" /></a></div>The goal is to generate trees within the “good trees" space. At Level 0, you simply choose randomly anywhere in the tree space:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgE-i6vrPOavqAOcINgotf3KqGRJux1Eazp946mRxMOrlm3Dt0ZRDrmjaTbU5nbzU7A2Z7E-cZnwGcOWnWS3e3yWsx0kpDo8OSCd95_lCTLU9l2LpIz2pVhfW-k0WF1-JzdFQhQeDKb_bt6/s400/Image71.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgE-i6vrPOavqAOcINgotf3KqGRJux1Eazp946mRxMOrlm3Dt0ZRDrmjaTbU5nbzU7A2Z7E-cZnwGcOWnWS3e3yWsx0kpDo8OSCd95_lCTLU9l2LpIz2pVhfW-k0WF1-JzdFQhQeDKb_bt6/s16000/Image71.png" /></a></div>So you generate random trees within the entire space and some are good trees and many are not. To try to get a better ratio of good trees to bad trees, you can constrain your choices to an area of the space where there are more good trees than bad trees:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoRPNo5dbK1axVMiVmj7bn9ilZH3GQb3zSDuN6i4cCL5NoqX0oClGEVghFlCunJVHXWNV6FEku6Tq396GnQxCy9CZr1AkAqcqitMMccajbdfxjx9_gqzL9BMf9-1fYoT52gm1dsanjcKBX/s400/Image71.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoRPNo5dbK1axVMiVmj7bn9ilZH3GQb3zSDuN6i4cCL5NoqX0oClGEVghFlCunJVHXWNV6FEku6Tq396GnQxCy9CZr1AkAqcqitMMccajbdfxjx9_gqzL9BMf9-1fYoT52gm1dsanjcKBX/s16000/Image71.png" /></a></div>In this case, we pick X and Y in the green ranges, and then we generate trees in this area:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFPoVik3bALRY4OeBmHlwDYR7UE3Snev_oInvQobQoF55WlxHb9O01hvrAlBukTEmniz18oCbNEDhooFWRzGgzorY5fgKyLYij6xy4ysLlHJ5O0gb_8Wqx4OG51PyK6elEeHzj6mr99e4R/s400/Image71.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFPoVik3bALRY4OeBmHlwDYR7UE3Snev_oInvQobQoF55WlxHb9O01hvrAlBukTEmniz18oCbNEDhooFWRzGgzorY5fgKyLYij6xy4ysLlHJ5O0gb_8Wqx4OG51PyK6elEeHzj6mr99e4R/s16000/Image71.png" /></a></div>And now we're generating many fewer bad trees than we were before. But there are limitation to using only this level of constraints.<div><br /><div>First of all, you're almost always going to continue to generate at least some bad trees, and depending on the complexity of your problem, you may be hard-pressed to generate more good than bad. If you have a situation where you need only good trees -- for example, you're generating this on the fly in a computer game -- then you're usually forced to constrain your choices so much that you can only generate a small portion of the good tree space:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmUwSKcjaQn45wMpjpkxAa1F6pDp7rsq0vzz7t8aogAW0meqAMrfDte4h7heCS3s_mnHlNThYbr25c4sNXLWR-KfMDELJvQR2rm5ScjeBXfyqXEfEpOqaM1uZFMjhIP88GpALbeGyR-AMO/s400/Image71.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmUwSKcjaQn45wMpjpkxAa1F6pDp7rsq0vzz7t8aogAW0meqAMrfDte4h7heCS3s_mnHlNThYbr25c4sNXLWR-KfMDELJvQR2rm5ScjeBXfyqXEfEpOqaM1uZFMjhIP88GpALbeGyR-AMO/s16000/Image71.png" /></a></div>And this diagram is misleadingly simple, because in reality procedural generation of something complex (like a fantasy map) involves thousands and thousands of choices/dimensions. That makes the “good trees" space vanishingly small. (In machine learning, this is called the <a href="https://en.wikipedia.org/wiki/Curse_of_dimensionality" target="_blank">Curse of Dimensionality</a>.) Trying to stay within it by only constraining individual random choices is impossible.</div><div><br /></div><div>Even worse, if you do constrain your choices sufficient to stay in the good tree space, it's likely that you will throw away any chance you had to generate anything novel or interesting. The novel and the interesting trees are all in the edges and unknown parts of the good tree space. You've purposely limited yourself to a well-known and understood area of the good tree space, so you're not going to find any surprises there.<br /><div><br /></div><div>Second, there are some kinds of problems that can't be fixed by independent constraints. Suppose, for example, that your tree generator can choose a round tree or a conical tree shape, and can also choose leaves or needles. You can never generate both deciduous trees and fir trees, because to allow both will always create round trees with needles or conical trees with leaves. There's a dependency between the two parameters, and this means that individual constraints cannot keep you within the good tree space.</div><div><br /></div><div>Despite these limitations, individual constraints are an important first element of procedural generation. Carefully designing your parameters so that you can constrain them to maximize your “good trees" makes everything else more efficient. And understanding what are individual constraints and what aren't is key to building an effective procedural generator. One of the first questions I ask myself as I build a choice in procedural generation is “What are the basic limits of this choice?" and its companion question “Is there some way that that a choice outside those limits could be good?" Answering these questions at least informally will help you build an understanding of the good tree space.</div><div><br /></div><div>The goal at this level is for each individual part to be generated reasonably as often as possible without greatly restricting the solution space or excluding interesting solutions.</div><div><br /></div><div><b>Level 2 -- Multi-part Constraints</b></div><div><br /></div><div>Above I pointed out an example where there was a dependency between two different choices in procedural generation. Capturing this requires a multi-part constraint. These constraints can consider and control multiple parts or choices simultaneously in order to stay within the good tree space. An example of a multi-parameter constraint is one that forces needles and conical tree shapes to be chosen together.</div><div><br /></div><div>Part of the reason rule-based systems and generative grammars are popular for procedural generation is because they provide an expressive system for implementing multi-parameter constraints. For example, in a typical grammar we could say something like this:</div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div><pre style="background: rgb(255, 255, 255); text-align: left;">TREE <span style="color: #808030;">=</span><span style="color: #808030;">></span> ROUND<span style="color: #808030;">-</span>BODY LEAVES <span style="color: #808030;">|</span> CONICAL<span style="color: #808030;">-</span>BODY NEEDLES</pre></div></blockquote><div><div>A tree is a round body with leaves OR a conical body with needles. </div><div><br /></div><div>One kind of multi-part constraint that grammars are very good at capturing is structure -- the relationships between the parts of an object. In a grammar you can easily describe the parts of an object and how they relate to one another. The grammar for CDL describes the parts of CDL (e.g., a command can be RPOINT, CIRCLE, etc.) and the sequence and order of the parts (e.g., an RPOINT command is followed by a parenthesis, followed by a decimal number, followed by a comma, and so on). So grammars are often a good choice to generate something you can describe as a structure.</div><div><br /></div><div>When I'm developing a procedural generation system, I try to understand the structure of what I'm creating either top-down (e.g., “What are the parts of a fantasy map and how do they relate to each other?") or bottom-up (e.g., “How do mountains relate to other parts of a fantasy map?"). I switch fluidly between the two as development dictates. I find that's it's usually good to at least start with the an initial top-down structure (e.g., “A map compass consists of an outer ring, some compass points, and then an inner ring") but each sub-part you identify becomes a new thing to generate, so as you dive down into a part you often start this over (e.g., "Compass points can include the cardinal directions only, the cardinal directions and the ordinal directions, or down to the interordinal directions") which leads to a natural trade-off between top-down and bottom-up.</div><div><br /></div><div>As I break down the structure of something to generate, I'm also trying to think about the other kinds of dependencies the parts may have. For example, I might note that if the cardinal compass points are shaded to the right then all the other compass points should be shaded to the right as well. Depending upon your development process you may be able to implement this immediately or add it to a list of dependencies to be added later when all the parts are complete. But noting the dependency will help you plan ahead for that and avoid rework.</div><div><br /></div><div>The goal at this level is to achieve consistency across all the parts of the procedural generation most of the time.<br /><div><br /></div><div><b>Level 3 -- External Constraints</b></div><div><br /></div><div>The previous two levels captured constraints that are intrinsic to the object being generated, so if they are complete the object should be consistent and correct, i.e., a round body with leaves or a conical body with needles, but never a round body with needles. Another kind of constraint is one that comes from outside of the object being generated, i.e., an external constraint. For example, if I'm generating trees for a mountainous area of a map, then the trees need to be conical bodies with needles because deciduous trees (even internally consistent ones ) shouldn't be in the mountains. These external factors place additional constraints on the procedural generation.</div><div><br /><div>You might argue that external constraints could be added to the procedural generator to become internal constraints. For example, if I were generating the mountains and the trees on the mountains within the same procedural generation process, then this external constraint would be an internal constraint. That's not wrong, but it isn't practical to pull every external constraint into a single process. It's hard enough to generate mountains on their own without having to entwine that with the generation of trees. However, it is useful to consider whether the procedural generation would be easier if combined. You might find a situation where two different things are so intertwined with dependencies that generating them together makes everything easier.</div><div><br /></div><div>The key question I ask myself when thinking about external constraints is “How will this be used once it is generated?" Most of the external constraints arise from making something “fit for purpose" -- suitable for how you are going to use it.</div><div><br /></div><div><b>Level 4 -- Independent Subtypes</b></div><div><br /></div><div>In many kinds of procedural generation, the object being generated has multiple subtypes. For example, if we're generating trees, they might be deciduous trees with leaves (like a maple tree) or they might be non-deciduous trees with needles (like a fir tree). Another example of this can be seen in games like NetHack where some dungeon levels are special and generated in a different way. Typically the subtypes might share some basic parameters or parts (e.g., size, having a trunk and branches, or number of rooms and stairways) but at some point diverge into independent parameters or parts (e.g., leaves versus needles, or irregular cavern-shaped rooms versus rectangular rooms). Enumerating these subtypes and adding them to the procedural generation is what I think of as Level 4.</div><div><br /></div><div>This is a mechanical (but perfectly acceptable) way to capture human creativity. In the case of map compasses, I noted that some (human-authored) compasses are made to look like the sun:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQ68u_UF9vCrwtz0cRtumZ_5C7xqtr5jdoq01LUDkrtJM-uUxdMbWxLxrs52TwZ7megWdVq5haXVpjy1aB8o2aoha7xh7akACKUusNrs2QKyFccHPQq8q9_ZNfG8DFdzQsYBM7IOAJGS35/s671/Image36.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="670" data-original-width="671" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQ68u_UF9vCrwtz0cRtumZ_5C7xqtr5jdoq01LUDkrtJM-uUxdMbWxLxrs52TwZ7megWdVq5haXVpjy1aB8o2aoha7xh7akACKUusNrs2QKyFccHPQq8q9_ZNfG8DFdzQsYBM7IOAJGS35/w200-h200/Image36.png" width="200" /></a></div>I could add “sun compasses" to my compass generator to capture that variety without understanding how a person came up with that idea, or without creating a program that could come up with that idea on its own. Adding subtypes like this is a good way to “fake" the kind of inventiveness people have.</div><div><br /></div><div>You'll sometimes find as you're enumerating subtypes that you figure out a “universal theory" of generation that covers multiple subtypes. For example, you might realize that needles can be treated as very skinny leaves and deciduous and non-deciduous trees can actually be generated in the same way. When this happens, it usually creates an opportunity to explore some new areas in the space of what you're creating. If needles are skinny leaves, what are really fat leaves? You should be open to gaining a better understanding of what you're generating through the process of creating the generator. <div><br /></div><div><b>Level 5 -- Simulated Processes</b></div><div><br /></div><div>The things we generate procedurally are often items of the natural world that have arisen through complex processes, e.g., a map is a representation of the landform, biomes, and other aspects of the physical world, a tree is the result of biological processes, etc. For topics like this, there is a level of procedural generation that creates digital objects by simulating the same processes that create their natural world counterparts. For example, there are computer programs that create landforms by simulating the types of tectonic processes that lead to real-world landforms.</div><div><br /></div><div>This level of procedural generation has the promise of generating very realistic objects, but in reality there are several challenges to this approach. First, many of the processes in our natural world are not completely or even well understood. The biology that results in a tree is still an area of active research. Second, even where processes are well-understood they are often complex enough that simulating them at complete fidelity in a computer program is impossible within realistic limits. So this level becomes a balancing act between abstraction and simplification of the natural processes, and creating a useful and realistic result.</div><div><br /></div><div>Those caveats aside, simulated processes are one of the most powerful tools for procedural generation because they provide an understandable framework for creating complex, interlocking, multi-part dependencies that would be difficult or impossible to capture in a traditional rule-based grammar. And in many cases we do have the understanding and tools to create a simplified simulation of a natural process that produces realistic results.</div><div><br /></div><div>In general, we don't actually care about the simulated process itself. If I want to generate a plausible landform for use in a computer game, it doesn't matter to the game whether I generated that landform through plate tectonics or with Perlin noise. So the key question to ask when you contemplate implementing a simulated process is “Is this the easiest way to get an acceptable result?" Answering this question might require you to think carefully about what constitutes an “acceptable" result. My experience is that the people who build procedural generation systems care much more about realism and fidelity than the users do, but you'll have to make your own judgement about that.</div><div><br /></div><div>Implementing any sort of realistic simulated natural process is usually complex and difficult, so the key decisions are about how to simplify the simulation. Your aim is to build a simulation where every part is absolutely necessary to achieve your acceptable result and as simple as possible. For example, if having realistic rainwater channels in my landform was very important to me (and hence part of what I consider an “acceptable result") then I might find it worthwhile to implement a droplet-based erosion simulation. On the other hand, if I only cared that my landform look generally plausible, I would avoid simulating erosion and find an easier method.</div><div><br /></div><div><b>Level 6 -- Artistic Processes</b></div><div><br /></div><div>A less obvious problem with simulated processes is that they may not produce very interesting or creative results. Most landforms and trees and maps are, after all, pretty uninteresting. So if we're using procedural generation for an artistic endeavor like a computer game, we may find that perfectly realistic results are not actually very good for our needs. It turns out our goal was not to generate perfectly lifelike and representational objects, but to generate acceptably realistic but interesting and creative objects.</div><div><br /></div><div>It's not hard to find real-world examples where creators have overruled realism in their own internal procedural generation in favor of some artistic purpose. Tolkien is often criticized for his <a href="https://www.tor.com/2017/08/01/tolkiens-map-and-the-messed-up-mountains-of-middle-earth/" target="_blank">unrealistic geography</a> but many critics miss the point that Tolkien created the geography of Middle Earth to serve the narrative, not to be realistic. Mordor is surrounded by mountains to create a hero quest for Frodo, not because Tolkien ran a plate tectonics simulation. Of course we cannot model human-level creativity, but we can capture artistic and creative decisions at a simpler level. For example, we might build a map compass generator that favors symmetrical designs because we find those more interesting and beautiful.</div><div><br /></div><div>Artistic goals are a kind of external constraints, so when I think about the artistic goals of a procedural generator, I ask myself a variant of the external constraints question: “What artistic purpose will this item serve, and where should that overrule the rules of consistency and structure?"</div><div><br /></div><div>One particular kind of artistic goal that is worth thinking about is artistic style, which for the purposes of this discussion I will define as “a consistent treatment of the non-functional characteristics of similar elements." In other words, creating a coherent flavor across all the parts of the procedural item. To take an example from <b style="font-variant-caps: small-caps;">Dragons Abound</b>, consider this map in the Knurden style:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8gO_iRf5hg_K7_NVmuzZIiXwZWeax1IkT7m6EIz1jXuPQk8yutxoYjfkDDlQOrtpuIieFC1Pt1IInRaM3u9OqvaVtJfaRWqyMkJU6huKlrisFU1rSOTTnwI706L9Q799s9lp8H-8jEnLZ/s537/image.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="306" data-original-width="537" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8gO_iRf5hg_K7_NVmuzZIiXwZWeax1IkT7m6EIz1jXuPQk8yutxoYjfkDDlQOrtpuIieFC1Pt1IInRaM3u9OqvaVtJfaRWqyMkJU6huKlrisFU1rSOTTnwI706L9Q799s9lp8H-8jEnLZ/s16000/image.png" /></a></div>You'll see that it has a consistent color scheme across land, forest and mountains, that the trees and the mountains share the same sort of softly rounded shapes, and that the trees and mountains have similar highlights and shadows. As a result there's a consistency of presentation across the map that is pleasing to the eye.</div><div><br /></div><div>One hallmark of simpler procedural generation is that you can look at the result and easily discern which parts were generated independently, because there is no consistency of choice across the parts. Style creates that connection between elements by making consistent choices on the non-functional characteristics of the parts.</div><div><br /></div><div><b style="font-variant-caps: small-caps;">Dragons Abound </b>selects or generates its own style. As a simple example, <b style="font-variant-caps: small-caps;">Dragons Abound</b> might have a rule that decides on a style for thin circles in a map compass by picking a width that will be used for consistently for all thin circles in the compass (rather than making a random choice for each). Or there might be a rule to enforce that any two elements grouped together should have a factor of (say) three in their size or spacing. Of course, you can also create a procedural system that implements a single style. Although <b style="font-variant-caps: small-caps;">Dragons Abound</b> can generate maps in many styles, it would be fine to have a system that could only generate the Knurden style.</div><div><br /></div><div>When I'm thinking about style in procedural generation, the two related questions I ask myself are “What non-functional characteristics of this design are defined by the style?" and “What are the values of those characteristics for this style?" In the Knurden style I might note that the land, forest and mountain colors are defined by the style, and that those colors fall into a range of green values. And indeed, if you were to look into the <b style="font-variant-caps: small-caps;">Dragons Abound</b> settings for the Knurden style you'd see that those colors are forced into the values for this style:</div><div><pre style="background: rgb(255, 255, 255);"> colorsLandForce<span style="color: purple;">:</span> <span style="color: #808030;">[</span><span style="color: #008c00;">143</span><span style="color: #808030;">,</span> <span style="color: #008c00;">163</span><span style="color: #808030;">,</span> <span style="color: #008c00;">94</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
colorsForestForce<span style="color: purple;">:</span> <span style="color: #808030;">[</span><span style="color: #008c00;">112</span><span style="color: #808030;">,</span> <span style="color: #008c00;">127</span><span style="color: #808030;">,</span> <span style="color: #008c00;">73</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span></pre></div><div>As far as building an artistic procedural generator goes, don't put too much pressure on yourself. You'll find that even small efforts to think about and intentionally address the style and artistic value of your procedural generation will pay off handsomely.</div><div><br /></div><div style="text-align: center;"><b>================</b><br /></div><div><br /></div><div>I've written more than I intended on the topic of the different levels of procedural generation, but I hope I've provided some useful ways to think about a growing scale of procedural generation complexity and corresponding questions to ask at each level.</div><div><br /></div><div>In the coming postings I'll be walking through my procedural generation development process in detail, but I want to share a few specific thoughts first.</div><div><br /></div><div><div>I like to think about the process of procedural generation as having two phases: Design and Execution. In the design phase, the procedural generation system makes choices about what it is creating -- all of those types of choices I talked about above. A procedural generation system to create a tree might start by deciding how many branches the tree will have; a procedural generation map system might start by deciding the landform. In the execution phase, the procedural generation system implements the choices by making a representation of the object, i.e., by drawing the branches or the landform.</div><div><br /></div><div>Usually both phases are incorporated into a single system, but not always. Sometimes both parts are not even computers, such as when a <a href="http://dfstories.com/the-hamlet-of-tyranny/" target="_blank">human author writes a story</a> based on the events from <a href="https://www.bay12games.com/dwarves/" target="_blank">Dwarf Fortress</a>. In that case the computer has played the design role by creating a plot that a person then executes as a story. Or a tool like <a href="https://www.autodraw.com/" target="_blank">Autodraw</a> where a person provides a rough design and the computer turns it into a drawing.</div><div><br /></div><div>I often reflect this split explicitly in my implementation of procedural generation, as I have done in map compasses, where I have separate systems for drawing compasses and for designing compasses. There are a couple of advantages to building a procedural generation system this way. </div><div><br /></div><div>First of all, it creates a <a href="https://en.wikipedia.org/wiki/Separation_of_concerns" target="_blank">separation of concerns</a> which helps avoid confusion between what is design and what is execution. If I have a problem with compass points in a generated compass, I can look separately at the design phase and the execution phase to help isolate where the problem lies. And often the kind of problem it is indicates whether it is in the design or the execution phase, helping me find it more quickly. This is more difficult when design and execution are intertwined. </div><div><br /></div><div>Second, one system can help with the development of the other system. I usually build the execution part first (as I have done here) because I find having a tool to visualize designs very useful when I'm developing the design system. Of course the reality is incremental, and I'll be going to back to the drawing system to add new capabilities as I discover I need them while developing the design system. But even this switching back and forth is possible because of the separation of the two parts. </div><div><br /></div><div>Third, having the two parts separate lets me create a hybrid system if I want. Most typically, I use this to take on the design role myself. I can design a map border and write it out in the Map Border Description Language, or I can create a map compass in CDL, and the have execution element in <b style="font-variant-caps: small-caps;">Dragons Abound</b> do the actual drawing. (I'm a better designer than artist I guess.) In theory I could also use this to combine one half of the <b style="font-variant-caps: small-caps;">Dragons Abound</b> map compass product generation system with the other half from (say) Watabou's <a href="https://watabou.itch.io/compass-rose-generator" target="_blank">compass rose generator</a>, if both were implemented with a split between design and execution.</div><div><br /></div><div>Building the design portion of a procedural generation system is generally the most daunting part. You want to <a href="https://watabou.itch.io/medieval-fantasy-city-generator" target="_blank">generate medieval cities</a>, but how do you even get started? I get started by doing what I might call “example-driven development." </div><div><br /></div><div>As the name suggests, this depends upon having some examples of the things you want to generate. Often you can collect these from other sources -- as I've done with compass roses -- but if not, you can act as your own artist and generate some examples. And since these examples are only being used to guide development, they don't have to be fancy or polished. Just good enough that you can imagine what they <i>should </i>look like.</div><div><br /></div><div>My very first step is to look through my examples and find a couple of examples that look somewhat like each other. They don't have to be completely similar but each should remind you of the other. So for map compasses, I might pick out these two compasses:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjesiXdNnFpMs3JrMcVb9Od199AjjMUlWmV6y5Yo1fonERzQO7pktbRoWCEUle85pZG9SpvKGOHJ7jc7T3UAA_Aypa1mIxIP885G58hiUF7F8OymyO7IGJ_UsdM9-yavKEw3Qb4S3ScBLOY/s500/Image72.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="251" data-original-width="500" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjesiXdNnFpMs3JrMcVb9Od199AjjMUlWmV6y5Yo1fonERzQO7pktbRoWCEUle85pZG9SpvKGOHJ7jc7T3UAA_Aypa1mIxIP885G58hiUF7F8OymyO7IGJ_UsdM9-yavKEw3Qb4S3ScBLOY/s16000/Image72.png" /></a></div>They're not exactly the same, but they have a lot of similarities. And now I'm going to use these two examples as a starting point to explore the “good map compass space."</div><div><br /></div><div>Remember earlier I talked about the bad tree and good tree spaces:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKDe8NNEk-syLRVKRHV6T6XGFEgQwu373JiVKtmAj9O9lIS9fFY8rX8wpEJYu37ncDgyrNKKERnMvdOMITM0s7MAhyLj7CmsrskPvcwUS50Ug-TksEoqYc6aokYzYsBw0wKPEj7Ug_O-L_/s400/Image71.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKDe8NNEk-syLRVKRHV6T6XGFEgQwu373JiVKtmAj9O9lIS9fFY8rX8wpEJYu37ncDgyrNKKERnMvdOMITM0s7MAhyLj7CmsrskPvcwUS50Ug-TksEoqYc6aokYzYsBw0wKPEj7Ug_O-L_/s16000/Image71.png" /></a></div>Well now I'm working in the compass space, but I don't know what's good and what's bad:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6GiZCKEivJ_YMlx8Ln-Q3LyVpWLZH2u9FZlqGh0SlrvAHLo_LHZ3KcR0esr0rUP9GGkpwRb_47DnTUIraRDuyOjcOJsvNbLSuhk1yib000Jyo4O5yqWIPFFlOLphFZDL8EbGUDa2bi5Qn/s400/Image71.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="225" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6GiZCKEivJ_YMlx8Ln-Q3LyVpWLZH2u9FZlqGh0SlrvAHLo_LHZ3KcR0esr0rUP9GGkpwRb_47DnTUIraRDuyOjcOJsvNbLSuhk1yib000Jyo4O5yqWIPFFlOLphFZDL8EbGUDa2bi5Qn/s16000/Image71.png" /></a></div>It's all a big mystery. But I do have two examples of good compasses, so I can put them down in my compass space and at least I'll know that they're within the good compass space.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfrlgNRyQuBSi8WH5hqodVlianYzmaFiBmCPjpyo6pISjWntmBkeidF1W9rnhaN7-mOdryQLH8hQScxX0jKuGLpog4EjTeurqSm2s6x9r5LcAEEDUC6O4JL_fMyjAHMwc01lIzN0kB3_Op/s400/Image71.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="225" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfrlgNRyQuBSi8WH5hqodVlianYzmaFiBmCPjpyo6pISjWntmBkeidF1W9rnhaN7-mOdryQLH8hQScxX0jKuGLpog4EjTeurqSm2s6x9r5LcAEEDUC6O4JL_fMyjAHMwc01lIzN0kB3_Op/s16000/Image71.png" /></a></div>Now my goal is to draw around those two examples an outline of a part of the good compass space.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBdPfSxIHH0LM_gmvFc-wlZdSDNfkaVjXBV3-Sb9sFKmMuZk7SdFU8FIrjzjq6vzz1p9uZN2pWIcrpf1Q5wjjHlUT_CWA2gkpRZzmFfwqxS32WzQD-Gaeqp4DXJWPPM-ihMoMvPJ951_2X/s400/Image71.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="225" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBdPfSxIHH0LM_gmvFc-wlZdSDNfkaVjXBV3-Sb9sFKmMuZk7SdFU8FIrjzjq6vzz1p9uZN2pWIcrpf1Q5wjjHlUT_CWA2gkpRZzmFfwqxS32WzQD-Gaeqp4DXJWPPM-ihMoMvPJ951_2X/s16000/Image71.png" /></a></div>You can think of our two examples as neighbors in the compass space -- neighbors because they share a lot of the same features and characteristics but with some differences. I want to explore that outline to discover other neighbors that like our examples are in the good compass space.</div><div><br /></div><div>I start this process by identifying where the two examples are the same and where they differ:</div><div><ul style="text-align: left;"><li>Both compasses are drawn in black and white.</li><li>Both compasses have compass points for both the cardinal and ordinal points.</li><li>Both compasses have straight compass points.</li><li>Both compasses have longer cardinal points than ordinal points.</li><li>Both compasses have compass points drawn with light and dark halves.</li><li>Both compasses have N/E/S/W labels on the outside of the cardinal points.</li><li>Both compasses have a single ring decoration.</li><li>Both compasses have the ring decoration in the outer half of the cardinal points.</li><li>Both compasses have the ring decoration under the compass points.<br /><br /></li><li>One compass has a circular scale for a ring decoration.</li><li>One compass has a combination of a thick and thin circle for a ring decoration.</li><li>One compass has fat compass points.</li><li>One compass has skinny compass points.</li><li>One compass has plain labels.</li><li>One compass has italic labels.</li></ul></div><div><div>Keeping in mind that this is a metaphor, I can view the features where the two compasses differ as representing the area in the compass space “between" the two compasses. I can create new compasses by starting with one of my example compasses and changing one or more of these differing values to match the other compass, or perhaps somewhere partway between the two compasses. For example, I could create the first compass but use the italic labels from the second compass:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV4twSnLHUAvdLghr32Byi6vzBRm1p7yokpXzg0ehrrMOeTuTW91kYgMlTOPeViIguAxT_tcIjMDCKJlrAb03tYQqOCMpYeFfF5yNa7OA3F-lbuW7sOPjnVootym8YM0a37eJET0ySzkwZ/s208/Image72.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="208" data-original-width="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV4twSnLHUAvdLghr32Byi6vzBRm1p7yokpXzg0ehrrMOeTuTW91kYgMlTOPeViIguAxT_tcIjMDCKJlrAb03tYQqOCMpYeFfF5yNa7OA3F-lbuW7sOPjnVootym8YM0a37eJET0ySzkwZ/s16000/Image72.png" /></a></div>In this way, I have “procedurally generated" a new compass. Conceptually, this new compass is somewhere “between" the two example compasses. And because this new compass shares all its features with known good compasses, there's a good chance that it's a good compass too. </div><div><div><br /></div><div>On the other hand, changes to the features the two compasses share move into the space outside of the two compasses. If I change the straight compass points to wavy compass points, I've created a compass that has a feature that neither of the example compasses shares:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsYhFMdVfYjCNxHdVAFTB6z6f_6PMoSBJUm-22pwC2sDifPCMvS_wYgKGP3PPIWX8TcBfVwOoBSGob3SAhFfvlt4fP9SDrCY8vAY_lzXnSeQR71CY_MhgJgGw1GvZecHxKZOnLVrji_F0y/s206/Image73.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="203" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsYhFMdVfYjCNxHdVAFTB6z6f_6PMoSBJUm-22pwC2sDifPCMvS_wYgKGP3PPIWX8TcBfVwOoBSGob3SAhFfvlt4fP9SDrCY8vAY_lzXnSeQR71CY_MhgJgGw1GvZecHxKZOnLVrji_F0y/s16000/Image73.png" /></a></div>That looks okay, but in general if my example compasses share a feature, that might mean that changing it pushes me off into Bad Compass Space. What if I make the big compass points wavy?<br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA-MgXhBO4cdclMUV-81EdNwxgN0ELQ0qT0hmRCQaG5_upBIFNFs9i3tOvk645xzE_jYn3QGXXs5CJsrvwECwJrNv1p0g5zpxeg8F3ivA7tbzY7ixPJbbTr9CEUspK6eqdeI55Xa1G35aA/s213/Image74.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="212" data-original-width="213" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA-MgXhBO4cdclMUV-81EdNwxgN0ELQ0qT0hmRCQaG5_upBIFNFs9i3tOvk645xzE_jYn3QGXXs5CJsrvwECwJrNv1p0g5zpxeg8F3ivA7tbzY7ixPJbbTr9CEUspK6eqdeI55Xa1G35aA/s0/Image74.png" width="213" /></a></div><div>Hmm. Not so good.</div><div><br /></div><div>So by analyzing examples, you can derive likely (and less likely) rules for generating new examples. You can also use examples to validate rules. For example, if I had created a rule that said “Straight compass points can be replaced with wavy compass points," I could then look through my corpus of examples to check that rule, and I might notice that cardinal compass points are never wavy.</div><div><br /></div><div>If you generate rules primarily by comparing similar examples and focusing on their differences, you'll end up with rules that are conservative: almost always producing an acceptable result, but without too many creative surprises. Conversely, if you compare very different examples, or focus on changing the shared features of similar examples, you're like to end up with liberal rules that produce more creative surprises but also produce more unacceptable results. Personally, I like to build a core of conservative rules and then carefully extend those rules to create more variety. You may find it better to start with an expansive set and prune it back to a better balance.</div><div><br /></div><div>Whatever your approach, a useful practice when you implement a new rule is to generate a bunch of random examples at both extremes of the rule. For example, if I had a rule that said I could have between 1 and 32 compass points, I would generate some examples with the number fixed at 1 and some with the number fixed at 32, and narrow down that range until the examples looked “mostly" good, where the value of mostly depends on your tolerance for bad examples versus your desire for creative surprises. (And that's an example of a Level 1 Constraint, so I have now come full circle in this discussion.)</div><div><br /></div><div>That's enough (probably too much) philosophy for one blog post. Next time I'll start on implementation.<br /><div><br /><br /></div></div></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com5tag:blogger.com,1999:blog-3367557180796013182.post-63888325151691402672022-01-19T21:01:00.000-05:002022-01-19T21:01:35.853-05:00Map Compasses (Part 10): Triangles, Diamonds and Wavy Compass Points<p>Welcome back to my blog series on implementing procedurally-generated map compasses! So far I've implemented the language and interpreter for all the capabilities needed to draw compasses like these examples:</p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsh0XQDnIlEsZ3FRbNqk5MyCy5tT_F5s314QETaMCTbOhGavy77R8ihb-4V_fUJ2fCWrIuW5ksBnJlvOfLDFjF-PeEOpwgHVw9OwPcseyfb0HAPVRszwFnba3y_QXlviqPW_S6Q5SH3qKq/s240/Image54.png" style="clear: left; float: left; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="240" data-original-width="235" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsh0XQDnIlEsZ3FRbNqk5MyCy5tT_F5s314QETaMCTbOhGavy77R8ihb-4V_fUJ2fCWrIuW5ksBnJlvOfLDFjF-PeEOpwgHVw9OwPcseyfb0HAPVRszwFnba3y_QXlviqPW_S6Q5SH3qKq/w196-h200/Image54.png" width="196" /></a></div></blockquote></blockquote><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjC7FFgH-20VySzCNjYGCncIMOSn64MLEb1y1F8TsZ3MPSRUgRtPIZd_54jcMDGnTK2gnFn4YKSW1eGQNBHz5vmdbXutjePRN68xCazWFK_7ywwfsrVLpkET2yC2ZBVNof6xYHgW7SrDskR/s415/Image30.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="415" data-original-width="399" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjC7FFgH-20VySzCNjYGCncIMOSn64MLEb1y1F8TsZ3MPSRUgRtPIZd_54jcMDGnTK2gnFn4YKSW1eGQNBHz5vmdbXutjePRN68xCazWFK_7ywwfsrVLpkET2yC2ZBVNof6xYHgW7SrDskR/w193-h200/Image30.png" width="193" /></a></p>With the current capabilities, CDL can draw almost half of the compasses in my examples set (in some cases missing a decorative element), so I want to move on to procedural generation soon. But I'll implement a few more capabilities first.<div><br /></div><div>First, let me return to radial triangles and modify them so they can have a light side and a dark side like a compass point, as seen in this example:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4XIhMLSF0FA-yp0_I81D5QbsKWgeCZaKchCGEHTlPStxpQqTKom9gel1AkW-Qa7Ki0qFxq5obdWStWtx4Lf57UT_WAzXY_TL2nmO0QjYtj7GRR1_vCMODAsXF_gMxe70dE7uWxst29VO4/s600/compass11.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="579" data-original-width="600" height="309" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4XIhMLSF0FA-yp0_I81D5QbsKWgeCZaKchCGEHTlPStxpQqTKom9gel1AkW-Qa7Ki0qFxq5obdWStWtx4Lf57UT_WAzXY_TL2nmO0QjYtj7GRR1_vCMODAsXF_gMxe70dE7uWxst29VO4/s320/compass11.png" width="320" /></a></div>And I'll take this opportunity to illustrate a grammar feature I haven't yet used:<div><pre style="background: rgb(255, 255, 255);"># A radial triangle element
# RTRI<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> height<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> lineWidth<span style="color: #808030;">,</span> lineColor<span style="color: #808030;">,</span> fillColor<span style="color: #808030;">)</span>
# or
# RTRI<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> height<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> lineWidth<span style="color: #808030;">,</span> lineColor<span style="color: #808030;">,</span> fillColor<span style="color: #808030;">,</span> darkFill<span style="color: #808030;">)</span>
rtriElement <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RTRI</span><span style="color: maroon;">"</span>i WS <span style="color: maroon;">"</span><span style="color: #0000e6;">(</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">)</span><span style="color: maroon;">"</span> WS
<span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">(</span><span style="color: purple;">{</span>op<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RTRI</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> start<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">4</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> repeats<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">8</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> height<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">12</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
width<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">16</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> lwidth<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">20</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> color<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">24</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
fill<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">28</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> darkfill<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">28</span><span style="color: #808030;">]</span>}<span style="color: #808030;">)</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span><br />rtriElement <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RTRI</span><span style="color: maroon;">"</span>i WS <span style="color: maroon;">"</span><span style="color: #0000e6;">(</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqstring WS<span style="color: maroon;">"</span><span style="color: #0000e6;">)</span><span style="color: maroon;">"</span> WS
<span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">(</span><span style="color: purple;">{</span>op<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RTRI</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> start<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">4</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> repeats<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">8</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> height<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">12</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
width<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">16</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> lwidth<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">20</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> color<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">24</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
fill<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">28</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> darkfill<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">32</span><span style="color: #808030;">]</span><span style="color: purple;">}</span><span style="color: #808030;">)</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span>
</pre><div><div>I've defined RTRI twice: once with 7 parameters and once with 8 parameters. The optional 8th parameter specifies a dark fill color. (The parser can tell the two commands apart because if it spots a comma after the fill parameters, it knows it needs to use the 8 parameter version of the rule.) In either case the rule produces an object with 8 parameters, but if no dark fill color was provided, then the dark fill is set to be the same as the regular fill color.</div><div><br /></div><div>In Draw.rtri(), I have to create the left and right halves of the triangle and fill them in before drawing the outline over the top, analogous to what I did with the compass points:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> rtri<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>height <span style="color: #808030;">==</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span> op<span style="color: #808030;">.</span>height <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> left <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
left <span style="color: #808030;">=</span> left<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>fill<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>left<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> right <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
right <span style="color: #808030;">=</span> right<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>darkFill<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>right<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> triangle <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
triangle <span style="color: #808030;">=</span> triangle<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>fill<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>triangle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></div><div>And now something like this:</div><div><pre style="background: rgb(255, 255, 255);">RTRI<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">8</span><span style="color: #808030;">,</span> <span style="color: #808030;">-</span><span style="color: #008c00;">8</span><span style="color: #808030;">,</span> <span style="color: #008c00;">8</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;">-</span><span style="color: #008c00;">1</span><span style="color: #808030;">)</span> CIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
</pre></div><div>produces</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZUo5QB2F8xubM4ccnHVlCRnC0tUmV3-4301BMRz1jAwhFqCr8eAAmdqkdZ8z3hQ0ld9Tk_0pNacagqVYdcheltxeg3U6hULRIGfCP4wYNo75xsZu4b19jqUJa-R6Zrw3USDQaRe25Rvf3/s214/Image55.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="214" data-original-width="214" height="214" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZUo5QB2F8xubM4ccnHVlCRnC0tUmV3-4301BMRz1jAwhFqCr8eAAmdqkdZ8z3hQ0ld9Tk_0pNacagqVYdcheltxeg3U6hULRIGfCP4wYNo75xsZu4b19jqUJa-R6Zrw3USDQaRe25Rvf3/s0/Image55.png" width="214" /></a></div><div><br /></div><div>A very similar element is radial diamonds, as seen in this example:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihFHVApA6tHyRWp9ZKLAZ4LA078V9Yb3Zl00QCUXES996mPvx_CFMS-ItUmdojiWXvwd_cW7oFnKVwp3cZ_3ICzKQJqunkeppj5Yo5LGmCwamuR3PywQqnCzEMUZgbqHKgLt4thGoWVUEr/s384/Image40.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="384" data-original-width="340" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihFHVApA6tHyRWp9ZKLAZ4LA078V9Yb3Zl00QCUXES996mPvx_CFMS-ItUmdojiWXvwd_cW7oFnKVwp3cZ_3ICzKQJqunkeppj5Yo5LGmCwamuR3PywQqnCzEMUZgbqHKgLt4thGoWVUEr/s320/Image40.png" width="283" /></a></div>As with the triangles, these can have a dark and a light side. The implementation is almost exactly the same as triangles, but with a diamond shape, so each side has four points instead of three.<br /><div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> rdiamond<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>height <span style="color: #808030;">==</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span> op<span style="color: #808030;">.</span>height <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> left <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
left <span style="color: #808030;">=</span> left<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>fill<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>left<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> right <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
right <span style="color: #808030;">=</span> right<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>darkFill<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>right<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> diamond <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span>
<span style="color: #808030;">]</span><span style="color: purple;">;</span>
diamond <span style="color: #808030;">=</span> diamond<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>diamond<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
</pre><p>producing this:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4Pxc5LSN6N8vmJjL0AprhuEbQtLi_9zF7EgWYezql-O-55lbn2Pk7dMeHE0DsjaKOj_P8viYq8ln3au5D3xnaQJKvFofh2yZvSAGAm7oniXM_Lknpc_5BsCCzuNG_aKUhoHxzBK3Q94qY/s192/Image56.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="192" data-original-width="192" height="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4Pxc5LSN6N8vmJjL0AprhuEbQtLi_9zF7EgWYezql-O-55lbn2Pk7dMeHE0DsjaKOj_P8viYq8ln3au5D3xnaQJKvFofh2yZvSAGAm7oniXM_Lknpc_5BsCCzuNG_aKUhoHxzBK3Q94qY/s0/Image56.png" width="192" /></a></div><div><br /></div><div>I'm also going to take this opportunity to go back and refactor the code for RCIRCLE slightly. The other radial elements (triangles, diamonds and lines) are drawn so that the bottom of the element is on the current radius. But radial circles are drawn centered on the current radius. It will be convenient later on (in Part 14 to be precise) if the placement of radial elements is handled consistently. So I'll fix RCIRCLE to place the bottom of the circle on the radius and draw upwards, unless the radius is negative, in which case it will be drawn downwards. The other elements also specify the total size of the element in the CDL, but RCIRCLE specifies half the size (the radius of the circle). I'll also fix this so that the size in RCIRCLE is the diameter instead of the radius. That looks like this:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> rcircle<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> effRadius <span style="color: #808030;">=</span> radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>size/2<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> sx <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> sy <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
circle<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> <span style="color: #808030;">[</span>sx<span style="color: #808030;">,</span> sy<span style="color: #808030;">]</span><span style="color: #808030;">,</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">abs</span><span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>size/2<span style="color: #808030;">)</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>fill<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
</pre></div><div>Two changes are necessary. First, I have to adjust the radius at which I'm drawing the circles by the radius of the circles. This essentially shifts the circle so that the bottom of the circle rather than the center of the circle is at the current drawing radius. Second, I have to treat the size of the circle as an absolute number, since SVG won't draw a circle with a negative radius.</div><div><br /></div>A more challenging feature that appears in a number of the example compasses is the wavy compass point:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPbEEShDWfxdT4nWZKLKsZ8BjbSi30nZ3odJJLY0Qf1lnZFQPCvjmuFSYkznnMceC2Vg0MTpbbYkQKeLIuTTBOInq8oLb9jHE6ukdb-5PORDVABZdr_bLezZ_bcUvNyBk8yQ35l5xjKDTg/s239/6885bcf130e58c796ca48785b564a77b.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="239" data-original-width="236" height="239" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPbEEShDWfxdT4nWZKLKsZ8BjbSi30nZ3odJJLY0Qf1lnZFQPCvjmuFSYkznnMceC2Vg0MTpbbYkQKeLIuTTBOInq8oLb9jHE6ukdb-5PORDVABZdr_bLezZ_bcUvNyBk8yQ35l5xjKDTg/s0/6885bcf130e58c796ca48785b564a77b.jpg" width="236" /></a></div>This is often but not always associated with an image of the Sun. Interestingly, it is almost always used as in the above compass, where the cardinal directions are straight compass points and the ordinal directions are wavy.</div><div><br /></div><div>I could add a “wave" parameter to RPOINT but the code will be quite different, so I think I'll just make a new command RWAVE with all the same arguments. That gets added to cdl.ne and compass.js in the usual ways, and the command invokes Draw.rwave(). So that only leaves the actual hard part to do: drawing the wavy compass points.</div><div><br /></div><div>My plan for drawing the wavy pointers starts with the two sides to a regular pointer:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgn5oHZVEVjh3eTi6uixN_hns34ructqj1FZYG1RnED3iSvNTfDRbUzw9cdX0ZQv3ng3E5V-MWvsO36oxCcA6jzUGFJ48CJrLF7k5skBgignbu3uyFXXvWMJszKxYkpr1OmHbpd42H0N5bV/s200/Image58.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgn5oHZVEVjh3eTi6uixN_hns34ructqj1FZYG1RnED3iSvNTfDRbUzw9cdX0ZQv3ng3E5V-MWvsO36oxCcA6jzUGFJ48CJrLF7k5skBgignbu3uyFXXvWMJszKxYkpr1OmHbpd42H0N5bV/s16000/Image58.png" /></a></div>I'll find points in the middle part of each side and move one set of points left and the other set right, like this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUWU8RezyOOvloM-sFaNzr1zz5ND_CLEc_Lud4w60FzuyD2fnLsEUpEjGj9ASe8sDvlKEODWJFhVKxw18jAzzXhJDftIlUVnzppdbTorTT2cduz0wwtiDTF7G6lUS49g6gu1fExNuPAk0l/s200/Image58.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUWU8RezyOOvloM-sFaNzr1zz5ND_CLEc_Lud4w60FzuyD2fnLsEUpEjGj9ASe8sDvlKEODWJFhVKxw18jAzzXhJDftIlUVnzppdbTorTT2cduz0wwtiDTF7G6lUS49g6gu1fExNuPAk0l/s16000/Image58.png" /></a></div>which should create a jagged version of a wavy point, like this:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_5MYQFUObLWMVxP-bZKsouTMgikwJcH4soxmTnRzNh62IV6p7xkpLwp-T1tgfpGg7w-B2Gr1qr5yzOg1D-qZwH6vWGvE1EE2GZr0yfbmCMN6FA6MJ5yHVVDcx66scuc1Zp1zhTowCKDx1/s200/Image58.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_5MYQFUObLWMVxP-bZKsouTMgikwJcH4soxmTnRzNh62IV6p7xkpLwp-T1tgfpGg7w-B2Gr1qr5yzOg1D-qZwH6vWGvE1EE2GZr0yfbmCMN6FA6MJ5yHVVDcx66scuc1Zp1zhTowCKDx1/s16000/Image58.png" /></a></div>In a later step I'll turn that into a smooth curve.<div><br /></div><div>I don't have a good intuition on how to split up the sides, but eyeballing the examples suggests that thirds is a reasonable starting point. My intuition on moving the points is that each point should move some small fraction of the width at the point.</div><div><br /></div><div>The first part of Draw.rwave() remains the same as Draw.rpoint:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> rwave<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> startAngle<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> start <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> end <span style="color: #808030;">=</span> center<span style="color: purple;">;</span>
<span style="color: dimgrey;">// Midpoint is the point on the center line where the</span>
<span style="color: dimgrey;">// diamond will have its maximum width</span>
<span style="color: maroon; font-weight: bold;">let</span> midpoint<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>span <span style="color: #808030;"><=</span> <span style="color: #008c00;">1</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Treat span as a % of radius</span>
op<span style="color: #808030;">.</span>span <span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>span<span style="color: #808030;">*</span>radius<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
midpoint <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// The maximum width is one that just touches the adjacent point.</span>
<span style="color: dimgrey;">// Phi is the angle between adjacent points.</span>
<span style="color: maroon; font-weight: bold;">const</span> phi <span style="color: #808030;">=</span> <span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> maxWidth <span style="color: #808030;">=</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">tan</span><span style="color: #808030;">(</span>phi<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">*</span><span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">)</span><span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Calculate the actual width based on width</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>width <span style="color: #808030;"><=</span> <span style="color: #008c00;">1</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
op<span style="color: #808030;">.</span>width <span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>width<span style="color: #808030;">*</span>maxWidth<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Calculate the two side points</span>
<span style="color: maroon; font-weight: bold;">let</span> side1 <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> side2 <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span></pre><div>I calculate the start, end and side vertices of the point. But for wavy points, I have to break up the two sides ([start, side1], [start, side2]) so that I have the two midpoints that I will perturb. I could calculate the thirds and do this manually, but breaking up a line segment into smaller pieces (a form of <a href="https://en.wikipedia.org/wiki/Interpolation" target="_blank">interpolation</a>) seems like something I might do again, so I'll write a function to handle it:<br /><div><pre style="background: rgb(255, 255, 255);"><span style="color: dimgrey;">// Divides a line segment into pieces</span>
<span style="color: maroon; font-weight: bold;">function</span> divideLineSegment<span style="color: #808030;">(</span>p1<span style="color: #808030;">,</span> p2<span style="color: #808030;">,</span> n<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Calculate the stepwise dx and dy</span>
<span style="color: maroon; font-weight: bold;">const</span> dx <span style="color: #808030;">=</span> <span style="color: #808030;">(</span>p2<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>p1<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">/</span>n<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> dy <span style="color: #808030;">=</span> <span style="color: #808030;">(</span>p2<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>p1<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">/</span>n<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> npl <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>p1<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// We do this n-1 times so that we can use p2 as</span>
<span style="color: dimgrey;">// the last point just to be sure it doesn't move</span>
<span style="color: dimgrey;">// because of a rounding error.</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> i<span style="color: #808030;">=</span><span style="color: #008c00;">1</span><span style="color: purple;">;</span>i<span style="color: #808030;"><</span>n<span style="color: purple;">;</span>i<span style="color: #808030;">++</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
npl<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span><span style="color: #808030;">[</span>p1<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>dx<span style="color: #808030;">*</span>i<span style="color: #808030;">,</span> p1<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>dy<span style="color: #808030;">*</span>i<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
npl<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span>p2<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> npl<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre>I calculate how much x and y change at each step by dividing the total change in x and y by the number of steps, and then start at p1 and step forward that much each time to get the new intermediate points. But note what I've done for the last point. I don't want the end point of the line (p2) to move when I interpolate it. But if I rely on the floating point arithmetic to make everything come out exactly, I risk a rounding error that will end up moving p2 to a new position. You might think “oh, that's going to be a tiny error" but over a long segment (and cascaded through multiple line segments) it has been a problem in <b style="font-variant-caps: small-caps;">Dragons Abound</b>.</div><div><br /></div><div>This routine splits the line segment into even pieces, which will be fine if my plan to use thirds works out. If I want to instead put the intermediate points at (say) 10% and 45%, then I can interpolate 20 points and just pull out the two that correspond to 10% and 45%.</div><div><br /></div><div>I'll use divideLineSegment to create two three-part lines for the sides:<br /><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> rwave<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> startAngle<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> start <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> end <span style="color: #808030;">=</span> center<span style="color: purple;">;</span>
<span style="color: dimgrey;">// Midpoint is the point on the center line where the</span>
<span style="color: dimgrey;">// diamond will have its maximum width</span>
<span style="color: maroon; font-weight: bold;">let</span> midpoint<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>span <span style="color: #808030;"><=</span> <span style="color: #008c00;">1</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Treat span as a % of radius</span>
op<span style="color: #808030;">.</span>span <span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>span<span style="color: #808030;">*</span>radius<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
midpoint <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// The maximum width is one that just touches the adjacent point.</span>
<span style="color: dimgrey;">// Phi is the angle between adjacent points.</span>
<span style="color: maroon; font-weight: bold;">const</span> phi <span style="color: #808030;">=</span> <span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> maxWidth <span style="color: #808030;">=</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">tan</span><span style="color: #808030;">(</span>phi<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">*</span><span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">)</span><span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Calculate the actual width based on width</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>width <span style="color: #808030;"><=</span> <span style="color: #008c00;">1</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
op<span style="color: #808030;">.</span>width <span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>width<span style="color: #808030;">*</span>maxWidth<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Create the side lines</span>
<span style="color: maroon; font-weight: bold;">let</span> side1 <span style="color: #808030;">=</span> divideLineSegment<span style="color: #808030;">(</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">,</span> <span style="color: #008c00;">3</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> side2 <span style="color: #808030;">=</span> divideLineSegment<span style="color: #808030;">(</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">,</span> <span style="color: #008c00;">3</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre>Now I want to take the middle points of those line segments and shift them right and left. Recall that I'm drawing this compass point vertically (and I'll later rotate it to where it should be), so I can shift points just by shifting their X values. <br /><pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// Create the side lines</span>
<span style="color: maroon; font-weight: bold;">let</span> side1 <span style="color: #808030;">=</span> divideLineSegment<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: #808030;">,</span> <span style="color: #008c00;">3</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> side2 <span style="color: #808030;">=</span> divideLineSegment<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: #808030;">,</span> <span style="color: #008c00;">3</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Shift the mid points</span>
<span style="color: maroon; font-weight: bold;">const</span> shiftPercentage <span style="color: #808030;">=</span> <span style="color: green;">0.33</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> shift1 <span style="color: #808030;">=</span> <span style="color: #808030;">(</span>side1<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>side2<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">*</span>shiftPercentage<span style="color: purple;">;</span>
side1<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+=</span> shift1<span style="color: purple;">;</span>
side2<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+=</span> shift1<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> shift2 <span style="color: #808030;">=</span> <span style="color: #808030;">(</span>side1<span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>side2<span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">*</span>shiftPercentage<span style="color: purple;">;</span>
side1<span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span><span style="color: #808030;">=</span> shift1<span style="color: purple;">;</span>
side2<span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span><span style="color: #808030;">=</span> shift1<span style="color: purple;">;</span>
</pre>Shift is the width of the point at that point, which is just the difference in the X value between the two sides. Then I add a fraction of that to the upper points, and subtract a fraction of the corresponding shift to the lower points. Here I'm using 33% of the width, based on some eyeballing about what looked right:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSiUSkZ7Fk0uZsnywFgjr-nRx_ozcNaY6iL2BvEEBwx3mtHP_65k5xZfPCMJx8lNqdeYG1TBA6j2f3U50CdEIfi07L7wcGKEW_jAqL7q7WyC6IKTYOCZMsfj7eqSJtcqH-Hz4X9qHDASVY/s194/Image59.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="194" data-original-width="194" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSiUSkZ7Fk0uZsnywFgjr-nRx_ozcNaY6iL2BvEEBwx3mtHP_65k5xZfPCMJx8lNqdeYG1TBA6j2f3U50CdEIfi07L7wcGKEW_jAqL7q7WyC6IKTYOCZMsfj7eqSJtcqH-Hz4X9qHDASVY/s0/Image59.png" width="194" /></a></div>Now I will combine the two sides and additional lines to the center of the compass to create a complete polygon:</div><div><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: dimgrey;">// Create a polygon consisting of the two sides, and the center point of the compass.</span>
<span style="color: dimgrey;">// Remove the first point in one of the sides so that we don't end up with duplicate</span>
<span style="color: dimgrey;">// points. Reverse one of the sides so that the polygon flows correctly.</span>
<span style="color: maroon; font-weight: bold;">let</span> outline <span style="color: #808030;">=</span> side1<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">slice</span><span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>reverse<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>side2<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">slice</span><span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span><span style="color: #808030;">[</span>end<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
</pre></pre></pre></pre></pre>As indicated in the code, I have to construct the outline carefully to get the sides in the right order and avoid duplicating points. I'm using slice() here to get copies of side1 and side2; I'll need them again later so I don't want to change them permanently here.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOCagC8d4a_k7c04HRSnHwml9W1KXjIKeIimr3SlIW83iDJd1s9NESrTQ6UlTgrF_aNfnI7zfG-DEtC4Sf04fMLMITF5ImPsp2CcIvXa_cs_wnQaG24vVKjoVveiMTzNc8mRN6voxEchAM/s187/Image60.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="187" data-original-width="187" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOCagC8d4a_k7c04HRSnHwml9W1KXjIKeIimr3SlIW83iDJd1s9NESrTQ6UlTgrF_aNfnI7zfG-DEtC4Sf04fMLMITF5ImPsp2CcIvXa_cs_wnQaG24vVKjoVveiMTzNc8mRN6voxEchAM/s0/Image60.png" width="187" /></a></div>With the jagged version complete, I need to somehow smooth out the lines. To do this, I'll use one of D3's <a href="http://using-d3js.com/05_04_curves.html" target="_blank">curve functions</a>.</div><div><br /></div><div>You may remember back in <a href="https://heredragonsabound.blogspot.com/2021/12/map-compasses-part-6-radial-triangles.html" target="_blank">Part 6</a> where I introduced the D3 line function:<br /><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;"><span> </span>const</span> lineFunc<span style="color: #808030;">=</span> d3<span style="color: #808030;">.</span>line<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">.
</span><span> </span><span> </span><span> </span><span> </span><span> </span><span> </span>x<span style="color: #808030;">(</span>pt<span style="color: #808030;">=</span><span style="color: #808030;">></span> pt<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">.
</span><span> </span><span> </span><span> </span><span> </span><span> </span><span> </span>y<span style="color: #808030;">(</span>pt<span style="color: #808030;">=</span><span style="color: #808030;">></span> pt<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">.
</span><span> </span><span> </span><span> </span><span> </span><span> </span><span> </span>curve<span style="color: #808030;">(</span>d3<span style="color: #808030;">.</span>curveLinearClosed<span style="color: #808030;">)</span><span style="color: purple;">;</span></pre><div>The line function takes a list of points and draws them on the screen using a supplied curve function. The purpose of the curve function is to draw between points to connect them together. So far I've used d3.curveLinearClosed, which simply draws straight lines between the points (and draws from the last point back to the first point to “close" the polygon). However, <span style="background-color: white;">D3 provides a bunch of different curves you can use to connect points (you can</span><span style="background-color: white;"> </span><a href="http://bl.ocks.org/d3indepth/b6d4845973089bc1012dec1674d3aff8" style="background-color: white;" target="_blank">explore some here </a>)<span style="background-color: white;">. Most of these curves try to draw a smooth curve that connects the points.</span></div><div><br /></div><div>For example, I can draw the same compass using d3.curveNaturalClosed, which creates a natural cubic spline to connect the points:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJpOu3VDVjUirdtnsH5rUDG-o4ByeqHCDLLUK8bZJRYEF0pWgFaOIJV0U2rU45ojVGD1AUVMFFd3ulYCvWurAmpvYwDiuf8sOw9P42K1UMRcMndhkFsTOx5C7OasXMqBgJoSLUgjNi0J_l/s187/Image61.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="187" data-original-width="187" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJpOu3VDVjUirdtnsH5rUDG-o4ByeqHCDLLUK8bZJRYEF0pWgFaOIJV0U2rU45ojVGD1AUVMFFd3ulYCvWurAmpvYwDiuf8sOw9P42K1UMRcMndhkFsTOx5C7OasXMqBgJoSLUgjNi0J_l/s0/Image61.png" width="187" /></a></div>And ta-dah, smooth curves! There's some interesting/funky stuff going on in the middle of the compass, but if you focus on the arms of the compass:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-K9V-nAHtra8a5f692QKToDRqTxMMHjWHLHJ40nE-VTewgFeAM2Vz6gosJrEDAKBHA2OKBHjLFCRZffQCZG6qXoMYHgalTubpXQymeLqSweH51OFvPUpvRbiZDBHZqsT3Y6hVqmp0RIDt/s187/Image62.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="187" data-original-width="187" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-K9V-nAHtra8a5f692QKToDRqTxMMHjWHLHJ40nE-VTewgFeAM2Vz6gosJrEDAKBHA2OKBHjLFCRZffQCZG6qXoMYHgalTubpXQymeLqSweH51OFvPUpvRbiZDBHZqsT3Y6hVqmp0RIDt/s0/Image62.png" width="187" /></a></div>That's actually very much the look we want. But I still need to implement the two fill halves.<div><br /></div><div>To do that, I need to create a line down the center of the point and connect it up to the two sides to create two separate polygons. And the center line needs to be wavy to match the sides.</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// Create a center line and shift it's points</span>
<span style="color: maroon; font-weight: bold;">let</span> cline <span style="color: #808030;">=</span> divideLineSegment<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: #808030;">,</span> <span style="color: #008c00;">3</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
cline<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+=</span> shift1<span style="color: purple;">;</span>
cline<span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span><span style="color: #808030;">=</span> shift2<span style="color: purple;">;</span>
cline<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span>end<span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></div><div>Note that I can reuse the same shifts I calculated earlier for the sides, and I add the end point (at the center of the compass).<div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDxZL6mUf2JvGckf3yy1YAPP7NIULCFCBD_coRd_myo24fZ13Oqq2trbFU_Hs7Bg9eg4KvZrOWe1HV0sE2BslVARksvqO134Q-EaPHsCoaucZys0WfxAE9z1T4eaXtYTbG40zkVKW5P5K1/s187/Image64.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="187" data-original-width="187" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDxZL6mUf2JvGckf3yy1YAPP7NIULCFCBD_coRd_myo24fZ13Oqq2trbFU_Hs7Bg9eg4KvZrOWe1HV0sE2BslVARksvqO134Q-EaPHsCoaucZys0WfxAE9z1T4eaXtYTbG40zkVKW5P5K1/s0/Image64.png" width="187" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div>The last step is to construct the two fill polygons by using the center line and each side, again being careful to make a complete polygon.</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// Construct left and right areas</span>
<span style="color: maroon; font-weight: bold;">let</span> left <span style="color: #808030;">=</span> cline<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>side1<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">slice</span><span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>reverse<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> right <span style="color: #808030;">=</span> cline<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>side2<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">slice</span><span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">)</span><span style="color: #808030;">.</span>reverse<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></div><div>These can then be rotated, filled with the provided colors, and drawn with the outline and centerline:<pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// Rotate everything</span>
outline <span style="color: #808030;">=</span> outline<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
cline <span style="color: #808030;">=</span> cline<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
left <span style="color: #808030;">=</span> left<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
right <span style="color: #808030;">=</span> right<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lightFill<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFuncWavy<span style="color: #808030;">(</span>left<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>darkFill<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFuncWavy<span style="color: #808030;">(</span>right<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lcolor<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFuncWavy<span style="color: #808030;">(</span>outline<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lcolor<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFuncWavy<span style="color: #808030;">(</span>cline<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;
</span>
</pre><pre style="background: rgb(255, 255, 255);"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7CnbRAF2q_bkouzza2jFBSI4ExWheaay6RRUfduaUzAEU1sNX-8d3mcDcooTHtHGzNaqqFq_BZXGxA7J4XoEjwhsXHMKgf7gY43LJqrmeMjryi6hCrzbz9DWwLLb8qmNPyyBDqkc4kkR7/s187/Image65.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="187" data-original-width="187" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7CnbRAF2q_bkouzza2jFBSI4ExWheaay6RRUfduaUzAEU1sNX-8d3mcDcooTHtHGzNaqqFq_BZXGxA7J4XoEjwhsXHMKgf7gY43LJqrmeMjryi6hCrzbz9DWwLLb8qmNPyyBDqkc4kkR7/s0/Image65.png" width="187" /></a></div></pre><div>I'm sure you've noticed that the central part of this drawing is a little odd. It actually makes an interesting pattern, but it would look odd as part of a compass. I'm not sure there's a really good way to handle this part of the wavy compass points. And indeed, every example of this I have covers up the central part of the wavy points one way or another:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAqS1OiL18a4o-MTc9pD81uwz_5u5qFPxU48S8cW_p1hSL_bERlpsX2-SJstQ_Wv8v_mRnmuLVS2BZPq7azPJ_zYcfCdZbAlL4scuXsvDqpsCXC-7AfQY4rDmV44YHi5vHsYphQIlFSEyW/s239/6885bcf130e58c796ca48785b564a77b.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="239" data-original-width="236" height="239" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAqS1OiL18a4o-MTc9pD81uwz_5u5qFPxU48S8cW_p1hSL_bERlpsX2-SJstQ_Wv8v_mRnmuLVS2BZPq7azPJ_zYcfCdZbAlL4scuXsvDqpsCXC-7AfQY4rDmV44YHi5vHsYphQIlFSEyW/s0/6885bcf130e58c796ca48785b564a77b.jpg" width="236" /></a></div>Another thing you'll notice as you try out these points is that they no longer match up exactly with other compass points, e.g., <br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU7YLl0Ol5ofj0ZRlpuwT1YrEqAIs1htNMXleryLXRZYdywqMqBY6X9-KbAOgzfz8goyWRS2q3kaggflpT3JlIcsoMDjb7UiHhVGJzHXPpNczVP-_7_2sGPHPb0PBzv7LtgDyE7wh4aO7g/s185/Image66.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="185" data-original-width="185" height="185" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU7YLl0Ol5ofj0ZRlpuwT1YrEqAIs1htNMXleryLXRZYdywqMqBY6X9-KbAOgzfz8goyWRS2q3kaggflpT3JlIcsoMDjb7UiHhVGJzHXPpNczVP-_7_2sGPHPb0PBzv7LtgDyE7wh4aO7g/s0/Image66.png" width="185" /></a></div>The ends of the wavy compass points are on the ordinal directions, but the center line is not on the ordinal direction where it meets the juncture of the straight compass points. Only certain combinations of sizes will meet up correctly. In this example:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKUncquhX7Fh-z14ked8HxRp-dG9WmTHucXG4Ouxa5ctFOrAPTzM3GbuXUdPxyva2UP9qLaLTKJ0uxHLTtA21nOZ3W4XKa_tIrE_0qQCdJXZslB5C4V1r9usEjLmMk2cvP18GZMNjAKDGM/s646/Image26.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="639" data-original-width="646" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKUncquhX7Fh-z14ked8HxRp-dG9WmTHucXG4Ouxa5ctFOrAPTzM3GbuXUdPxyva2UP9qLaLTKJ0uxHLTtA21nOZ3W4XKa_tIrE_0qQCdJXZslB5C4V1r9usEjLmMk2cvP18GZMNjAKDGM/s320/Image26.png" width="320" /></a></div>the artist has drawn part of the long wavy compass points with straight lines so that they meet up correctly, but this makes them look awkward and asymmetrical. And if you look at the shorter wavy compass points you'll see that they do not meet up correctly. If you look again at this example:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLvDN4YHdL-oU4aTo85CXTcZbNuKEh_U1jLGzbzl_E0gh5bswDJ69_jrh97A7wRRjNUQMmz9lasnnSAUK4dXft9I7Lj-wKGLjCIoU3aVf0lKfvOxB6-fIpMzCg3koDnrpTEohhE5VbcZbo/s239/6885bcf130e58c796ca48785b564a77b.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="239" data-original-width="236" height="239" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLvDN4YHdL-oU4aTo85CXTcZbNuKEh_U1jLGzbzl_E0gh5bswDJ69_jrh97A7wRRjNUQMmz9lasnnSAUK4dXft9I7Lj-wKGLjCIoU3aVf0lKfvOxB6-fIpMzCg3koDnrpTEohhE5VbcZbo/s0/6885bcf130e58c796ca48785b564a77b.jpg" width="236" /></a></div><br /><div>you'll see that the artist has covered up this junction to hide any problems.</div><div><br /></div><div>Let's check this out and see how it works. Here's an example of a generated compass using wavy points:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPXRDg89Ts_qGBbcgTIan7bGfnO9OlBgukES_qLYY2gNBwxCo5QH6LdQiNVtnGC2dym_pWvn30Kb5FVCJOcstuOUgyPpdN9iVpbl2_2x33fc1WOoZCMuk2dWFdT7dNFtlXW3g0KeMvHF6T/s296/Image35.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="296" data-original-width="296" height="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPXRDg89Ts_qGBbcgTIan7bGfnO9OlBgukES_qLYY2gNBwxCo5QH6LdQiNVtnGC2dym_pWvn30Kb5FVCJOcstuOUgyPpdN9iVpbl2_2x33fc1WOoZCMuk2dWFdT7dNFtlXW3g0KeMvHF6T/s0/Image35.png" width="296" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div>which was created with this CDL:<br /><div><pre style="background: rgb(255, 255, 255);">SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">32</span><span style="color: #808030;">)</span> RCIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">32</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;">-</span><span style="color: #008c00;">32</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">27</span><span style="color: #808030;">)</span> RWAVE<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">+Math.PI/8+</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">8</span><span style="color: #808030;">,</span> <span style="color: green;">0.80</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;">-</span><span style="color: #008c00;">27</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">11</span><span style="color: #808030;">)</span> CIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> RDIAMOND<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">16</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: green;">0.5</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span>
<span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">4</span><span style="color: #808030;">)</span> CIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;">-</span><span style="color: #008c00;">19</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">20</span><span style="color: #808030;">)</span> RWAVE<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">+Math.PI/4+</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: green;">0.85</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #808030;">-</span><span style="color: #008c00;">20</span><span style="color: #808030;">)</span>
RPOINT<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: green;">0.85</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">65</span><span style="color: #808030;">)</span> CIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">3</span><span style="color: #808030;">)</span> CIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> SPACE<span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span> CIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">black</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">white</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span>
</pre></div><div>This example is in compass.js for you to try out.</div><div><br /></div><div>I'll leave you with this eye-watering example:</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPlWTMWek34O_7vKrxhEsFzbiUs4sHsyHXvRBS6T_m8IqMA5rOV2yF39k18OLZtc2jE1QaotYp_lJOYt-AbXF4cj97ApOYBszrdOhyphenhyphenkyulE4wVK5Bjaaj0Q2AjQIHavORAu8QjJ0pnKqhX/s380/Image68.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="380" data-original-width="380" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPlWTMWek34O_7vKrxhEsFzbiUs4sHsyHXvRBS6T_m8IqMA5rOV2yF39k18OLZtc2jE1QaotYp_lJOYt-AbXF4cj97ApOYBszrdOhyphenhyphenkyulE4wVK5Bjaaj0Q2AjQIHavORAu8QjJ0pnKqhX/s320/Image68.png" width="320" /></a></div><div class="separator" style="clear: both;"><span><br /></span></div><div class="separator" style="clear: both;"><span>A reminder that if you want to follow along from home, you need to download the Part 10 branch from the </span><a href="https://github.com/srt19170/Procedural-Map-Compasses" target="_blank">Procedural Map Compasses repository </a><span> on Github and get the test web page up and open the console. <a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">Part 1</a> 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 10 directory, and then select the test.html link. Obviously you can browse the code and other files, and make changes of your own. </span><span>You can also try out this code on the web at </span><a href="https://dragonsabound-part10.netlify.app/test.html">https://dragonsabound-part10.netlify.app/test.html</a><span> although you'll only be able to run the code, not modify it.</span><span style="background-color: white; white-space: pre;"> </span></div><div class="separator" style="clear: both;"><br /></div></div><div class="separator" style="clear: both;">Next time I'll offer some thoughts on procedural generation. </div><div><span style="font-weight: 700;"><br /></span></div><div><span style="font-weight: 700;">Suggestions to Explore</span><div><div><ul><li>For both the triangles and the diamonds I've always placed the dark fill color to the right. You can easily swap to the left by swapping the colors in the CDL command, but shading that changes from right to left based on position around the compass (like it was being lit from a specific direction) is more challenging. Implement shading for triangles and diamonds that changes based on the light direction. (As was Suggested for compass points in <a href="https://heredragonsabound.blogspot.com/2021/12/map-compasses-part-7-compass-points.html" target="_blank">Part 7</a>.)<br /><br /></li><li>Diamonds as implemented here are symmetrical. Add a way to make asymmetrical diamonds where the widest point is not right in the center. Is this the same as RPOINT? Should the RPOINT and RDIAMOND commands be combined into a single command?<br /><br /></li><li>d3.curveNatural is only one possibility for curving the wavy compass points. Experiment with d3.curveCardinal using different tension values, e.g. try d3.curveCardinal.tension(0.25), and d3.curveCardinal.tension(0.75)<br /><br /></li><li>I've used a fairly aggressive amount of shift, but wavy points can look very nice with more subtle shift:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMMy6bVyjHXH_sGlrVfY6JDwvx7pQzAzpSpoqkSzBWJ20n3UJtfS7BNnkL5wYoD_1SlkoH2wkWRh2aEYeBDblcbOkUqRyM_RU-HWIUxerDd6epUVTzz81jzazZfN3u63gYDx0jNAP1bOpR/s188/Image69.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="188" data-original-width="188" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMMy6bVyjHXH_sGlrVfY6JDwvx7pQzAzpSpoqkSzBWJ20n3UJtfS7BNnkL5wYoD_1SlkoH2wkWRh2aEYeBDblcbOkUqRyM_RU-HWIUxerDd6epUVTzz81jzazZfN3u63gYDx0jNAP1bOpR/s0/Image69.png" width="188" /></a></div>Add another parameter to RWAVE that controls the amount of shift to apply. As with span and width treat values under 1 as a percent of the point width. Treat values over 1 as the number of pixels to shift.<br /><div class="separator" style="clear: both; text-align: center;"><br /></div></li><li>RWAVE does interesting things with unusual values for span and width, e.g., <br /><br /> RWAVE(0, 4, 0.50, 1, 1, "black", "white", "black")<br /><br />produces this:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgjcl_NrGNQuzbKKLtRHR-K2NyJ7_yiZxn1hyphenhyphen46u-LntWUg2YYX8T8feYqCTS4fcs3tJF1if-mdDE6y1vjwvs2ZriXL9b_H8kEGVLDXuZ2VmMX231csNoR_vGe82QBPbBs3PGBymkPORxB/s190/Image67.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="190" data-original-width="190" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgjcl_NrGNQuzbKKLtRHR-K2NyJ7_yiZxn1hyphenhyphen46u-LntWUg2YYX8T8feYqCTS4fcs3tJF1if-mdDE6y1vjwvs2ZriXL9b_H8kEGVLDXuZ2VmMX231csNoR_vGe82QBPbBs3PGBymkPORxB/s0/Image67.png" width="190" /></a></div>Experiment with this and see what can be done.<br /><br /></li><li>This example:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYFkWTEWUwAFui4on71mJOrfNtZiB4esoQDm8TwnJH08gwkNPa3iYAyjCX4D0ox2qi767At6HFs28ifNw8xO9lYKcwz3TVEHMK07bu14be0wWTRT1ojqzLsf8lTl_VyGxIKJJ6DgVg9iLQ/s187/Image63.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="187" data-original-width="187" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYFkWTEWUwAFui4on71mJOrfNtZiB4esoQDm8TwnJH08gwkNPa3iYAyjCX4D0ox2qi767At6HFs28ifNw8xO9lYKcwz3TVEHMK07bu14be0wWTRT1ojqzLsf8lTl_VyGxIKJJ6DgVg9iLQ/s0/Image63.png" width="187" /></a><br /><div style="text-align: left;">almost looks like a flower. Can you turn CDL into a flower generation language?</div></div></li></ul></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com2tag:blogger.com,1999:blog-3367557180796013182.post-41231224835521734982022-01-07T18:15:00.001-05:002022-01-07T18:15:11.839-05:00Map Compasses (Part 9): Vertical Text and Radial Arcs<p>Happy 2022! Hopefully this year will be better than the last two -- that shouldn't be difficult. </p><p>Welcome back to my blog series on implementing procedurally-generated map compasses! So far I've implemented the language and interpreter for all the capabilities needed to draw compasses like this example:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKFwX11UOU0fOjWtk8YMSGXdo7hcmhdeV2lc2X0DMqYg3btNxCdQFBr2xzGCKikaUZGuBVmER953eLn5Chra9WDQtTDwx4NmND2CeK6d6WhYDWqLJYsNd6sfackJ8yxoL4BIXY8EY5iPU9/s295/Image45.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="295" data-original-width="295" height="295" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKFwX11UOU0fOjWtk8YMSGXdo7hcmhdeV2lc2X0DMqYg3btNxCdQFBr2xzGCKikaUZGuBVmER953eLn5Chra9WDQtTDwx4NmND2CeK6d6WhYDWqLJYsNd6sfackJ8yxoL4BIXY8EY5iPU9/s0/Image45.png" width="295" /></a></div><div class="separator" style="clear: both; text-align: left;">In the last part I implemented radial labels as in the example above, but I noted that many compasses do not rotate the labels, especially for the compass points, as in this example:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigzT0SSYCzfTtIn-bzDEZnqhxbG7X94MOFZt8_ek5I35-_rRXb9UhfRWw8Qh8jsPiZN3IUa1yH4HjgrJoenBRzZIZRinjAtteGhEfyuyxulQyFx7viYSiDUtlX2XCwbju_aVliFD4AXKVB/s440/compass14.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="415" data-original-width="440" height="302" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigzT0SSYCzfTtIn-bzDEZnqhxbG7X94MOFZt8_ek5I35-_rRXb9UhfRWw8Qh8jsPiZN3IUa1yH4HjgrJoenBRzZIZRinjAtteGhEfyuyxulQyFx7viYSiDUtlX2XCwbju_aVliFD4AXKVB/s320/compass14.jpg" width="320" /></a></div>So let me implement this option. (This was one of my “Suggestions to Explore" in the last part.) To start with, I'll add an option to the RTEXT() command to control the orientation of the labels, either “radial" or “vertical."<div><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="background-color: white;"># Radial text
# RTEXT</span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">start</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> repeats</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> font</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> size</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> color</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> style</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> </span><span style="background-color: #fcff01;">orientation,</span><span style="background-color: white;"> texts</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;">
rtextElement </span><span style="background-color: white; color: #808030;">-</span><span style="background-color: white; color: #808030;">></span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">RTEXT</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;">i WS </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">(</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;"> WS decimal WS </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">,</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;"> WS decimal WS </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">,</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;"> WS dqstring WS </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">,</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;">
WS decimal WS </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">,</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;"> WS dqstring WS </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">,</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;"> WS dqstring WS </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">,</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;">
WS dqstring WS </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">,</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;"> </span><span style="background-color: #fcff01;">WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span></span><span style="background-color: white;"> WS dqList WS </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">)</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white;"> WS
</span><span style="background-color: white; color: purple;">{</span><span style="background-color: white; color: #808030;">%</span><span style="background-color: white;"> data </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white; color: #808030;">></span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white; color: purple;">{</span><span style="background-color: white;">op</span><span style="background-color: white; color: purple;">:</span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #0000e6;">RTEXT</span><span style="background-color: white; color: maroon;">"</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> start</span><span style="background-color: white; color: purple;">:</span><span style="background-color: white;"> data</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white; color: #008c00;">4</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> repeats</span><span style="background-color: white; color: purple;">:</span><span style="background-color: white;"> data</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white; color: #008c00;">8</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> font</span><span style="background-color: white; color: purple;">:</span><span style="background-color: white;"> data</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white; color: #008c00;">12</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;">
size</span><span style="background-color: white; color: purple;">:</span><span style="background-color: white;"> data</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white; color: #008c00;">16</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> color</span><span style="background-color: white; color: purple;">:</span><span style="background-color: white;"> data</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white; color: #008c00;">20</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> style</span><span style="background-color: white; color: purple;">:</span><span style="background-color: white;"> data</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white; color: #008c00;">24</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> weight</span><span style="background-color: white; color: purple;">:</span><span style="background-color: white;"> data</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white; color: #008c00;">28</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;">
</span><span style="background-color: #fcff01;">orientation<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">32</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span></span><span style="background-color: white;"> texts</span><span style="background-color: white; color: purple;">:</span><span style="background-color: white;"> data</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white; color: #008c00;">36</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: purple;">}</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">%</span><span style="background-color: white; color: purple;">}</span></pre></div><div>Now I'll fix the code to draw the label at the correct spot and with the vertical orientation if that option is chosen. Since I don't have to rotate the text, I can do this by simply rotating the spot at which I'm going to draw the label just as I did for the other radial elements.</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rtext<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> startAngle<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> iteration<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> x <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> y <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: purple;">;</span>
<span style="color: dimgrey;">// If orientation == vertical, then rotate [x, y] around the center by</span>
<span style="color: dimgrey;">// angle to find the spot for the label.</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>orientation <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">vertical</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: #808030;">[</span>x<span style="color: #808030;">,</span> y<span style="color: #808030;">]</span> <span style="color: #808030;">=</span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> <span style="color: #808030;">[</span>x<span style="color: #808030;">,</span> y<span style="color: #808030;">]</span><span style="color: #808030;">,</span> angle<span style="color: #808030;">-</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Now draw the label</span>
<span style="color: maroon; font-weight: bold;">let</span> label <span style="color: #808030;">=</span> svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">text</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">x</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> x<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">y</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> y<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-family</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>font<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-size</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>size<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-style</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>style<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-weight</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>weight<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">text-anchor</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">middle</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>text<span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>texts<span style="color: #808030;">[</span>iteration<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// If orientation == radial, then we've drawn the label at 12 o'clock</span>
<span style="color: dimgrey;">// and need to use SVG transform to rotate it around to the correct</span>
<span style="color: dimgrey;">// spot.</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>orientation <span style="color: #808030;">!=</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">vertical</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
label<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">transform</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">rotate(</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>rad2degrees<span style="color: #808030;">(</span>angle<span style="color: #808030;">-</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">,</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">,</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">)</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre><div>Note that I don't have to set all the attributes and styles of an SVG element when it is created. I can add or change these later, so I take advantage of that to add the rotation to the radial labels after drawing them as vertical labels.</div><div><br /></div>Let me test this on the N/S/E/W labels from the complicated compass up above:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggY66_EimByafX6BusQnBuPi5h3K4AHbCaVdMW0Yxi11CFV0ieiZ21Uy0pyg-5N2BrPIEoAhZLTSchOOGAO_T4dzD48mNCiOtErxD5aDwI-XxicH8XfSQM-M0TYlEfVEE6kHtisUOJzN3w/s295/Image46.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="295" data-original-width="295" height="295" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggY66_EimByafX6BusQnBuPi5h3K4AHbCaVdMW0Yxi11CFV0ieiZ21Uy0pyg-5N2BrPIEoAhZLTSchOOGAO_T4dzD48mNCiOtErxD5aDwI-XxicH8XfSQM-M0TYlEfVEE6kHtisUOJzN3w/s0/Image46.png" width="295" /></a></div>The labels are now vertical but the placement is off. Why? If you look at the code above you'll see that I'm setting the text-anchor of the label to “middle". This is the middle of the bottom of the label, so SVG is putting that spot at the tip of each of the compass points.<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQQUIKA4-XzbhbA80DJu050u-SL0ueDePb5Kad2IC-cwBJkFlfRDY9hAOAV8DiVsBQn8EIyMinl9bT6CMwFYdyaBVGjfzcR6VqzzyC2SzmJPeK4LZ4gJNCNbsNtTbUCOu1GmqeIHlyQoMP/s295/Image46.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="295" data-original-width="295" height="295" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQQUIKA4-XzbhbA80DJu050u-SL0ueDePb5Kad2IC-cwBJkFlfRDY9hAOAV8DiVsBQn8EIyMinl9bT6CMwFYdyaBVGjfzcR6VqzzyC2SzmJPeK4LZ4gJNCNbsNtTbUCOu1GmqeIHlyQoMP/s0/Image46.png" width="295" /></a></div>That works for north but not for the other positions. Instead of anchoring to the bottom of the text, in this case I should anchor to the center of the text [1]. This can be accomplished with something called the “dominant-baseline" style. Setting this to “central" does [2] what we want:<div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhB2tp6CkPYmJyE9cLC5oXZw9ejo0chA6GuHQj6pYRqWd6vGNMfS_TZrSvPb9XvoEWYI5fC6dO_J2p5MHK23jD5_PzDc_0GqZVAixZEI6kbGScuVLUOPcPT87t_9bKHPJJT9zyYPu9QxZlK/s295/Image47.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="295" data-original-width="295" height="295" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhB2tp6CkPYmJyE9cLC5oXZw9ejo0chA6GuHQj6pYRqWd6vGNMfS_TZrSvPb9XvoEWYI5fC6dO_J2p5MHK23jD5_PzDc_0GqZVAixZEI6kbGScuVLUOPcPT87t_9bKHPJJT9zyYPu9QxZlK/s0/Image47.png" width="295" /></a></div>although now we have the problem that the labels are impaled on the compass points.</div><div><br /></div><div>[1] Not really. See below.</div><div>[2] This setting doesn't really do exactly what we want, for reasons having to do with text complications like ascenders and descenders and the possibility of subscript accent marks and so on. But this gets pretty close in most cases.</div><div><br /></div><div>I can address the impalement by drawing the labels further out:<br /><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgugifjB2b6R7Hz6LKhTQA8fBCNKeKpThH5Q0knfre03X3Vfqhcqa8FyDeFc5LGBbjTEH2X0QuhZchS7-zJvL6KmawJxmWEIXdIAoLAj67DHnRC3XoZQPSc4omdUZ8incb-ak7capsYO8-l/s295/Image48.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="295" data-original-width="295" height="295" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgugifjB2b6R7Hz6LKhTQA8fBCNKeKpThH5Q0knfre03X3Vfqhcqa8FyDeFc5LGBbjTEH2X0QuhZchS7-zJvL6KmawJxmWEIXdIAoLAj67DHnRC3XoZQPSc4omdUZ8incb-ak7capsYO8-l/s0/Image48.png" width="295" /></a></div>This works fairly well but you can see that the placement of W and E are different. That's because W is a wider character than E. I can make one of the labels longer to make the problem more apparent:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzfVU1d7wKxv9K6b3Uiu8MCGvsLmfF3kmsz2Nhw-wSjTq_L0EUo7Y0Wvtxk4f0bpzrst_ecQ9jaQu_gFKdhs1OUxKo0DoE9mBqXEyRNZSkgepy3YJ_JemptSeTGqzJE04ZL-l2N7eO2qyX/s319/Image49.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="295" data-original-width="319" height="295" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzfVU1d7wKxv9K6b3Uiu8MCGvsLmfF3kmsz2Nhw-wSjTq_L0EUo7Y0Wvtxk4f0bpzrst_ecQ9jaQu_gFKdhs1OUxKo0DoE9mBqXEyRNZSkgepy3YJ_JemptSeTGqzJE04ZL-l2N7eO2qyX/s0/Image49.png" width="319" /></a></div>I think for most compasses a “close enough" approach is fine because the labels are usually the same number of characters, but see the Suggestions below for a better approach. (And if you really want to be finicky, you can place each label using a separate RTEXT command to fully control where they are displayed.)<div><br /></div><div>And here's a recreation of the sample compass from above with its vertical labels:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKeyFjSBzwDwI7F0iUXUhV4s533R_vA6-uq9sy_I0qizAyiqiy4mGvkliYJzxohxgG1tlDXwufgifmDrZ-wOpBhoMzHxVsQLmESzaMBBTqYCrlv5YCKlWyEHGiAl9n83b3_HTh5lEHMMCV/s230/Image50.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="219" data-original-width="230" height="219" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKeyFjSBzwDwI7F0iUXUhV4s533R_vA6-uq9sy_I0qizAyiqiy4mGvkliYJzxohxgG1tlDXwufgifmDrZ-wOpBhoMzHxVsQLmESzaMBBTqYCrlv5YCKlWyEHGiAl9n83b3_HTh5lEHMMCV/s0/Image50.png" width="230" /></a></div>Another problem with the recreation of the fancy compass is that the line behind the ordinal labels (NE/SE/SW/NW) is not blocked out where the labels are (compare left to right):</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-5HJDQB4FY1fZB3MJoelXBMrPEqN5pGXA4Hxu1M356chSkO8rf6Uhnbnl2U6TfoEMmsGhlpMoxZj57vZAifpx01GtYzV71_er7y7O64LFOoEn-8BCLW342cw50XyFXptFcfPYSqQPj7Uk/s617/Image25.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="320" data-original-width="617" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-5HJDQB4FY1fZB3MJoelXBMrPEqN5pGXA4Hxu1M356chSkO8rf6Uhnbnl2U6TfoEMmsGhlpMoxZj57vZAifpx01GtYzV71_er7y7O64LFOoEn-8BCLW342cw50XyFXptFcfPYSqQPj7Uk/s16000/Image25.png" /></a></div>I suggested last time that this could be handled by creating a radial rectangle element and then putting white rectangles between the label and the line. If I implement this to support any color, then alternating white and black rectangles would enable the radial scale in this example:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU6XCi_6xqxgnfsxyjNuugzjj_kQx-EwVRDvxLnw6_Cv2_EGSDtQOBgxOmB0wp2rkSF3IDCB2VbHzmLX9ruawSEY9IzBgQMugkamIMavhI1941uYvfQ_zZa73-weUl-NJsqOeb5iCtd1-X/s401/Image29.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="401" data-original-width="381" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU6XCi_6xqxgnfsxyjNuugzjj_kQx-EwVRDvxLnw6_Cv2_EGSDtQOBgxOmB0wp2rkSF3IDCB2VbHzmLX9ruawSEY9IzBgQMugkamIMavhI1941uYvfQ_zZa73-weUl-NJsqOeb5iCtd1-X/s320/Image29.png" width="304" /></a></div>Although those elements aren't really rectangles, they're arcs of a circle, so I'll call this command RARC. Here's the CDL command:<div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"># Radial arc
# RARC<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> subtend<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> color<span style="color: #808030;">)</span>
rarcElement <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RARC</span><span style="color: maroon;">"</span>i WS <span style="color: maroon;">"</span><span style="color: #0000e6;">(</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">)</span><span style="color: maroon;">"</span> WS
<span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">(</span><span style="color: purple;">{</span>op<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RARC</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> start<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">4</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> repeats<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">8</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> subtend<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">12</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
width<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">16</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> color<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">20</span><span style="color: #808030;">]</span><span style="color: purple;">}</span><span style="color: #808030;">)</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span></pre></pre></pre><div>“Subtend" is the angle between the two ends of the arc, e.g., setting this to 20 degrees will result in 18 arcs around the circle. (Although I'll use radians not degrees.)</div><div><br /></div><div>Drawing an arc is a bit of a challenge. SVG has a path command for drawing an elliptical arc; it's notoriously difficult. As is often the case, D3 provides a <a href="https://github.com/d3/d3-shape#arcs" target="_blank">much simpler way to draw a circular arc</a>. However, I'm going to choose not to use an SVG command and instead draw the arc directly. I have two motivations to do it this way. First, it's a more interesting coding problem. Second, while SVG will draw a perfect arc, when I use this code in DA I'll want to be able to draw a (subtly) imperfect arc to make it look hand-drawn. To do that, I need to do the drawing myself.</div><div><br /></div><div>So, how do you draw an arc? It starts with drawing a circle, or rather a polygon that is like a circle:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: dimgrey;">// Make a circular polygon</span>
<span style="color: maroon; font-weight: bold;">function</span> makeCircle<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> num<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> result <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> step <span style="color: #808030;">=</span> <span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">)</span><span style="color: #808030;">/</span>num<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> t<span style="color: #808030;">=</span><span style="color: #008c00;">0</span><span style="color: purple;">;</span>t<span style="color: #808030;"><</span><span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">)</span><span style="color: purple;">;</span>t <span style="color: #808030;">+=</span> step<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
result<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span><span style="color: #808030;">[</span>radius<span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>t<span style="color: #808030;">)</span><span style="color: #808030;">+</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>radius<span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>t<span style="color: #808030;">)</span><span style="color: #808030;">+</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Close off the circle</span>
result<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span>result<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> result<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre><div>What this does is step around a circle num times, adding the point at each step to a polygon. So if num was 5, the result is a five-sided polygon (a pentagon) inscribed in the circle. </div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYO_AWLkoa_eqVeTyZ96sLBhXj_xYLK_JU-4gLDCakLsS3CjqlxlL2TNcmbz-9v6dvsny-tVvf8Kjm8gDKPardnR8X_BVlFKXXx1elNJWVduspRqksvMfFUbSWcWum_rAd1w6TuX1J-B3s/s198/Image51.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="198" data-original-width="190" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYO_AWLkoa_eqVeTyZ96sLBhXj_xYLK_JU-4gLDCakLsS3CjqlxlL2TNcmbz-9v6dvsny-tVvf8Kjm8gDKPardnR8X_BVlFKXXx1elNJWVduspRqksvMfFUbSWcWum_rAd1w6TuX1J-B3s/s0/Image51.png" width="190" /></a></div><div>(The returned polygon has six points in it, because the first point is repeated at the end to “close" the polygon.) As you increase the number of sides, the polygon gets closer and closer to a circle.</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgX_OgKgF7OVElXDkxLfrIKFT2DiFStAVomrEwlUxeXSPGYA5NeFZBuBEErzjhmui-aICv7OFYLGHhK1_I7pcDTeqc6_PhBBGyClc_UGNCAgaahbfRn6sz3YVyDa7AqWdBE0a0IAOaGa96s/s198/Image52.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="198" data-original-width="190" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgX_OgKgF7OVElXDkxLfrIKFT2DiFStAVomrEwlUxeXSPGYA5NeFZBuBEErzjhmui-aICv7OFYLGHhK1_I7pcDTeqc6_PhBBGyClc_UGNCAgaahbfRn6sz3YVyDa7AqWdBE0a0IAOaGa96s/s0/Image52.png" width="190" /></a></div><div class="separator" style="clear: both; text-align: left;">To turn makeCircle into makeArc, I just need to only step over the angles of the arc rather than all the way around the circle. </div><pre style="background: rgb(255, 255, 255);"><span style="color: dimgrey;">// Make a circular arc</span>
<span style="color: maroon; font-weight: bold;">function</span> makeCircularArc<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> end<span style="color: #808030;">,</span> num<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> <span style="color: #808030;">[</span>x<span style="color: #808030;">,</span> y<span style="color: #808030;">]</span> <span style="color: #808030;">=</span> center<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> result <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> step <span style="color: #808030;">=</span> <span style="color: #808030;">(</span>end<span style="color: #808030;">-</span>start<span style="color: #808030;">)</span><span style="color: #808030;">/</span>num<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> t<span style="color: #808030;">=</span>start<span style="color: purple;">;</span>t<span style="color: #808030;"><</span>end<span style="color: purple;">;</span>t <span style="color: #808030;">+=</span> step<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
result<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span><span style="color: #808030;">[</span>radius<span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>t<span style="color: #808030;">)</span><span style="color: #808030;">+</span>x<span style="color: #808030;">,</span>radius<span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>t<span style="color: #808030;">)</span><span style="color: #808030;">+</span>y<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
result<span style="color: #808030;">.</span>push<span style="color: #808030;">(</span><span style="color: #808030;">[</span>radius<span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>end<span style="color: #808030;">)</span><span style="color: #808030;">+</span>x<span style="color: #808030;">,</span>radius<span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>end<span style="color: #808030;">)</span><span style="color: #808030;">+</span>y<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> result<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre><div>The rarc function starts by creating the inside and the outside edges of the arc:</div><div><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="background-color: white; color: maroon; font-weight: bold;">function</span><span style="background-color: white;"> rarc</span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">svg</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> center</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> radius</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> startAngle</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> repeats</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> angle</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> iteration</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> op</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> </span><span style="background-color: white; color: purple;">{</span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">const</span><span style="background-color: white;"> arcStart </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white;"> angle</span><span style="background-color: white; color: #808030;">-</span><span style="background-color: white; color: green;">0.5</span><span style="background-color: white; color: #808030;">*</span><span style="background-color: white;">op</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">subtend</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">const</span><span style="background-color: white;"> arcEnd </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white;"> angle</span><span style="background-color: white; color: #808030;">+</span><span style="background-color: white; color: green;">0.5</span><span style="background-color: white; color: #808030;">*</span><span style="background-color: white;">op</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">subtend</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">const</span><span style="background-color: white;"> outsideEdge </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white;"> makeCircularArc</span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">center</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> radius</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> arcStart</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> arcEnd</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> </span><span style="background-color: white; color: #008c00;">20</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">const</span><span style="background-color: white;"> insideEdge </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white;"> makeCircularArc</span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">center</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> radius</span><span style="background-color: white; color: #808030;">-</span><span style="background-color: white;">op</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">width</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> arcStart</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> arcEnd</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> </span><span style="background-color: white; color: #008c00;">20</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: purple;">}</span><span style="background-color: white; color: purple;">;</span>
</pre><div>I say inside and outside, but I'm following the same convention for the size of radial elements I have before. One edge of the arc (called the outsideEdge above) is “radius" away from the center point of the compass. The other edge is closer to the center point if op.width is positive, and further away if negative.</div></div><div><br /></div><div>Note that I'm centering the arc on “angle." That seems more intuitive to me than starting at angle and going clockwise or counter-clockwise.</div><div><br /></div><div>To connect the two edges into a polygon, I need to reverse the order of points in one of them, and then concatenate the two edges together. Then I can draw it like any other path, but in this case there's no edge, it's just a filled area of color.<br /><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> rarc<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> startAngle<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> iteration<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> arcStart <span style="color: #808030;">=</span> angle<span style="color: #808030;">-</span><span style="color: green;">0.5</span><span style="color: #808030;">*</span>op<span style="color: #808030;">.</span>subtend<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> arcEnd <span style="color: #808030;">=</span> angle<span style="color: #808030;">+</span><span style="color: green;">0.5</span><span style="color: #808030;">*</span>op<span style="color: #808030;">.</span>subtend<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> outsideEdge <span style="color: #808030;">=</span> makeCircularArc<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> arcStart<span style="color: #808030;">,</span> arcEnd<span style="color: #808030;">,</span> <span style="color: #008c00;">20</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> insideEdge <span style="color: #808030;">=</span> makeCircularArc<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> radius<span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">,</span> arcStart<span style="color: #808030;">,</span> arcEnd<span style="color: #808030;">,</span> <span style="color: #008c00;">20</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> polygon <span style="color: #808030;">=</span> outsideEdge<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">concat</span><span style="color: #808030;">(</span>insideEdge<span style="color: #808030;">.</span>reverse<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>polygon<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre>And here is a result:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA_h0LCJwXCXhMyT28c-Db-JOr1-L84UpQCCZPwzpHmdYwqVM1s5h25DZt5LUpEptX6Byx5Y_VV9q49n7nIegnaRXcdFe3Bu6cxpDcClUvI_rVDaAbP4smbf2zUgCS-mqkxHlCaJXjUFUv/s196/Image53.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="196" data-original-width="190" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA_h0LCJwXCXhMyT28c-Db-JOr1-L84UpQCCZPwzpHmdYwqVM1s5h25DZt5LUpEptX6Byx5Y_VV9q49n7nIegnaRXcdFe3Bu6cxpDcClUvI_rVDaAbP4smbf2zUgCS-mqkxHlCaJXjUFUv/s0/Image53.png" width="190" /></a></div>This is using 20 points on the edges; that seems to be good enough to give a smooth curve at these sizes. By setting the color to white and placing it immediately behind the ordinal direction labels in this compass, I can now mask out the line:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgEGOtN8bbd21zmOjINGzNuuUW9lUf4uAlovVIsAM60hPjub6d7_g8uqpfMGyeiL0_ksQIp77zmIew6mUdV-ys9oWiB-6iPnDnIrcFV-zKlkw0HvA7MNBh9HNMM_yZ_60ET0J2T7Nc8JAp/s240/Image54.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="240" data-original-width="235" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgEGOtN8bbd21zmOjINGzNuuUW9lUf4uAlovVIsAM60hPjub6d7_g8uqpfMGyeiL0_ksQIp77zmIew6mUdV-ys9oWiB-6iPnDnIrcFV-zKlkw0HvA7MNBh9HNMM_yZ_60ET0J2T7Nc8JAp/s16000/Image54.png" /></a></div><div><span style="background-color: white;"><br /></span></div><div><span style="background-color: white;">A reminder that if you want to follow along from home, you need to download the Part 9 branch from the </span><a href="https://github.com/srt19170/Procedural-Map-Compasses" style="background-color: white;" target="_blank">Procedural Map Compasses repository</a><span style="background-color: white;"> on Github and get the test web page up. 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 9 directory, and then select the test.html link. Obviously you can browse the code and other files, and make changes of your own. </span><span>You can also try out this code on the web at </span><a href="https://dragonsabound-part9.netlify.app/test.html">https://dragonsabound-part9.netlify.app/test.html</a><span> although you'll only be able to run the code, not modify it.</span><span style="background-color: white; white-space: pre;"> </span></div></div><div><br /></div><div><span style="font-weight: 700;">Suggestions to Explore</span><br /><div><ul><li>I've suggested that RARC can be used to draw the alternating black and white scale as used in this compass:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCju1VG0-cAyOZy0umL6x0OHeBA1NfpsbeVUObB6fht_9IJZsSaLSp10xA-5gX9d-LjpBUCJgOskmdJjy-aJqFy6uF8obm5Xf_DIy2XExriNDH_xzNvH2eyR-OAlrMeS44EizuAPUhUJ_l/s401/Image29.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="401" data-original-width="381" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCju1VG0-cAyOZy0umL6x0OHeBA1NfpsbeVUObB6fht_9IJZsSaLSp10xA-5gX9d-LjpBUCJgOskmdJjy-aJqFy6uF8obm5Xf_DIy2XExriNDH_xzNvH2eyR-OAlrMeS44EizuAPUhUJ_l/w190-h200/Image29.png" width="190" /></a></div>Write the CDL for this compass and generate it. How did you handle the lines around the white arcs?<br /><br /></li><li>For map borders, I sometimes generate a scale with three colors:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8aEp9ehyHBcPyMoyxV2nZCWNKZYUVubM88n0fo-XI8UDL06dp6zjFDHLUwNTJOtsm_8uoWvVQ-k4pDfszLIfJyEtOTVlZZ6V-bc4U9Lc6oc2SQ3dD3uM-yvNONbXwmhyphenhyphenmuxBNtW1DKf8f/s600/image.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="150" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8aEp9ehyHBcPyMoyxV2nZCWNKZYUVubM88n0fo-XI8UDL06dp6zjFDHLUwNTJOtsm_8uoWvVQ-k4pDfszLIfJyEtOTVlZZ6V-bc4U9Lc6oc2SQ3dD3uM-yvNONbXwmhyphenhyphenmuxBNtW1DKf8f/s16000/image.png" /></a></div>Create a compass with a three-colored scale with all the colors outlined. How did you handle the lines between the arcs?<br /><br /></li><li>This compass has triangles at the ordinal points on the outer scale that have black and white halves like compass points:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipcUZfeFLF-Kq8qDFOu-Tw9KJa3bK5xZCLIBYXF0xBzpbZhZv9qf1xb1W4blftlibfVYqh_00yiha_htFRUqjBdJETbPt4G9gO_k06xncwurE_nuPEicmhK0_jqI5b7-l1IG6wGWmnShAS/s600/compass11.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="579" data-original-width="600" height="309" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipcUZfeFLF-Kq8qDFOu-Tw9KJa3bK5xZCLIBYXF0xBzpbZhZv9qf1xb1W4blftlibfVYqh_00yiha_htFRUqjBdJETbPt4G9gO_k06xncwurE_nuPEicmhK0_jqI5b7-l1IG6wGWmnShAS/s320/compass11.png" width="320" /></a></div>Implement this as an option in RTRI. Ignoring the hatched shading, can you now recreate the entire compass?</li></ul></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-7907641350806301922021-12-16T17:14:00.000-05:002021-12-16T17:14:51.547-05:00Map Compasses (Part 8): Radial Text<p>Welcome back to my blog series on procedurally-generated map compasses! I've been implementing the Compass Design Language (CDL) and an interpreter for the language. I've been measuring my progress (on the right) against this compass (on the left):</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhn9gShrD6zKCZuqITi4e9pv2BLTNs4j-mNsfufybbWOLbN3b6A_L_kSga2-y28s93oziUIOEYYb310ek_cZo_dRwAMkKogkt4X62ClPp39tZDgsGvUiduwkvGCcwIaC_Pq_EK-A-iT-ORt/s565/Image25.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="320" data-original-width="565" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhn9gShrD6zKCZuqITi4e9pv2BLTNs4j-mNsfufybbWOLbN3b6A_L_kSga2-y28s93oziUIOEYYb310ek_cZo_dRwAMkKogkt4X62ClPp39tZDgsGvUiduwkvGCcwIaC_Pq_EK-A-iT-ORt/s16000/Image25.png" /></a></div>I've made good progress on the graphic elements of the compass, and now I'll tackle the labels.<div><br /></div><div>Like the triangles, hash marks and compass points, the text in this example is a radial element that appears at intervals around the circle of the compass. Unlike the previous radial elements, this element differs at every place, so our command will need to include a list of texts, one for each location around the compass.</div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div><pre style="background: rgb(255, 255, 255); text-align: left;">RTEXT<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> font<span style="color: #808030;">,</span> size<span style="color: #808030;">,</span> color, style<span style="color: #808030;">,</span> weight, texts<span style="color: #808030;">)</span></pre></div></blockquote>Start and repeat are the usual parameters for a radial element that defines where to start and how many repeats to place around the compass. Font, size, color, and style are parameters that will define the font to use and its characteristics (e.g., italic, bold, etc.). The final element is the list of texts to use, one for each repeat. Most of these parameters are similar to what I've implemented in the parser for previous commands, but I've never tried to parse a list in Nearley so it will be interesting to figure that out.<div><br /></div><div>To start with, I'll use brackets to delimit the list, and this will be a list of double-quoted strings:</div><div><pre style="background: rgb(255, 255, 255);"># DQLIST
# A list of double<span style="color: #808030;">-</span>quoted strings<span style="color: #808030;">,</span> possibly empty
dqList <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">[</span><span style="color: maroon;">"</span> WS dqElements WS <span style="color: maroon;">"</span><span style="color: #0000e6;">]</span><span style="color: maroon;">"</span></pre></div><div>The elements can be nothing, a single double-quoted string, or a single double-quoted string followed by a comma and then more elements. </div><div><pre style="background: rgb(255, 255, 255);"># DQLIST
# A list of double<span style="color: #808030;">-</span>quoted strings<span style="color: #808030;">,</span> possibly empty
dqList <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">[</span><span style="color: maroon;">"</span> WS dqElements WS <span style="color: maroon;">"</span><span style="color: #0000e6;">]</span><span style="color: maroon;">"</span>
dqElements <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: #0f4d75;">null</span>
<span style="color: #808030;">|</span> dqstring
<span style="color: #808030;">|</span> dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqElements</pre>You might (if you've taken any classes on grammars) have been discourage from writing rules like that last clause because it does “right recursion" -- that is, the last element in that rule loops back to the rule itself. That causes problems in some grammar parsers, but it turns out that Nearley is fine with this.</div><div><br /></div><div>The grammar above recognizes the list of texts but doesn't return the most useful values. I'll fix that with some post-processing.</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"># DQLIST
# A list of double<span style="color: #808030;">-</span>quoted strings<span style="color: #808030;">,</span> possibly empty
dqList <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">[</span><span style="color: maroon;">"</span> WS dqElements WS <span style="color: maroon;">"</span><span style="color: #0000e6;">]</span><span style="color: maroon;">"</span> <span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> data<span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span>
dqElements <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: #0f4d75;">null</span> <span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">[</span><span style="color: #808030;">]</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span>
<span style="color: #808030;">|</span> dqstring <span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> data <span style="color: #808030;">%</span><span style="color: purple;">}</span>
<span style="color: #808030;">|</span> dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqElements <span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">[</span>data<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> <span style="color: #808030;">.</span><span style="color: #808030;">.</span><span style="color: #808030;">.</span>data<span style="color: #808030;">[</span><span style="color: #008c00;">4</span><span style="color: #808030;">]</span><span style="color: #808030;">]</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span></pre></pre>Mostly this should be familiar from previous blog entries -- the post-processing for all except the last clause just removes an unnecessary level of arrays. The last clause is a little more interesting. The three dots I use there is the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax" target="_blank">spread operator</a>, and essentially what it does is stick in the contents of an array. (It “spreads" out the array so to speak.) In this case, I use it to concatenate together the first element of the list (data[0]) and the list of all the remaining elements (data[4]).<br /></div><div><br /></div><div>With that defined we can create the rule for RTEXT:<br /><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"># Radial text
# RTEXT<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> font<span style="color: #808030;">,</span> size<span style="color: #808030;">,</span> color<span style="color: #808030;">,</span> style<span style="color: #808030;">,</span> weight, texts<span style="color: #808030;">)</span>
rtextElement <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RTEXT</span><span style="color: maroon;">"</span>i WS <span style="color: maroon;">"</span><span style="color: #0000e6;">(</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"
</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">" </span>WS dqList WS <span style="color: maroon;">"</span><span style="color: #0000e6;">)</span><span style="color: maroon;">"</span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">(</span><span style="color: purple;">{</span>op<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RTEXT</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> start<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">4</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> repeats<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">8</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> font<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">12</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
size<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">16</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> color<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">20</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> style<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">24</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span> </span><span> </span><span> </span><span> </span><span> </span><span> </span><span> </span><span> weight: data[28], </span>texts<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">32</span><span style="color: #808030;">]</span><span style="color: purple;">}</span><span style="color: #808030;">)</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span></pre></pre></pre>Now I add RTEXT to the interpreter in the usual way and create a function Draw.rtext that will do the work of drawing the text.</div><div><br /></div><div>Perhaps not unsurprisingly, text is added to SVG by appending an SVG element, i.e., in D3 something like this:</div><div><pre style="background: rgb(255, 255, 255);"> svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">text</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">x</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> x<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">y</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> y<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-family</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span>font<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> color<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-size</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> fontSize<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-style</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> fontStyle<span style="color: #808030;">)
<span> </span><span> <span> </span></span>.style(<span style="color: maroon;">'</span><span style="color: #0000e6;">font-weight</span><span style="color: maroon;">'</span>, fontWeight)</span>
<span style="color: #808030;">.</span>text<span style="color: #808030;">(</span>text<span style="color: #808030;">)</span><span style="color: purple;">;</span></pre>There are many possible attributes and styles that can be applied to SVG text elements but the above covers the ones we'll be using.</div><div><br /></div><div>Let me start the implementation of rtext() by putting some text at noon.</div><div><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="background-color: white; color: maroon; font-weight: bold;">function</span><span style="background-color: white;"> rtext</span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">svg</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> center</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> radius</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> startAngle</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> repeats</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> angle</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> iteration</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> op</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> </span><span style="background-color: white; color: purple;">{</span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="background-color: white;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: maroon; font-weight: bold;">let</span> x <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> y <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">text</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">x</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> x<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">y</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> y<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-family</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>font<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-size</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>size<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-style</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>style<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-weight</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>weight<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>text<span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>texts<span style="color: #808030;">[</span>0<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></span></pre></div>That results in this:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4gYhp6EG5EikfPwJKByPYBDgC9FM8DtFw81DCLP2Dk8u86n7XcmUVMka97lQ-bYYA8PFHGdGJuSPaAMR-Ouc4Y42aIBBdC-t8JhK4xWXL5LLjyE1g_DgXV4XgNAqSj-NkABGA-Ci1T92x/s295/Image39.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="232" data-original-width="295" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4gYhp6EG5EikfPwJKByPYBDgC9FM8DtFw81DCLP2Dk8u86n7XcmUVMka97lQ-bYYA8PFHGdGJuSPaAMR-Ouc4Y42aIBBdC-t8JhK4xWXL5LLjyE1g_DgXV4XgNAqSj-NkABGA-Ci1T92x/s0/Image39.png" width="295" /></a></div>That worked, but it has placed the text so that the lower-left-hand corner of the text box is at the specified [x,y] location. I'm okay with having the bottom of the text at the [x,y] location (or at least I think I am, that might change) but I think I'd prefer that the text be centered on that point. To do this, SVG has a text-anchor attribute which I can set to “middle".<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTgVkb4k-r6FBU5vZs6rFd7cx-hwAXksyuOt9Sh0IK-Z_xRTfmXhRJcs3eclWza8twS-ghOR8ToGwytQrTLjc5BZIVVs8OddyOmdL29u-RST51IO21ilNSd6KwE5QgYxrTFoaPi38sMS6v/s292/Image40.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="232" data-original-width="292" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTgVkb4k-r6FBU5vZs6rFd7cx-hwAXksyuOt9Sh0IK-Z_xRTfmXhRJcs3eclWza8twS-ghOR8ToGwytQrTLjc5BZIVVs8OddyOmdL29u-RST51IO21ilNSd6KwE5QgYxrTFoaPi38sMS6v/s0/Image40.png" width="292" /></a></div>Now let's move on to the stickier issue of placing text somewhere other than 12. I'd like to use the same “draw at 12 and rotate to the correct position" strategy I've used for most of the other radial elements, but text is obviously not a polygon that I can go through and rotate the points with math. The good news is that SVG itself has a method for rotating text (and many other elements). <div><br /></div><div>SVG rotate is part of the SVG transform attribute, which also offers other transformations like scaling and translation (move). The syntax for a rotate transformation is simple:</div><div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div style="text-align: left;"><pre>rotate(degrees, x, y)</pre></div></blockquote><p>This rotates an object by degrees around the point [x, y]. Let me add that to the rtext function:</p><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rtext<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> startAngle<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> iteration<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> x <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> y <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">text</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">x</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> x<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">y</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> y<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-family</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>font<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-size</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>size<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-style</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>style<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-weight</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>weight<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">text-anchor</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">middle</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">transform</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">rotate(</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>rad2degrees<span style="color: #808030;">(</span>angle<span style="color: #808030;">-</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">,</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">,</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">)</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>text<span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>texts<span style="color: #808030;">[</span>0<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioIbVsQNOCsp3W_8xvRAf5TvpU6Rzx2Yz4QEgbOC2IW9AvNlW6td-R7ce7ZO3oxanTp75t9fUmSmkhVJeo8wCogMNN-OwcYSh9-Ekgf-V23739_nXcSwaRKwsEl1VvOz-Y4-UnptgUvaGL/s330/Image41.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="266" data-original-width="330" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioIbVsQNOCsp3W_8xvRAf5TvpU6Rzx2Yz4QEgbOC2IW9AvNlW6td-R7ce7ZO3oxanTp75t9fUmSmkhVJeo8wCogMNN-OwcYSh9-Ekgf-V23739_nXcSwaRKwsEl1VvOz-Y4-UnptgUvaGL/s320/Image41.png" width="320" /></a></div>Notice I needed a helper function “rad2degrees" because my angles are in radians and the SVG command expects degrees.</div><div><br /></div><div>The other thing you might note is that the text at every compass point is “N". That's because I'm just using text[0] in the SVG command. What I need to do is use the text corresponding to which iteration of the label I'm currently writing. That's the reason I've been passing in the iteration parameter:</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rtext<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> startAngle<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> iteration<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> x <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> y <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">text</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">x</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> x<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">y</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> y<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-family</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>font<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-size</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>size<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-style</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>style<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">font-weight</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>weight<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">text-anchor</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">middle</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">transform</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">rotate(</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>rad2degrees<span style="color: #808030;">(</span>angle<span style="color: #808030;">-</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">,</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">,</span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;">)</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>text<span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>texts<span style="color: #808030;">[</span>iteration<span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre></div><div>And with that in place we can stress test the code:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgV5d5ym3gwdKkFp5NlSNepHOatHwNWkO-Of9Bq62KX5fgnOs-QW3ODgew_KdcEqk3_L9qLBu-U7UvYFLQ6lNasvxMceFbCEqhwwsIWXGesEUqcosrPDEia3S-ZhhF1xZLndbBacEEhsuks/s617/Image25.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="320" data-original-width="617" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgV5d5ym3gwdKkFp5NlSNepHOatHwNWkO-Of9Bq62KX5fgnOs-QW3ODgew_KdcEqk3_L9qLBu-U7UvYFLQ6lNasvxMceFbCEqhwwsIWXGesEUqcosrPDEia3S-ZhhF1xZLndbBacEEhsuks/s16000/Image25.png" /></a></div><br /><div class="separator" style="clear: both; text-align: left;">I haven't fussed too much with matching the fonts, etc., but for the most part I'm able to recreate the labels. One obvious problem is that the line isn't blanked out behind the ordinal labels -- I'll look at that and some other challenges next time. (And see “Suggestions to Explore" below.)</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><div><span style="background-color: white;">A reminder that if you want to follow along from home, you need to download the Part 8 branch from the </span><a href="https://github.com/srt19170/Procedural-Map-Compasses" style="background-color: white;" target="_blank">Procedural Map Compasses repository</a><span style="background-color: white;"> 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 8 directory, and then select the test.html link. Obviously you can browse the code and other files, and make changes of your own. </span><span>You can also try out this code on the web at </span><a href="https://dragonsabound-part8.netlify.app/test.html">https://dragonsabound-part8.netlify.app/test.html</a><span> although you'll only be able to run the code, not modify it.</span><span style="background-color: white; white-space: pre;"> </span></div><div><span style="background-color: white; white-space: pre;"><br /></span></div></div><div><br /></div><div><span style="font-weight: 700;">Suggestions to Explore</span><br /><div><ul style="text-align: left;"><li>How would you address the problem of blanking out behind the ordinal labels? One possibility is to have an radial rectangle element (“RRECT"?) and use it to draw white rectangles where the labels will be. Can you use RRECT to draw the black and white ring in this compass?<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1cQprS6ibADdBF_WhTIo-xFn-TXzdnRBNuAq2GoHWlGw7pTucfJZikHogKFz4lSYq4q_StfMbpPjGJOb5mFULFN90KMkz7fJJC990qtfJNEoqkfYB4i5eXghliDhHCqj1HCQ2Y11D4BM-/s401/Image29.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="401" data-original-width="381" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1cQprS6ibADdBF_WhTIo-xFn-TXzdnRBNuAq2GoHWlGw7pTucfJZikHogKFz4lSYq4q_StfMbpPjGJOb5mFULFN90KMkz7fJJC990qtfJNEoqkfYB4i5eXghliDhHCqj1HCQ2Y11D4BM-/w190-h200/Image29.png" width="190" /></a></div><div class="separator" style="clear: both; text-align: center;"><div style="text-align: left;">These rectangles aren't really rectangles -- they're arcs of a line instead. How will you draw that?<br /><br /></div></div></li><li><div style="text-align: left;">In the example compass directly above, the cardinal labels are in the correct positions, but the text has *not* been rotated. Implement a version of RTEXT that puts the text in the correct position but does not rotate the text. For the rotated version of RTEXT, the “anchor" of the text was at the bottom center of the text. Where should the “anchor" of the unrotated text be located?<br /><br /></div></li><li><div style="text-align: left;">In the stress test example above, I used the “Serif" and “Helvetica" fonts. Both these are generally available in the browser. What if you want to use a font that's not normally available? How do you load a font into the browser and use it in SVG? Can you write a CDL command that loads a font and makes it available?</div></li></ul></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-86445291600864886622021-12-10T09:39:00.000-05:002021-12-10T09:39:52.765-05:00Map Compasses (Part 7): Compass Points<p>Welcome back to my blog series on implementing procedurally-generated map compasses! So far I've implemented the language and interpreter for circles, space, and radial lines, circles and triangles, as shown in this demo image below:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho_ThwSLr4XmBhFQfbDdPMXKoolUSSW_gTgiLpS4Mc_6aBn5ps12-ZiPk1NAcGfDb9P8swdz106Tg6dkjXuUUiVlNbmL9Wxs4DQ2zF0C1Xc0pr9_-MGPRSM3HPBIzd6-uGtaOl9S_QTYPU/s225/Image31.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="224" data-original-width="225" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho_ThwSLr4XmBhFQfbDdPMXKoolUSSW_gTgiLpS4Mc_6aBn5ps12-ZiPk1NAcGfDb9P8swdz106Tg6dkjXuUUiVlNbmL9Wxs4DQ2zF0C1Xc0pr9_-MGPRSM3HPBIzd6-uGtaOl9S_QTYPU/s0/Image31.png" width="225" /></a></div>This time I'm going to tackle compass points. These are the two-sided diamond-shaped elements that typically point out to the points of the compass:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje75-BqvKep_t1mGKEt4zjCtPOkLHQAJmdLcRxkODspC61WIpxWAQ_yNrb1U2lYO9XsMopbVHVx0zC0SlxL-kdsUM2mZyp099tx0SmUCCXnzEKxrpJvfg-lwxzjeFFD1HsNfDAMILlM9UW/s425/compass.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="424" data-original-width="425" height="319" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje75-BqvKep_t1mGKEt4zjCtPOkLHQAJmdLcRxkODspC61WIpxWAQ_yNrb1U2lYO9XsMopbVHVx0zC0SlxL-kdsUM2mZyp099tx0SmUCCXnzEKxrpJvfg-lwxzjeFFD1HsNfDAMILlM9UW/s320/compass.jpg" width="320" /></a></div>Like the lines and triangles, the compass points are a radial element that repeats around the circle. As in the example above, there are often multiple points, the largest pointing at the <a href="https://en.wikipedia.org/wiki/Cardinal_direction" target="_blank">cardinal directions</a> (N, S, E, W), a shorter set of points at the ordinal directions (e.g., NE), a shorter set still pointing at the secondary intercardinal directions (e.g., NNE) and so on.<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbR7bvqVQgSWiE94SYYGPfcjsw3eRJej5UNBP7dNgKKcD3tPe03DLYNplINgJJKsxCUmkvz0Y2quuq0LaWqTvsacUwokuy3mwzTFcCd3IaM4GHoAgtFHJPXgWtYdb7-U3cjsyavcKfIP6o/s452/Image37.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="444" data-original-width="452" height="314" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbR7bvqVQgSWiE94SYYGPfcjsw3eRJej5UNBP7dNgKKcD3tPe03DLYNplINgJJKsxCUmkvz0Y2quuq0LaWqTvsacUwokuy3mwzTFcCd3IaM4GHoAgtFHJPXgWtYdb7-U3cjsyavcKfIP6o/s320/Image37.png" width="320" /></a></div>Regardless of the number, these are all versions of the same basic shape set at different intervals around the compass.<div><br /></div><div>Typically the compass points are shaded as in the two examples above -- split down the middle with the right side dark and the left side light. This is probably a remnant of picturing the compass points as 3D with lighting coming from the same direction as on the map (i.e., typically from the NE) but has largely become stylized. You do sometimes see compasses where the shaded and lit sides of the points switch to more accurately reflect the lighting, or where the lighting comes from a different direction, as in this example: </div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjou3j4RozJjt4Dv8jup1OhhvBQrke4ERWBzJe7y2ritYs2Z3quNRkt41Dp4vDntRtUwFmBrFFP9GFkFeLh7-sjmAX2d2iLHwNCfmz4uDlXkOQ8Aoyc7vmbpHKm3CqJXJ0AWwdJ7JmoC26M/s416/compass4.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="416" data-original-width="378" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjou3j4RozJjt4Dv8jup1OhhvBQrke4ERWBzJe7y2ritYs2Z3quNRkt41Dp4vDntRtUwFmBrFFP9GFkFeLh7-sjmAX2d2iLHwNCfmz4uDlXkOQ8Aoyc7vmbpHKm3CqJXJ0AWwdJ7JmoC26M/s320/compass4.png" width="291" /></a></div>But the more stylized version seems more common today, and that's what I'll implement (at least initially).<div><br /></div><div> Unlike the other radial elements, compass points always extend all the way to the center of the compass, so the overall length will be decided by that distance. (Which I've called the radius.) The distance from the tip of the point to the widest point of the diamond (which I'm calling the span) can be any value less than the radius, and the width is the width of the diamond at that point:</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyjIvxprAn6b53qkPOAm4TGQOeWtUg2chh1TjM-uDW9hPMOQG2-MJKsl7vyY8xDZ2qeOevVJA7ys6x2Yf6YDHI-tbm46Hc_Yr2aOSc0wqY0Wr961oGyMODA19YlkzPV2XUqYY8WpA9G31b/s391/image.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="391" data-original-width="351" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyjIvxprAn6b53qkPOAm4TGQOeWtUg2chh1TjM-uDW9hPMOQG2-MJKsl7vyY8xDZ2qeOevVJA7ys6x2Yf6YDHI-tbm46Hc_Yr2aOSc0wqY0Wr961oGyMODA19YlkzPV2XUqYY8WpA9G31b/s320/image.png" width="287" /></a></div><div>But the width can't be just any distance. If the compass points are on top and visible, then we want the width to be so that the point just touches the adjacent point, as in the top points in this compass:</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxGMWYmlJlUxl6bfsHViTZ4z6ohajKyB7deeXQy8_EiRrOMdsoNUiVlxWoCvO4Nm3Iy-WlEasyy7oQsa9XRFWw9qgX1cF5gWHTZ7XHiTBJ1td8M5ZpAfxudUHiYraZxDveHpUb0Jdr6AzB/s440/compass14.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="415" data-original-width="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxGMWYmlJlUxl6bfsHViTZ4z6ohajKyB7deeXQy8_EiRrOMdsoNUiVlxWoCvO4Nm3Iy-WlEasyy7oQsa9XRFWw9qgX1cF5gWHTZ7XHiTBJ1td8M5ZpAfxudUHiYraZxDveHpUb0Jdr6AzB/s16000/compass14.jpg" /></a></div>The width should never be greater than that, because that would result in asymmetry depending on the order in which the points are drawn. Sometimes the width can be less, though, as in the points in this compass:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje75-BqvKep_t1mGKEt4zjCtPOkLHQAJmdLcRxkODspC61WIpxWAQ_yNrb1U2lYO9XsMopbVHVx0zC0SlxL-kdsUM2mZyp099tx0SmUCCXnzEKxrpJvfg-lwxzjeFFD1HsNfDAMILlM9UW/s425/compass.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="424" data-original-width="425" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje75-BqvKep_t1mGKEt4zjCtPOkLHQAJmdLcRxkODspC61WIpxWAQ_yNrb1U2lYO9XsMopbVHVx0zC0SlxL-kdsUM2mZyp099tx0SmUCCXnzEKxrpJvfg-lwxzjeFFD1HsNfDAMILlM9UW/s16000/compass.jpg" /></a></div>This could be drawn several ways, but in any case the width does not extend all the way to touch the neighboring points, so that there's a “gap" through which the other points show.</div><div><br /></div><div><div>I don't want to have to calculate the proper width to touch the adjacent compass point, so I'll let the interpreter do all the hard work. I'll specify the width of compass point as a percentage from 0 to 1, where 1 is the proper width to touch the adjacent compass point. Using a value less than 1 will create a gap. But to give myself a little flexibility in case I do want to specify the width, I'll treat any width value greater than 1 as an absolute width.</div><div><br /></div><div>This seems like a handy approach, so I'll do the same with the span parameter, so that it can be expressed as either a percentage of the radius or a specific value.<br /><div class="separator" style="clear: both; text-align: center;"><br /></div>Now let me define the point command. In addition to the normal radial element parameters, I need to specify the span, the width, the line width and color of the outline, and the light and dark fill colors:</div></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div style="text-align: left;"><pre style="background: rgb(255, 255, 255);">RPOINT<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> span<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> lineWidth<span style="color: #808030;">,</span> lineColor<span style="color: #808030;">,</span> lightFill<span style="color: #808030;">,</span> darkFill<span style="color: #808030;">)</span></pre></div></blockquote><div><div>and here's the corresponding parse rule in Nearley:</div><div><pre style="background: rgb(255, 255, 255);"># A radial compass point element
# RPOINT<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> span<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> lineWidth<span style="color: #808030;">,</span> lineColor<span style="color: #808030;">,</span> lightFill<span style="color: #808030;">,</span> darkFill<span style="color: #808030;">)</span>
rpointElement <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RPOINT</span><span style="color: maroon;">"</span>i WS <span style="color: maroon;">"</span><span style="color: #0000e6;">(</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqstring WS<span style="color: maroon;">"</span><span style="color: #0000e6;">)</span><span style="color: maroon;">"</span> WS
<span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">(</span><span style="color: purple;">{</span>op<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RPOINT</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> start<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">4</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> repeats<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">8</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> span<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">12</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
width<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">16</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> lwidth<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">20</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> lcolor<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">24</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
lightFill<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">28</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> darkFill<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">28</span><span style="color: #808030;">]</span><span style="color: purple;">}</span><span style="color: #808030;">)</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span></pre></div><div>and the corresponding if-else clause in the interpreter:</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> } <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">RPOINT</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span><br /> <span style="color: dimgrey;">// Draw radial compass points</span>
Draw<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">repeat</span><span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>start<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>repeats<span style="color: #808030;">,</span> op<span style="color: #808030;">,</span> Draw<span style="color: #808030;">.</span>rpoint<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Draw radial compass points.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span></pre></pre></div><div>Now I have to write the code to actually draw the compass point.</div><div><br /></div><div>As I did with the radial triangles, in order to simplify my calculations, I'll draw all the compass points aligned straight up and down, and then rotate them to their final positions. The first step is to calculate the start and end of the diamond and the midpoint:</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rpoint<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> startAngle<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> start <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> end <span style="color: #808030;">=</span> center<span style="color: purple;">;</span>
<span style="color: dimgrey;">// Midpoint is the point on the center line where the</span>
<span style="color: dimgrey;">// diamond will have its maximum width</span>
<span style="color: maroon; font-weight: bold;">let</span> midpoint<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>span <span style="color: #808030;"><=</span> <span style="color: #008c00;">1</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Treat span as a % of radius</span>
op<span style="color: #808030;">.</span>span <span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>span<span style="color: #808030;">*</span>radius<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
midpoint <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span></pre></pre></pre></div><div>Here you can see how I treat span as a percentage if it is <= 1.</div><div><br /></div><div>Figuring out the width is a little more difficult, and requires some trigonometry. I'm not very skilled at this sort of problem, but I have an approach that works for me -- I send it to my kids, who both have math degrees. A few minutes later I get this back:</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1FKLcm7iIkKSStupKNZyiAsoWWl5AjgkwolZtjWA5xjCkPLYaR0VESPjPNtlhUZ7-So9pz4nB-yk99diGD5aV6UL9761z8dYUuRoxDzy7_RX2jzZfzkYG3_rgWizBpHBzHPpCqy66CtP5/s1600/79643c0f-873c-4ed9-919e-3a5957e27e86.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1600" data-original-width="1200" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1FKLcm7iIkKSStupKNZyiAsoWWl5AjgkwolZtjWA5xjCkPLYaR0VESPjPNtlhUZ7-So9pz4nB-yk99diGD5aV6UL9761z8dYUuRoxDzy7_RX2jzZfzkYG3_rgWizBpHBzHPpCqy66CtP5/w480-h640/79643c0f-873c-4ed9-919e-3a5957e27e86.jpg" width="480" /></a></div><div class="separator" style="clear: both; text-align: left;">Here <span face=""open sans", sans-serif" style="background-color: white; color: #4a4a4a; font-size: 20px; text-align: center;">Φ</span> (phi) is the angle between two adjacent compass points. This isn't the “angle" that gets passed into RPOINT -- that's the orientation of the compass point being drawn. Phi is calculated from the number of repeats that are being drawn -- if there are 4 repeats, then the angle between two adjacent compass points is 90 degrees (or PI/2 radians). (This is why I've been passing in repeats even though I didn't need it in the previous radial elements.)</div></div></div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rpoint<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> startAngle<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> start <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> end <span style="color: #808030;">=</span> center<span style="color: purple;">;</span>
<span style="color: dimgrey;">// Midpoint is the point on the center line where the</span>
<span style="color: dimgrey;">// diamond will have its maximum width</span>
<span style="color: maroon; font-weight: bold;">let</span> midpoint<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>span <span style="color: #808030;"><=</span> <span style="color: #008c00;">1</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Treat span as a % of radius</span>
op<span style="color: #808030;">.</span>span <span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>span<span style="color: #808030;">*</span>radius<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
midpoint <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// The maximum width is one that just touches the adjacent point.</span>
<span style="color: dimgrey;">// Phi is the angle between adjacent points.</span>
<span style="color: maroon; font-weight: bold;">const</span> phi <span style="color: #808030;">=</span> <span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> maxWidth <span style="color: #808030;">=</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">tan</span><span style="color: #808030;">(</span>phi<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">*</span><span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">)</span><span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: purple;">;</span></pre></pre></div><div>Now I can calculate the two side points of the diamond using the midpoint and the maxWidth. For testing purposes I can temporarily borrow some code from RTRI to rotate the points as necessary and draw the compass point:</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rpoint<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> startAngle<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> start <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> end <span style="color: #808030;">=</span> center<span style="color: purple;">;</span>
<span style="color: dimgrey;">// Midpoint is the point on the center line where the</span>
<span style="color: dimgrey;">// diamond will have its maximum width</span>
<span style="color: maroon; font-weight: bold;">let</span> midpoint<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>span <span style="color: #808030;"><=</span> <span style="color: #008c00;">1</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Treat span as a % of radius</span>
op<span style="color: #808030;">.</span>span <span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>span<span style="color: #808030;">*</span>radius<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
midpoint <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// The maximum width is one that just touches the adjacent point.</span>
<span style="color: dimgrey;">// Phi is the angle between adjacent points.</span>
<span style="color: maroon; font-weight: bold;">const</span> phi <span style="color: #808030;">=</span> <span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">*</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> maxWidth <span style="color: #808030;">=</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">tan</span><span style="color: #808030;">(</span>phi<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">*</span><span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">)</span><span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Calculate the two side points</span>
<span style="color: maroon; font-weight: bold;">let</span> side1 <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>maxWidth<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> side2 <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>maxWidth<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Rotate the diamond points.</span>
start <span style="color: #808030;">=</span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
end <span style="color: #808030;">=</span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> end<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
side1 <span style="color: #808030;">=</span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> side1<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
side2 <span style="color: #808030;">=</span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> side2<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Draw the outline</span>
<span style="color: maroon; font-weight: bold;">let</span> p <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>start<span style="color: #808030;">,</span> side1<span style="color: #808030;">,</span> end<span style="color: #808030;">,</span> side2<span style="color: #808030;">]</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lcolor<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>p<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
</pre></pre></div><div>Which produces this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizPLhOPzPSS7M5_iHkC50s9Ry2FO6ehkWRPQ2JmLsW9iI5Oiq63rdD20R-FtcVsFo5XCvao4RAgcvGBaUyTgGs2DfNFTarAoJjD6Dt5grrSzAXKY3FfUqjdF8npM9x_1ONNVEq6updG7JZ/s298/Image34.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="236" data-original-width="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizPLhOPzPSS7M5_iHkC50s9Ry2FO6ehkWRPQ2JmLsW9iI5Oiq63rdD20R-FtcVsFo5XCvao4RAgcvGBaUyTgGs2DfNFTarAoJjD6Dt5grrSzAXKY3FfUqjdF8npM9x_1ONNVEq6updG7JZ/s16000/Image34.png" /></a></div>Looks like it worked!<div><br /></div><div>There are a couple of things left to do. First of all, I need to calculate the actual width rather than always use the maxWidth, and use this width to calculate the side points of the diamond:</div><div><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: dimgrey;">// Calculate the actual width based on width</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span><span>op.</span>width <span style="color: #808030;"><=</span> <span style="color: #008c00;">1</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
op.width <span style="color: #808030;">=</span> op.width<span style="color: #808030;">*</span>maxWidth<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Calculate the two side points</span>
<span style="color: maroon; font-weight: bold;">let</span> side1 <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span><span>op.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> side2 <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span><span>op.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>span<span style="color: #808030;">]</span><span style="color: purple;">;</span></pre></pre></div><div>As with span, we treat the width parameter as a percentage of the maxWidth if it is less than 1; otherwise we just use it as an absolute value.</div><div><br /></div><div>Finally, the dark and light sides of the compass also need to get filled. To do this I'll make two new polygons consisting of one side of the diamond and then a line to connect the start and end of the diamond. I'll fill these with the provided colors and then draw the outline last so that it's on top.</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: dimgrey;">// Create and fill the left (light) side area</span>
<span style="color: maroon; font-weight: bold;">const</span> left <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>start<span style="color: #808030;">,</span> side1<span style="color: #808030;">,</span> end<span style="color: #808030;">]</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lightFill<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>left<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Create and fill the right (dark) side area</span>
<span style="color: maroon; font-weight: bold;">const</span> right <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>start<span style="color: #808030;">,</span> side2<span style="color: #808030;">,</span> end<span style="color: #808030;">]</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> darkFill<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>right<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Draw the outline</span>
<span style="color: maroon; font-weight: bold;">let</span> p <span style="color: #808030;">=</span> <span style="color: #808030;">[</span>start<span style="color: #808030;">,</span> side1<span style="color: #808030;">,</span> end<span style="color: #808030;">,</span> side2<span style="color: #808030;">]</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lwidth<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lcolor<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">none</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>p<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></div><div>And that's basically it:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzPUE8JwcK4IbwEcOz9NJYDESq7oK5-tOJ2Ws_bYq6FywQ6MeTF95aCbRcFBxrfcxWiphgdoFJd-ZdJ44RcRE5BkJcaoyGtxIYYhH6v01qLKByfsY_3I4c_2Mk98UN-VqkO2ON2oUJFKcV/s296/Image35.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="234" data-original-width="296" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzPUE8JwcK4IbwEcOz9NJYDESq7oK5-tOJ2Ws_bYq6FywQ6MeTF95aCbRcFBxrfcxWiphgdoFJd-ZdJ44RcRE5BkJcaoyGtxIYYhH6v01qLKByfsY_3I4c_2Mk98UN-VqkO2ON2oUJFKcV/s0/Image35.png" width="296" /></a></div><div>And now I can add compass points to my test compass recreation:</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWg0FhGmWO9JkCovmEBGOuwtsXUkTJyU2Z1H36qSsnRhBQhUARVr3KT1BQpxJcFAzbViz8XI0-zqE8em52UKAobktERYb3wN57RTyPiDM7xzZxbHB1gfPvmTPTfaeBOlscmxroHC6wK-li/s565/Image25.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="320" data-original-width="565" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWg0FhGmWO9JkCovmEBGOuwtsXUkTJyU2Z1H36qSsnRhBQhUARVr3KT1BQpxJcFAzbViz8XI0-zqE8em52UKAobktERYb3wN57RTyPiDM7xzZxbHB1gfPvmTPTfaeBOlscmxroHC6wK-li/s16000/Image25.png" /></a></div>We're getting there! Next time I'll tackle labels and then this example compass at least will be complete.</div><div><br /></div><div><span style="background-color: white;">A reminder that if you want to follow along from home, you need to download the Part 7 branch from the </span><a href="https://github.com/srt19170/Procedural-Map-Compasses" style="background-color: white;" target="_blank">Procedural Map Compasses repository</a><span style="background-color: white;"> 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 7 directory, and then select the test.html link. Obviously you can browse the code and other files, and make changes of your own. </span><span>You can also try out this code on the web at </span><a href="https://dragonsabound-part7.netlify.app/test.html">https://dragonsabound-part7.netlify.app/test.html</a><span> although you'll only be able to run the code, not modify it.</span><span style="background-color: white; white-space: pre;"> </span></div><div><span style="background-color: white;"><br /></span></div><div><span style="background-color: white;">Let me know in a comment or an email if you're making use of the code at all!</span></div><div><span style="background-color: white;"><br /></span></div><h4 style="text-align: left;">Suggestions to Explore</h4><div><ul style="text-align: left;"><li>I've only implemented the standard “dark on the right" color scheme for compass points. You can easily implement “dark on left" by swapping the fill colors in the RPOINT command. Shading based on the light direction is more challenging. Extend the code to add “shaded based on the lighting direction" as discussed above. Add a parameter to the RPOINT command to indicate whether to shade “fixed" or a number that represents the angle of lighting in degrees. How do you handle a parameter where the value can be a string or a number?<br /><br /></li><li>In the example compass, the ordinal compass points are smaller and sit “between" the cardinal compass points. If the widest point of the ordinal compass points doesn't land exactly on the cardinal compass points, the compass looks wrong:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHDhxrnuFVdAGgTjCMo02bFwVIR5vzYalayQ8FsZcQC0BaUwvIi_Zt_L5AWBA1TbrRY-D5iQzthoWIFYkkZpMs2ZMSMHUNzluiZxBdUj_vDoLuJPRjMtfQy-H-MHEyyk7mVofDZmGMxMpR/s199/Image38.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="198" data-original-width="199" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHDhxrnuFVdAGgTjCMo02bFwVIR5vzYalayQ8FsZcQC0BaUwvIi_Zt_L5AWBA1TbrRY-D5iQzthoWIFYkkZpMs2ZMSMHUNzluiZxBdUj_vDoLuJPRjMtfQy-H-MHEyyk7mVofDZmGMxMpR/s0/Image38.png" width="199" /></a></div>I made the example work by trial-and-error to get the correct values for the span and width of the ordinal compass points. That won't work when I'm using procedural generation. How would you change the code to avoid this trial and error?<br /><br /></li><li>Several of the example compasses in the repository shade the points using lines, as in this example:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQPOHZnLdLOdEBaxOXWjIORmdbK3IYz9h3rLlLNd0FdfFJmnv_8IKsZEV3ymV_W_99eEaZ0Y08dgC3nzxYn2L9nrU2g65a9VkwayRUzaZoyIm5qgYeUOyxhrOc88LgjczbYi3BPCBIIzrR/s671/Image36.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="670" data-original-width="671" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQPOHZnLdLOdEBaxOXWjIORmdbK3IYz9h3rLlLNd0FdfFJmnv_8IKsZEV3ymV_W_99eEaZ0Y08dgC3nzxYn2L9nrU2g65a9VkwayRUzaZoyIm5qgYeUOyxhrOc88LgjczbYi3BPCBIIzrR/w200-h200/Image36.png" width="200" /></a></div>How would you implement this?</li></ul></div><div><br /><div><br /></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com3tag:blogger.com,1999:blog-3367557180796013182.post-16309017381490245332021-12-02T10:30:00.000-05:002021-12-02T10:30:43.856-05:00Map Compasses (Part 6): Radial Triangles<p>Welcome back to my blog series on implementing procedurally-generated map compasses! So far I've implemented the language and interpreter for circles, space, and radial lines and radial circles. Let's see what other capabilities we'll need to draw some compasses:
</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje75-BqvKep_t1mGKEt4zjCtPOkLHQAJmdLcRxkODspC61WIpxWAQ_yNrb1U2lYO9XsMopbVHVx0zC0SlxL-kdsUM2mZyp099tx0SmUCCXnzEKxrpJvfg-lwxzjeFFD1HsNfDAMILlM9UW/s425/compass.jpg" style="margin-left: 1em; margin-right: 1em;">
<img border="0" data-original-height="424" data-original-width="425" height="319" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje75-BqvKep_t1mGKEt4zjCtPOkLHQAJmdLcRxkODspC61WIpxWAQ_yNrb1U2lYO9XsMopbVHVx0zC0SlxL-kdsUM2mZyp099tx0SmUCCXnzEKxrpJvfg-lwxzjeFFD1HsNfDAMILlM9UW/s320/compass.jpg" width="320" />
</a>
</div>I can already do a surprising amount of this compass. There are only three things left to do: the triangles that mark the 45 degree angles, the two-toned diamonds that form the arms of the compass, and the labels. I'll start with the triangles.
<div>
<br />
</div>
<div>Radial triangles are a like radial circles with one important distinction: they're not symmetrical. When I drew the radial circles, the angle where I drew them was unimportant, since circles are symmetrical and look the same no matter how they are rotated. That's not going to be true for triangles. I'll have to rotate them to match the angle at which they are drawn.
</div>
<div>
<br />
</div>
<div>Initially this seems a little daunting. I can figure out the math to draw a triangle pointing North. But figuring out how to draw one pointing NE (or some other random angle) is going to take some thought. Fortunately for me, there's a simpler workaround: draw the triangle pointing North and then rotate it around the center of the compass to the desired angle.
</div>
<div>
<br />
</div>
<div>I'll start by defining a command for radial triangles:
</div>
<blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;">
<div style="text-align: left;">
<pre style="background: rgb(255, 255, 255);">RTRI<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> height<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> lineWidth<span style="color: #808030;">,</span> lineColor<span style="color: #808030;">,</span> fillColor<span style="color: #808030;">)</span>
</pre>
</div>
</blockquote>
<div>
<p>This is similar to RCIRCLE, although here I need both a height and a width. (All the radial element commands will start with R.) And here's the Nearley rule to parse that:</p><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"># A radial triangle element
# RTRI<span style="color: #808030;">(</span>start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> height<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> lineWidth<span style="color: #808030;">,</span> lineColor<span style="color: #808030;">,</span> fillColor<span style="color: #808030;">)</span>
rtriElement <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RTRI</span><span style="color: maroon;">"</span>i WS <span style="color: maroon;">"</span><span style="color: #0000e6;">(</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span>
WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">)</span><span style="color: maroon;">"</span> WS
<span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">(</span><span style="color: purple;">{</span>op<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RTRI</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> start<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">4</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> repeats<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">8</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
height<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">12</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> width<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">16</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
lwidth<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">20</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> color<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">24</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
fill<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">28</span><span style="color: #808030;">]</span><span style="color: purple;">}</span><span style="color: #808030;">)</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span></pre></pre>
</div>
<div>Now I have to add an if-else clause to the interpreter to pick up this operator and call a handler function:
</div>
<div>
<pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> } <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">RTRI</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span><br /> <span style="color: dimgrey;">// Draw radial triangles</span>
Draw<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">repeat</span><span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>start<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>repeats<span style="color: #808030;">,</span> op<span style="color: #808030;">,</span> Draw<span style="color: #808030;">.</span>rtri<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Draw radial triangles.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span></pre></pre></div><div>And so the signature of the rtri function is:
</div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div><pre style="background: rgb(255, 255, 255); text-align: left;"><span style="color: maroon; font-weight: bold;">function</span> rtri<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span></pre></div></blockquote>
<div>
<div>
<pre style="background: rgb(255, 255, 255); font-family: sans-serif;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: "Times New Roman"; white-space: normal;">Which is the same signature as all the radial element functions. Just a reminder of these parameters: Center is the center of the compass. Radius is the distance from the center to the base of the triangle. Start is the starting angle for the repeats. Repeats is the number of repeats around the compass. Angle is the angle of this repeat. I is which iteration of the repeats this call is. And op is the original RTRI command.</span></pre></pre>
<pre style="background: rgb(255, 255, 255); font-family: sans-serif;"><span style="font-family: "Times New Roman"; white-space: normal;">In the radial line element, height was measured down towards the center of the compass. I'll keep a consistent interpretation of height here, but I expect most triangles will point away from the center, meaning they'll have a negative height. So this means that the end point is just the triangle height closer to the center.</span></pre>
<pre style="background: rgb(255, 255, 255); font-family: sans-serif;"><span style="font-family: "Times New Roman"; white-space: normal;">Here's a diagram that will hopefully clarify (remember I'm drawing all the triangles as if they point North):</span></pre><pre style="background: rgb(255, 255, 255);"><div class="separator" style="clear: both; font-family: sans-serif; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgY_ZrMgAsLLEn3oh4F2_bcyWHu2Ll6_Ae38M2A8pCXJjceyA8Pq3DEGaHaSkI_1fchzYbBz593z-zcWenZ0IomyjOgL317ZlSLlZxSu-sAIDiVugEiYwMmKfw8GkX3pLU3HQxUCGTW-Mjg/s401/Image20.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="387" data-original-width="401" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgY_ZrMgAsLLEn3oh4F2_bcyWHu2Ll6_Ae38M2A8pCXJjceyA8Pq3DEGaHaSkI_1fchzYbBz593z-zcWenZ0IomyjOgL317ZlSLlZxSu-sAIDiVugEiYwMmKfw8GkX3pLU3HQxUCGTW-Mjg/s16000/Image20.png" /></a></div><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Both start and end are directly above the center point. Here are the calculations for that:</span></span>
</pre></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: sans-serif;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background: rgb(255, 255, 255);"> <span style="color: maroon; font-weight: bold;">function</span> <span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> start<span style="color: #808030;">=</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span><span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">==</span><span style="color: #008c00;">0</span><span style="color: #808030;">)</span> op<span style="color: #808030;">.</span>height <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> end<span style="color: #808030;">=</span><span style="color: #808030;">[</span>start<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre></pre><pre style="background: rgb(255, 255, 255); font-family: sans-serif;"><span style="font-family: "Times New Roman"; white-space: normal;">However, this isn't quite what I want. The start point is in the center of the base of the triangle, so I don't actually need that. What I need are the points to the left and right, like this:</span></pre><pre style="background: rgb(255, 255, 255); font-family: sans-serif;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwZH-yoWc8Rx2W2_CMueZnj0uVyeQJnMkjSOXBnNH7ijCcX8f5IJ1ttLGbQRcdR3F5QbxP_H6EiPTdt4IH2NkQXLxovMUeJ0gI7wCvZJLKFP3c-OC85n-QxHz52MDoTNSX3MSezycL2kl8/s546/Image21.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="352" data-original-width="546" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwZH-yoWc8Rx2W2_CMueZnj0uVyeQJnMkjSOXBnNH7ijCcX8f5IJ1ttLGbQRcdR3F5QbxP_H6EiPTdt4IH2NkQXLxovMUeJ0gI7wCvZJLKFP3c-OC85n-QxHz52MDoTNSX3MSezycL2kl8/s16000/Image21.png" />
</a>
</div><span style="font-family: "Times New Roman"; white-space: normal;">Updated:</span>
</pre>
<pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: sans-serif;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: maroon; font-weight: bold;">function</span> <span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> left<span style="color: #808030;">=</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> right<span style="color: #808030;">=</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span><span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">==</span><span style="color: #008c00;">0</span><span style="color: #808030;">)</span> op<span style="color: #808030;">.</span>height <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> end<span style="color: #808030;">=</span><span style="color: #808030;">[</span>start<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: sans-serif;"><span style="font-family: "Times New Roman"; white-space: normal;">I'm going to pause here and use the line drawing function to draw this triangle and make sure I've got everything right.</span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: sans-serif;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: maroon; font-weight: bold;">function</span> <span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> left<span style="color: #808030;">=</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> right<span style="color: #808030;">=</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span><span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">==</span><span style="color: #008c00;">0</span><span style="color: #808030;">)</span> op<span style="color: #808030;">.</span>height <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> end<span style="color: #808030;">=</span><span style="color: #808030;">[</span>start<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: purple;">;</span>
line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> left<span style="color: #808030;">,</span> right<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> right<span style="color: #808030;">,</span> end<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> end<span style="color: #808030;">,</span> left<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: sans-serif;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOe79-Ec-VVqpoZ4EEq1XXQjOPqwq8tdl58gUH9B11oEFGctfjrtPNJAqUA_0f4VCQYWrI3OEsTUs5OhAzlJNVsB3AjU7pK5B31w0tfdXr5YIskZIv-7HWkE9NJbFLSg5ZKbhiNOg9_hSB/s328/Image22.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="268" data-original-width="328" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOe79-Ec-VVqpoZ4EEq1XXQjOPqwq8tdl58gUH9B11oEFGctfjrtPNJAqUA_0f4VCQYWrI3OEsTUs5OhAzlJNVsB3AjU7pK5B31w0tfdXr5YIskZIv-7HWkE9NJbFLSg5ZKbhiNOg9_hSB/s16000/Image22.png" /></a></div></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Looks good! Now let me tackle rotating the triangle to the proper location.</span></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">To do this, I need to rotate all the points in the triangle around the center of the compass by angle. Right now I have the points as left, right and end, which is not very convenient. I'll modify rtri to create an array of the triangle points:</span></span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rtri<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>height <span style="color: #808030;">==</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span> op<span style="color: #808030;">.</span>height <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> triangle <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: #808030;">]</span><span style="color: purple;">;</span><br /> line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: Times New Roman;"><span style="white-space: normal;">Now I need a function to
<a href="https://stackoverflow.com/a/34374437" target="_blank">rotate a point through an angle
</a>. In Javascript, that looks like this:</span></span>
</pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: dimgrey;">//
</span><span style="color: dimgrey;">// Rotate p2 around p1 by angle (in radians)
</span><span style="color: dimgrey;">//
</span><span style="color: maroon; font-weight: bold;">function</span> rotate<span style="color: #808030;">(</span>p1<span style="color: #808030;">,</span> p2<span style="color: #808030;">,</span> angle<span style="color: #808030;">) </span><span style="color: purple;">{
</span><span style="color: maroon; font-weight: bold;"><span> </span>let </span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">=</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: #808030;">,</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">=</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: #808030;">,</span>
<span> </span><span> </span>nx<span style="color: #808030;">=</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">*</span><span style="color: #808030;">(</span>p2<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span> p1<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">-</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">*</span><span style="color: #808030;">(</span>p2<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span> p1<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">+</span> p1<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span> </span><span> </span>ny<span style="color: #808030;">=</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">*</span><span style="color: #808030;">(</span>p2<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span> p1<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">+</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">*</span><span style="color: #808030;">(</span>p2<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span> p1<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: #808030;">+</span> p1<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: purple;">;
</span><span style="color: maroon; font-weight: bold;"><span> </span>return</span><span style="color: #808030;">[</span>nx<span style="color: #808030;">,</span> ny<span style="color: #808030;">]</span><span style="color: purple;">;
</span><span style="color: purple;">}</span>
</pre><div><span style="font-family: Times New Roman;"><span style="white-space: normal;">I need to apply this function to all the points in the triangle array. There are various ways to do this in Javascript -- a for loop is an obvious choice -- but Javascript has a feature specifically for this sort of operation, called map. Map takes an array and a function and returns a new array that results from applying the function to every element in the list. Now I can't just map the rotate function directly, because the arguments won't be right. So I need an arrow function to fix that up.</span></span></div><div><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rtri<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>height <span style="color: #808030;">==</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span> op<span style="color: #808030;">.</span>height <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> triangle <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
triangle<span style="color: #808030;">=</span> triangle<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt =<span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">2</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> triangle<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre></div><div><span style="font-family: Times New Roman;"><span style="white-space: normal;">For every pt in triangle, the map function calls “rotate(center, pt, angle)" and collects the results in a new list (which gets put back into triangle).</span></span></div><div><span style="font-family: Times New Roman;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikEc6jla2oqR20cDnUDtrEwqelrMLfFACqGF8YLnHosQ4SPkkX-AAmm8Tc-HJ40FatIkeHRlGDjMTbb31tZklWIU8nR7W2dODW0FegGsVzV0t1_g_jR_LFy6HMHUwdFAVzexsZRaLu3em5/s327/Image23.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="264" data-original-width="327" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikEc6jla2oqR20cDnUDtrEwqelrMLfFACqGF8YLnHosQ4SPkkX-AAmm8Tc-HJ40FatIkeHRlGDjMTbb31tZklWIU8nR7W2dODW0FegGsVzV0t1_g_jR_LFy6HMHUwdFAVzexsZRaLu3em5/s16000/Image23.png" /></a>
</div>
<div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;">And now every triangle gets drawn at the proper spot. All that remains is to fill in the triangle. </span><span style="white-space: normal;">To do that, I have to draw the triangle differently. Right now I'm drawing it as three separate lines, which means there's no “inside" to fill. I need to draw it as connected lines.</span></div>
<div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;">There are a couple of ways to do this in SVG, but I typically use an
<a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths" target="_blank">SVG path</a>. SVG paths are a bit of a nightmare if I'm being honest, but they're very flexible and can be used in ways that simpler methods don't allow. And here again
<a href="http://d3js.com" target="_blank">D3
</a> will come to our rescue with several powerful methods for constructing SVG paths. Principally I use
<a href="http://d3.line" target="_blank">d3.line</a>, which is a function that creates a function that turns a polygon into the commands expected in the SVG path command. (Whew!) </span></div>
<div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;">In practice, it's not too difficult to understand:</span>
<pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;"><span> </span>const</span> lineFunc<span style="color: #808030;">=</span> d3<span style="color: #808030;">.</span>line<span style="color: #808030;">(</span><span style="color: #808030;">)</span><span style="color: #808030;">.
</span><span> </span><span> </span><span> </span><span> </span><span> </span><span> </span>x<span style="color: #808030;">(</span>pt<span style="color: #808030;">=</span><span style="color: #808030;">></span> pt<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">.
</span><span> </span><span> </span><span> </span><span> </span><span> </span><span> </span>y<span style="color: #808030;">(</span>pt<span style="color: #808030;">=</span><span style="color: #808030;">></span> pt<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: #808030;">.
</span><span> </span><span> </span><span> </span><span> </span><span> </span><span> </span>curve<span style="color: #808030;">(</span>d3<span style="color: #808030;">.</span>curveLinearClosed<span style="color: #808030;">)</span><span style="color: purple;">;</span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: "Times New Roman"; white-space: normal;">This creates a function called “lineFunc" that takes a list of points. In x and y I define how you get the x and y values out of a point. In this case, points are arrays like [x, y], so the x value is the first element of the array and the y value is the second element of the array. Curve is used to specify how points are connected. In this case, I want to use straight lines between the points and I want to draw a closed polygon that connects the last point back to the first point. D3 itself provides a curve function that does that, called d3.curveLinearClosed. D3 provides a bunch of different curves you can use to connect points -- you can
<a href="http://bl.ocks.org/d3indepth/b6d4845973089bc1012dec1674d3aff8" target="_blank">explore some here
</a>.</span>
</pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="font-family: "Times New Roman"; white-space: normal;">With lineFunc defined, the code to draw the triangle as a path instead of three separate lines is simple:</span></pre></div><div class="separator" style="clear: both; text-align: left;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rtri<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>height <span style="color: #808030;">==</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span> op<span style="color: #808030;">.</span>height <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">let</span> triangle <span style="color: #808030;">=</span> <span style="color: #808030;">[</span><span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>width<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">]</span><span style="color: #808030;">,</span>
<span style="color: #808030;">[</span>center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">-</span>radius<span style="color: #808030;">+</span>op<span style="color: #808030;">.</span>height<span style="color: #808030;">]</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
triangle <span style="color: #808030;">=</span> triangle<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">map</span><span style="color: #808030;">(</span>pt <span style="color: #808030;">=</span><span style="color: #808030;">></span> rotate<span style="color: #808030;">(</span>center<span style="color: #808030;">,</span> pt<span style="color: #808030;">,</span> angle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">path</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lwidth<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>fill<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">d</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineFunc<span style="color: #808030;">(</span>triangle<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre></div><div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;">The only non-obvious part is passing the output of lineFunc into the “d" attribute. (I have no clue why it is called that.)</span></div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu8IQL_7_obx7PhaxDek3LhH73d1mKJAQ2K0LE_XNkznh8OA2AqXMiKizdfn2tM9gawue1awWv7t9nbNuWWaI-bhwSMEix3Q0VzUQ8DmQ4o1a9NQoIoQrwlbtDcay9qsaiP_aiY7t8Leon/s329/Image24.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="268" data-original-width="329" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu8IQL_7_obx7PhaxDek3LhH73d1mKJAQ2K0LE_XNkznh8OA2AqXMiKizdfn2tM9gawue1awWv7t9nbNuWWaI-bhwSMEix3Q0VzUQ8DmQ4o1a9NQoIoQrwlbtDcay9qsaiP_aiY7t8Leon/s16000/Image24.png" />
</a></div><div class="separator" style="clear: both; text-align: left;">And now a little bit of a stress test:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTbroHirUWow25iDw9UACT1KBttNzXdb0ykl4etnllx4JqdLYsTFAUaY3Fnn2LzYiQ6E7ZSeT1Vf5KpgbSAc7xfUZvUvaO8pjGPsEWFvIGDRMBHek_qBI7Rd1VmxE5M3zRId26PHhGVcc_/s565/Image25.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="320" data-original-width="565" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTbroHirUWow25iDw9UACT1KBttNzXdb0ykl4etnllx4JqdLYsTFAUaY3Fnn2LzYiQ6E7ZSeT1Vf5KpgbSAc7xfUZvUvaO8pjGPsEWFvIGDRMBHek_qBI7Rd1VmxE5M3zRId26PHhGVcc_/s16000/Image25.png" />
</a></div><div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;">I haven't bothered to get this exactly right, but we're a long ways towards generating a pretty complex and interesting compass. And SVG path will come in handy next time when I tackle the compass points.</span></div>
<div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;">Just for fun, here's the CDL to generate the partial compass above:</span></div><pre><div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;">
<div class="separator" style="clear: both;">
<div class="separator" style="clear: both;">RLINE(0, 72, 8, 0.5, "rgb(108,65,38)")
</div>
<div class="separator" style="clear: both;">SPACE(2)
</div>
<div class="separator" style="clear: both;">RLINE(0, 360, 4, 0.25, "rgb(108,65,38)")
</div>
<div class="separator" style="clear: both;">SPACE(3)
</div>
<div class="separator" style="clear: both;">RTRI(0, 8, -8, 6, 0.5, "rgb(108,65,38)", "white")
</div>
<div class="separator" style="clear: both;">CIRCLE(3, "rgb(108,65,38)", "none")
</div>
<div class="separator" style="clear: both;">SPACE(8)
</div>
<div class="separator" style="clear: both;">CIRCLE(0.5, "rgb(108,65,38)", "none")
</div>
<div class="separator" style="clear: both;">SPACE(8)
</div>
<div class="separator" style="clear: both;">CIRCLE(2, "rgb(108,65,38)", "none")
</div>
<div class="separator" style="clear: both;">SPACE(4)
</div>
<div class="separator" style="clear: both;">RCIRCLE(0, 24, 1, 0, "none", "rgb(108,65,38)")
</div>
<div class="separator" style="clear: both;">SPACE(4)
</div>
<div class="separator" style="clear: both;">CIRCLE(2, "rgb(108,65,38)", "none")
</div>
<div class="separator" style="clear: both;">SPACE(2)
</div>
<div class="separator" style="clear: both;">CIRCLE(0.5, "rgb(108,65,38)", "none")</div></div></span></div></pre><div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;">And this shows how to input an arbitrary RGB color in CDL.</span></div><div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;"><br /></span></div><div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;">A reminder that if you want to follow along from home, you need to download the Part 6 branch from the </span><a href="https://github.com/srt19170/Procedural-Map-Compasses" style="white-space: normal;" target="_blank">Procedural Map Compasses repository
</a><span style="white-space: normal;"> on Github and get the test web page up and open the console. <a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">Part 1 has instructions</a>, but the short version (on Windows) is to double-click mongoose-free-6.9.exe to start up a web server in the Part 6 directory, and then select the test.html link. Obviously you can browse the code and other files, and make changes of your own. </span><span style="background-color: transparent; white-space: normal;">You can also try out this code on the web at </span><a href="https://dragonsabound-part6.netlify.app/test.html" style="background-color: transparent; white-space: normal;">https://dragonsabound-part6.netlify.app/test.html</a><span style="background-color: transparent; white-space: normal;"> although you'll only be able to run the code, not modify it.</span> </div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><span style="white-space: normal;"><span style="font-weight: 700;">Suggestions to Explore</span></span></div><div class="separator" style="clear: both; text-align: left;"><ul style="text-align: left;"><li><span style="white-space: normal;">The RTRI code is the basis for any sort of polygon. Try implementing diamonds, or for the adventurous, <a href="https://heredragonsabound.blogspot.com/2019/04/map-borders-part-9.html" target="_blank">star shapes</a>.<br /><br /></span></li><li><span style="white-space: normal;">Try re-implementing RLINE using the method in RTRI of drawing the line at noon and then rotating it to the proper position. If you implemented RCROSS in Part 5, try re-implementing it using the draw and rotate approach. Is that easier/better?</span></li></ul></div></span></div></pre></pre>
</div>
</div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com4tag:blogger.com,1999:blog-3367557180796013182.post-45191613364625393432021-11-25T09:48:00.000-05:002021-11-25T09:48:59.477-05:00Map Compasses (Part 5): Radial Lines and Circles<p>Welcome back to my blog series on implementing procedurally-generated map compasses! In the previous post, I write the code for the language commands CIRCLE and SPACE. In this posting, I'm going to work on commands for radial elements.</p><p>Radial elements are the parts of the compass that repeat at regular intervals in a circle around the center point of the compass. For example, consider this very simple compass:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEht8SYv77v99NTfb6fVi2vhyphenhyphenh68_aCo4j1jpmfM_jLqVdUTXU97I1oHo5KAt_Fy5Et_cXF57JKktdcGblX2hOifRidkNc2H3llGtuCpaUpdOuiWBJRN03N_T-4ciRULIY7fCTlwzjsvkBp4/s176/image.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="176" data-original-width="143" height="176" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEht8SYv77v99NTfb6fVi2vhyphenhyphenh68_aCo4j1jpmfM_jLqVdUTXU97I1oHo5KAt_Fy5Et_cXF57JKktdcGblX2hOifRidkNc2H3llGtuCpaUpdOuiWBJRN03N_T-4ciRULIY7fCTlwzjsvkBp4/s0/image.png" width="143" /></a></div>You could view this compass as having a line repeated every 90 degrees, plus a label “N" repeated every 360 degrees. A more complex example:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje75-BqvKep_t1mGKEt4zjCtPOkLHQAJmdLcRxkODspC61WIpxWAQ_yNrb1U2lYO9XsMopbVHVx0zC0SlxL-kdsUM2mZyp099tx0SmUCCXnzEKxrpJvfg-lwxzjeFFD1HsNfDAMILlM9UW/s425/compass.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="424" data-original-width="425" height="319" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje75-BqvKep_t1mGKEt4zjCtPOkLHQAJmdLcRxkODspC61WIpxWAQ_yNrb1U2lYO9XsMopbVHVx0zC0SlxL-kdsUM2mZyp099tx0SmUCCXnzEKxrpJvfg-lwxzjeFFD1HsNfDAMILlM9UW/s320/compass.jpg" width="320" /></a></div>Here's a compass with a lot of radial elements. Labels of various sorts, but also short lines (and somewhat longer lines) that provide the degrees scale, some triangles to mark the diagonals on that scale, a set of smaller compass points and a set of larger compass points, and even some little dots for decoration in an inner ring. (And everything else in the compass is some version of CIRCLE.)<div><br /></div><div>To start with, I have to add some commands to the Compass Description Language (CDL) to describe radial elements. There are a couple of ways I might do this. I could make a separate command for every type of radial element, or I could do something like RADIAL ... END to wrap around commands that I want to place radially. I'm going to use separate commands, partially because it makes the interpreter simpler and partially because I'd still need those commands to put inside the RADIAL loop anyway.</div><div><br /></div><div>But I would like all the radial elements to have a similar syntax. Looking at those radial elements in the complex example above, there are some parameters that seem to apply to all radial elements. First, a starting point. Some elements start at the top of the circle (like the larger compass points above) but some start elsewhere (like the smaller compass points above, which start at 45 degrees). Second, the number of repeats. The compass points above repeat 4 times, while the smaller lines in the degree scale repeat 360 times. Third, there's a length or a size. Some elements extend all the way to the center of the compass (like the compass points above) but some only extend partway to the center (like the lines that make up the degrees scale above).</div><div><br /></div><div>Pulling all that together, I'll implement a simple radial line element:<br /><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div style="text-align: left;"><span style="font-family: courier; font-size: x-small;"><b>RLINE(start, repeats, length, lineWidth, color)</b></span></div></blockquote><p>This command will draw a radial line of the given length in the given color. The length will start at the current radius and extend in towards the center of the compass. But I'll also support a negative length, which will let me draw outward as well. It will be handy to have a shorthand syntax that means “draw all the way to the center of the compass" so that I don't have to keep track of that distance when generating CDL. Zero is conveniently unused in this scheme, so I'll use a zero length to mean “all the way to the center."</p><p>The first step in implementing this is adding it to the parser. The rule for this command is long but straightforward:<br /></p><pre style="background: rgb(255, 255, 255);"># A radial line element
rlineElement <span style="color: #808030;">-</span><span style="color: #808030;">></span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RLINE</span><span style="color: maroon;">"</span>i WS <span style="color: maroon;">"</span><span style="color: #0000e6;">(</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS decimal WS <span style="color: maroon;">"</span><span style="color: #0000e6;">,</span><span style="color: maroon;">"</span> WS dqstring WS <span style="color: maroon;">"</span><span style="color: #0000e6;">)</span><span style="color: maroon;">"</span>
<span style="color: purple;">{</span><span style="color: #808030;">%</span> data <span style="color: #808030;">=</span><span style="color: #808030;">></span> <span style="color: #808030;">(</span><span style="color: purple;">{</span>op<span style="color: purple;">:</span> <span style="color: maroon;">"</span><span style="color: #0000e6;">RLINE</span><span style="color: maroon;">"</span><span style="color: #808030;">,</span> start<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">4</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> repeats<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">8</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> length: data<span style="color: #808030;">[</span><span style="color: #008c00;">12</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> width<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">16</span><span style="color: #808030;">]</span><span style="color: #808030;">,</span> color<span style="color: purple;">:</span> data<span style="color: #808030;">[</span><span style="color: #008c00;">20</span><span style="color: #808030;">]</span><span style="color: purple;">}</span><span style="color: #808030;">)</span> <span style="color: #808030;">%</span><span style="color: purple;">}</span></pre><div><div>Other than the literal elements like the name and the parentheses, most of the command is decimals and double-quoted strings to define the parameters. As with the other commands, it gets translated during post-processing into a Javascript object with the various parameters saved onto the object.</div><div><br /></div><div>We also need to modify the list of legal CDL elements to include the RLINE element:</div><div><pre style="background: rgb(255, 255, 255);"># A list of all the legal CDL elements
Element <span style="color: #808030;">-</span><span style="color: #808030;">></span> spaceElement <span style="color: #808030;">|</span> circleElement <span style="color: #808030;">|</span> rlineElement</pre><p>A description that includes RLINE will now be parsed. (Remember that when you modify the Nearley grammar file, you need to recompile it using the command line at the top of the file.)</p><p>The next step is to implement RLINE in the interpreter (in compass.js). First, we need to add RLINE to our if-else in the interpreter that dispatches based on the op code:</p><pre style="background: rgb(255, 255, 255);"> } <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">RLINE</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span><br /> <span style="color: dimgrey;">// Draw radial lines</span>
Draw<span style="color: #808030;">.</span>rline<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>start<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>repeats<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>length<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>width<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Draw radial lines.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span></pre><p>I've filled in a call here to “Draw.rline" that will do the actual work of drawing the radial lines. “Op" here is the Javascript structure built in the Nealey rule above, and op.start, etc., are the parameters. Now I'll need to jump over to draw.js and write that function.</p><p>The heart of the function is a loop that draws the proper number of repeats around the circle. </p><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> rline<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> len<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> color<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> angle <span style="color: #808030;">=</span> start<span style="color: purple;">;</span> angle <span style="color: #808030;"><</span> start<span style="color: #808030;">+</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: purple;">;</span> angle <span style="color: #808030;">+=</span> <span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre><p>angle is where I am currently drawing. It starts at the start angle and loops all the way around and back to the same spot. I'm working in radians here, so a full circle is 2PI. Every step advances 2PI/repeats, which means I'll draw repeat number of elements as I go around the circle. From the angle and the radius I need to calculate the coordinates of the actual point for the element. Recalling my old friend <a href="https://www.mathsisfun.com/algebra/sohcahtoa.html" target="_blank">Sohcahtoa</a> from high school geometry, and remembering to measure from the center, I add those calculations:</p><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rline<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> len<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> color<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> angle <span style="color: #808030;">=</span> start<span style="color: purple;">;</span> angle <span style="color: #808030;"><</span> start<span style="color: #808030;">+</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: purple;">;</span> angle <span style="color: #808030;">+=</span> <span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> sx <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span>cos<span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> sy <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span>sin<span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre><p>Actually I lied just a little bit right there. In SVG (and in many computer graphics systems), the Y axis is reversed from your familiar days in high school geometry. The origin of a window is typically at the upper left, and Y goes up as you move down from the origin. In practical terms, this means I have to reverse the sign on the calculation of sy:</p><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rline<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> len<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> color<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> angle <span style="color: #808030;">=</span> start<span style="color: purple;">;</span> angle <span style="color: #808030;"><</span> start<span style="color: #808030;">+</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: purple;">;</span> angle <span style="color: #808030;">+=</span> <span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> sx <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span>cos<span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> sy <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span>sin<span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre><p>(And yes, I messed this up when I first wrote the code. I always mess this up.)</p><p>With that corrected, I can do a similar calculation to determine the end point of the line, but instead of using radius, I'll subtract the length. So a positive length will result in a point closer to the center, as desired. And I need to remember the special treatment when length == 0, which indicates a line that goes to the center of the compass.</p><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rline<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> len<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> color<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> angle <span style="color: #808030;">=</span> start<span style="color: purple;">;</span> angle <span style="color: #808030;"><</span> start<span style="color: #808030;">+</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: purple;">;</span> angle <span style="color: #808030;">+=</span> <span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> sx <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span>cos<span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> sy <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span>sin<span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Length == 0 indicates a line that goes to the center of the compass</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>len <span style="color: #808030;">==</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span> len <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> ex <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> <span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>len<span style="color: #808030;">)</span> <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span>cosine<span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> ey <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> <span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>len<span style="color: #808030;">)</span> <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span>sine<span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre><p>Now that I have the start and the end of the line I can call a line drawing routine. This is very straightforward:<br /></p><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> end<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> color<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">line</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> width<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> color<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">x1</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> start<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">y1</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> start<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">x2</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> end<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">y2</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> end<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre><p>This appends an <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line" target="_blank">SVG <line> element</a> to the SVG. Let me feed a CDL that uses a RLINE element into the test routine:</p></div></div><blockquote style="border: none; margin: 0 0 0 40px; padding: 0px;"><pre style="background: rgb(255, 255, 255);">CIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> “black<span style="color: maroon;">"</span><span style="color: #0000e6;">, “none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> RLINE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">8</span><span style="color: #808030;">,</span> <span style="color: #008c00;">4</span><span style="color: #808030;">,</span> <span style="color: #008c00;">2</span><span style="color: #808030;">,</span> “black<span style="color: maroon;">"</span><span style="color: #0000e6;">)</span>
</pre></blockquote><div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSpbEuyRBuE0FtrKe9Qzvecfh-FgD3lJIdwt0ajGg6kUsRgGWrbP2dOaURf0_59eg_jDy_mUlEMdWwTMss3qu4koD7pPUKcF2FyN05Y4VBMaABoJiBg89HX7nbKwGM664ZE_fHNlV4uQsW/s327/Image15.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="265" data-original-width="327" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSpbEuyRBuE0FtrKe9Qzvecfh-FgD3lJIdwt0ajGg6kUsRgGWrbP2dOaURf0_59eg_jDy_mUlEMdWwTMss3qu4koD7pPUKcF2FyN05Y4VBMaABoJiBg89HX7nbKwGM664ZE_fHNlV4uQsW/s16000/Image15.png" /></a></div>And voila! Eight tick marks around the inside of our circle. Let's see if drawing to the center of the compass works:</div></div><blockquote style="border: none; margin: 0 0 0 40px; padding: 0px;"><div><div><pre style="background: rgb(255, 255, 255); text-align: left;">CIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> “black<span style="color: maroon;">"</span><span style="color: #0000e6;">, “none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> RLINE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">8</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">2</span><span style="color: #808030;">,</span> “black<span style="color: maroon;">"</span><span style="color: #0000e6;">)</span></pre></div></div></blockquote><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgN5_tfRtjmQf5nVWGxqYWW8mO5z-ON8eeYa3A1i-LKFYo2Zn4m9WczHLbeiRfAd_i90TxI-U2QK-JBR8kmo7tabyisM6aS_5kGxeMv2h0l69DQtLTlSe0u8SSlNbfNlKTOlpDP04eim99t/s326/Image16.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="266" data-original-width="326" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgN5_tfRtjmQf5nVWGxqYWW8mO5z-ON8eeYa3A1i-LKFYo2Zn4m9WczHLbeiRfAd_i90TxI-U2QK-JBR8kmo7tabyisM6aS_5kGxeMv2h0l69DQtLTlSe0u8SSlNbfNlKTOlpDP04eim99t/s16000/Image16.png" /></a></div>Whoo-hoo! Pizza pie!</div><div><br />Before I get too happy, let's check a couple of other things. How about a single line:</div><blockquote style="border: none; margin: 0 0 0 40px; padding: 0px;"><div><pre style="background: rgb(255, 255, 255); text-align: left;">CIRCLE<span style="color: #808030;">(</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> “black<span style="color: maroon;">"</span><span style="color: #0000e6;">, “none</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span> RLINE<span style="color: #808030;">(</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">1</span><span style="color: #808030;">,</span> <span style="color: #008c00;">0</span><span style="color: #808030;">,</span> <span style="color: #008c00;">2</span><span style="color: #808030;">,</span> “black<span style="color: maroon;">"</span><span style="color: #0000e6;">)</span></pre></div></blockquote><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5KudNcnVJPO7NRtqO_pVCSj6Rxaa9ImhjuTIC6dl2wTjnN0nT-YjQDxa-NILOZrLITWTR49GKflXKuVq9rr3vMac3Hfjk5kbdBZBPzZlzoo44L_1NiBbgde0d_-ltcur5TJr7wkvm9lDp/s326/Image17.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="264" data-original-width="326" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5KudNcnVJPO7NRtqO_pVCSj6Rxaa9ImhjuTIC6dl2wTjnN0nT-YjQDxa-NILOZrLITWTR49GKflXKuVq9rr3vMac3Hfjk5kbdBZBPzZlzoo44L_1NiBbgde0d_-ltcur5TJr7wkvm9lDp/s16000/Image17.png" /></a></div>Well, partial success. It's a single line as expected, but pointing at 3 o'clock rather than 12 o'clock. In retrospect, this makes sense. The starting angle is 0, and a zero angle is right on the X axis. I should have started at 90 degrees instead. On the other hand, I'm always going to want to think of noon as the zero angle on a compass, so I'll shift the start angle by 90 degrees before I start drawing:<div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> rline<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> len<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> color<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> angle <span style="color: #808030;">=</span> start<span style="color: #808030;">+</span><span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: purple;">;</span> angle <span style="color: #808030;"><</span> start<span style="color: #808030;">+</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: purple;">;</span> angle <span style="color: #808030;">+=</span> <span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> sx <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> sy <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Length == 0 indicates a line that goes to the center of the compass</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>len <span style="color: #808030;">==</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span> len <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> ex <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> <span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>len<span style="color: #808030;">)</span> <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> ey <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> <span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>len<span style="color: #808030;">)</span> <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> <span style="color: #808030;">[</span>sx<span style="color: #808030;">,</span> sy<span style="color: #808030;">]</span><span style="color: #808030;">,</span> <span style="color: #808030;">[</span>ex<span style="color: #808030;">,</span> ey<span style="color: #808030;">]</span><span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre><div><p>Now let me quickly implement another radial element, one that draws circles like this:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmJPUIyzsJqmgaWPZ18b1Q-5wGtfsPqN-cSxMWDkzHwKUSUblOtxbJBppwh89GTAiZdHv32UpTaIXlOdn6aIOsmkn3lLJ1t4Rfb43v22-ONPZur-Xzxc9RCMM-ZOHQ0PnXeSAOs7R1sjN5/s328/Image18.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="262" data-original-width="328" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmJPUIyzsJqmgaWPZ18b1Q-5wGtfsPqN-cSxMWDkzHwKUSUblOtxbJBppwh89GTAiZdHv32UpTaIXlOdn6aIOsmkn3lLJ1t4Rfb43v22-ONPZur-Xzxc9RCMM-ZOHQ0PnXeSAOs7R1sjN5/s16000/Image18.png" /></a></div><p>I won't go through adding this to the parser and the interpreter, as it is much the same as RLINE. But here is the function that implements RCIRCLE:</p><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> rcircle<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> len<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> color<span style="color: #808030;">,</span> fill<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> angle <span style="color: #808030;">=</span> start<span style="color: #808030;">+</span><span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: purple;">;</span> angle <span style="color: #808030;"><</span> start<span style="color: #808030;">+</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: #808030;">+</span><span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: purple;">;</span> angle <span style="color: #808030;">+=</span> <span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> sx <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> sy <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Length == 0 indicates a line that goes to the center of the compass</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>len <span style="color: #808030;">==</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span> len <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> ex <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> <span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>len<span style="color: #808030;">)</span> <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> ey <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> <span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>len<span style="color: #808030;">)</span> <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
circle<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> <span style="color: #808030;">[</span>sx<span style="color: #808030;">,</span> sy<span style="color: #808030;">]</span><span style="color: #808030;">,</span> len<span style="color: #808030;">,</span> width<span style="color: #808030;">,</span> color<span style="color: #808030;">,</span> fill<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre><p>In this case “len" is the size of the circle to draw so I don't actually need the end point. I left it in to illustrate how similar RLINE and RCIRCLE are -- they're identical except for the actual call to the drawing function. All of the radial elements are going to share the same repeats loop and this suggests that can be abstracted out and not rewritten in every element.</p><p>To do that, I'll write a function that implements the repeats loop and takes the drawing function as a parameter. That looks like this:</p><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> <span style="color: maroon; font-weight: bold;">repeat</span><span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> op<span style="color: #808030;">,</span> fn<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">let</span> angle <span style="color: #808030;">=</span> start<span style="color: #808030;">+</span><span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> i<span style="color: #808030;">=</span><span style="color: #008c00;">0</span><span style="color: purple;">;</span>i<span style="color: #808030;"><</span>repeats<span style="color: purple;">;</span>i<span style="color: #808030;">++</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
fn<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span><span style="color: purple;">;</span>
angle <span style="color: #808030;">+=</span> <span style="color: #808030;">(</span><span style="color: #797997;">Math</span><span style="color: #808030;">.</span>PI<span style="color: #808030;">*</span><span style="color: #008c00;">2</span><span style="color: #808030;">)</span><span style="color: #808030;">/</span>repeats<span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre></pre><p>The new repeat function loops all the radial placements and calls fn with the SVG, the start angle, the number of repeats, the angle, the iteration and the original operation object. Some of these parameters aren't needed for RLINE or RCIRCLE but (warning: foreshadowing!) they will be needed for future functions. In particular, passing in the operation object will give our drawing routines access to the parameters that were in the original CDL command.</p><p>Now we can replace the call to RCIRCLE with a call to repeat. This code in the interpreter:</p><pre style="background: rgb(255, 255, 255);"> } <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">RCIRCLE</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span><br /> <span style="color: dimgrey;">// Draw radial circles</span>
Draw.rcircle(svg, center, radius, op.start, op.repeats, op.length, op.width, op.color, op.fill);
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Draw radial circles.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span></pre><p>becomes:</p><pre style="background: rgb(255, 255, 255);"> } <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">RCIRCLE</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span><br /> <span style="color: dimgrey;">// Draw radial circles</span>
Draw<span style="color: #808030;">.</span>repeat<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>start<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>repeats<span style="color: #808030;">,</span> op<span style="color: #808030;">,</span> Draw.rcircle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Draw radial circles.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span></pre><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><p style="background-color: white; font-family: "Times New Roman"; white-space: normal;">Now I have to adjust the Draw.rcircle function slightly to handle the new parameters.</p><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rcircle<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> sx <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> sy <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
circle<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> <span style="color: #808030;">[</span>sx<span style="color: #808030;">,</span> sy<span style="color: #808030;">]</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>length<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>width<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>fill<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre><p style="background-color: white; font-family: "Times New Roman"; white-space: normal;">And then a similar thing for RLINE:</p><pre style="background: rgb(255, 255, 255);"> } <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">RLINE</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span><br /> <span style="color: dimgrey;">// Draw radial lines</span>
Draw<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">repeat</span><span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>start<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>repeats<span style="color: #808030;">,</span> op<span style="color: #808030;">,</span> Draw<span style="color: #808030;">.</span>rline2<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Draw radial lines.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span> </pre><pre style="background: rgb(255, 255, 255);"> <span style="color: purple;">}</span><span style="font-family: "Times New Roman"; white-space: normal;"><br /></span></pre><pre style="background: rgb(255, 255, 255);"><span style="font-family: "Times New Roman"; white-space: normal;">I'll define the rline2 function to calculate the start and end points and call Draw.line:</span></pre><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> rline2<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> start<span style="color: #808030;">,</span> repeats<span style="color: #808030;">,</span> angle<span style="color: #808030;">,</span> i<span style="color: #808030;">,</span> op<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Length == 0 indicates a line that goes to the center of the compass</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span> <span style="color: #808030;">==</span> <span style="color: #008c00;">0</span><span style="color: #808030;">)</span> op<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span> <span style="color: #808030;">=</span> radius<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> sx <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> sy <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> radius <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> ex <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span> <span style="color: #808030;">+</span> <span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>op<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span><span style="color: #808030;">)</span> <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">cos</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> ey <span style="color: #808030;">=</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span> <span style="color: #808030;">-</span> <span style="color: #808030;">(</span>radius<span style="color: #808030;">-</span>op<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span><span style="color: #808030;">)</span> <span style="color: #808030;">*</span> <span style="color: #797997;">Math</span><span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">sin</span><span style="color: #808030;">(</span>angle<span style="color: #808030;">)</span><span style="color: purple;">;</span>
line<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> <span style="color: #808030;">[</span>sx<span style="color: #808030;">,</span> sy<span style="color: #808030;">]</span><span style="color: #808030;">,</span> <span style="color: #808030;">[</span>ex<span style="color: #808030;">,</span> ey<span style="color: #808030;">]</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>width<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>color<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre><p style="background-color: white; font-family: "Times New Roman"; white-space: normal;">I've used the kind of awkward rline2 name here because I wanted to leave the original rline function in the code so that you could see it there. </p><p style="background-color: white; font-family: "Times New Roman"; white-space: normal;">That's where I'll stop for today. A reminder that if you want to follow along from home, you can download the Part 5 branch from the <a href="https://github.com/srt19170/Procedural-Map-Compasses" target="_blank">Procedural Map Compasses repository</a> on Github. <a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">Part 1</a> 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 5 directory, and then select the test.html link. Obviously you can browse the code and other files, and make changes of your own. Try writing some CDL, inserting it into the test function, and using the button to generate it. <span style="background-color: transparent;">You can also try out this code on the web at </span><a href="https://dragonsabound-part5.netlify.app/test.html" style="background-color: transparent;">https://dragonsabound-part5.netlify.app/test.html</a><span style="background-color: transparent;"> although you'll only be able to run the code, not modify it.</span></p><p style="background-color: white; font-family: "Times New Roman"; white-space: normal;">I've left the code set up to produce this:</p><div class="separator" style="background-color: white; clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib0e-Gt-hBjjucaqxUjMxDgcrwUn1t53RJcmJ8eQZ-4HWsEcSeJSais2d_f3yBKagB0by2Q0cRXxW4VNeCMTReiGwo3LuJo_oAG95aFrrxblwIBEq3bbqrF2QkVfLpoP6CtGwuFcDTKH_g/s327/Image19.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="267" data-original-width="327" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib0e-Gt-hBjjucaqxUjMxDgcrwUn1t53RJcmJ8eQZ-4HWsEcSeJSais2d_f3yBKagB0by2Q0cRXxW4VNeCMTReiGwo3LuJo_oAG95aFrrxblwIBEq3bbqrF2QkVfLpoP6CtGwuFcDTKH_g/s16000/Image19.png" /></a></div><p style="background-color: white; font-family: "Times New Roman"; white-space: normal;">But the CDL for all the examples is included in the code in comments just above the test function in compass.js.</p><div style="background-color: white;"><span style="font-family: "Times New Roman"; font-weight: 700; white-space: normal;">Suggestions to Explore</span></div><div><ul style="text-align: left;"><li><span style="background-color: white; font-family: Times New Roman;"><span style="white-space: normal;">Implement RCROSS that draws a radial cross element. This is the same as RLINE but it adds a horizontal line across the existing line. How did you deal with getting the horizontal line at the correct angle? Extend RCROSS to an asterisk by adding the two lines at 45 degree angles.</span></span> </li></ul></div></pre></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com6tag:blogger.com,1999:blog-3367557180796013182.post-20012512541517577882021-11-17T08:25:00.000-05:002021-11-17T08:25:18.340-05:00Map Compasses (Part 4): Implementing CIRCLE and SPACE<p>Welcome back to my blog series on implementing procedurally-generated map compasses! In previous posts, I've started defining the Compass Description Language (CDL), built a parser for the language, and started creating an interpreter to draw the compasses. At the end of the last posting I had prepared the web page for drawing. The next step is to write the code to draw the current language commands (CIRCLE and SPACE).</p><p>Before I do that, let me discuss how drawing will work. First of all, the interpreter will need to know where the compass is to be drawn (its center point) and how big the compass is (its total radius). (For now I'll just set those parameters.) Second, the compass will be (generally) drawn from the outside in, similar to how I handled drawing map borders. The interpreter will have a “current radius" that will start at the total radius for the compass and be decremented after every drawing command. For example, if the total radius of the compass was 100, the command CIRCLE(5, “black", “none") to draw a 5 pixel wide black circle would be drawn at 100 and decrement the marker from 100 to 95. If that were followed by CIRCLE(1, “red", “none"), the red circle would be drawn at 95 and would just touch the inside edge of the black circle.</p><p>It's important to note that the marker indicates the <b>outside</b> edge of whatever is being drawn. So to draw CIRCLE(5, “black", “none") at 100, I have to take into account the width of the line and draw so that the outside edge of the circle is at 100.</p><p>I'll begin by putting in the basic structure for drawing a circle:</p><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="background-color: white; color: maroon; font-weight: bold;">function</span><span style="background-color: white;"> interpretCDL</span><span style="background-color: white; color: #808030;">(</span><span style="background-color: #fff2cc;">svg</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> cdl</span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> </span><span style="background-color: #fff2cc;">center<span style="color: #808030;">=</span><span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">,</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span></span><span style="background-color: white; color: #808030;">,</span><span style="background-color: white;"> debug</span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white; color: #0f4d75;">false</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> </span><span style="background-color: white; color: purple;">{</span><span style="background-color: white;">
</span><span style="background-color: #fff2cc;"><span style="color: maroon; font-weight: bold;">let</span> radius <span style="color: #808030;">=</span> <span style="color: #008c00;">100</span><span style="color: purple;">;</span></span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">for</span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white; color: maroon; font-weight: bold;">let</span><span style="background-color: white;"> i</span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white; color: #008c00;">0</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">i</span><span style="background-color: white; color: #808030;"><</span><span style="background-color: white;">cdl</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white; color: maroon; font-weight: bold;">length</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">i</span><span style="background-color: white; color: #808030;">++</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> </span><span style="background-color: white; color: purple;">{</span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">const</span><span style="background-color: white;"> op </span><span style="background-color: white; color: #808030;">=</span><span style="background-color: white;"> cdl</span><span style="background-color: white; color: #808030;">[</span><span style="background-color: white;">i</span><span style="background-color: white; color: #808030;">]</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">if</span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">op</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">op </span><span style="background-color: white; color: #808030;">==</span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #0000e6;">CIRCLE</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> </span><span style="background-color: white; color: purple;">{</span><span style="background-color: white;">
</span><span style="background-color: white; color: dimgrey;">// Draw a circle</span><span style="background-color: white;">
</span><span style="background-color: #fff2cc;">Draw<span style="color: #808030;">.</span>circle<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">-</span>op<span style="color: #808030;">.</span>lineWidth<span style="color: #808030;">/</span><span style="color: #008c00;">2</span><span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lineWidth<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>lineColor<span style="color: #808030;">,</span> op<span style="color: #808030;">.</span>fillColor<span style="color: #808030;">)</span><span style="color: purple;">;</span></span><span style="background-color: white;">
</span><span style="background-color: #fff2cc;">radius <span style="color: #808030;">-</span><span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>lineWidth<span style="color: purple;">;</span></span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">if</span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">debug</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> console</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white; color: maroon; font-weight: bold;">log</span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #0000e6;">Draw a circle.</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: purple;">}</span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon; font-weight: bold;">else</span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon; font-weight: bold;">if</span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">op</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">op </span><span style="background-color: white; color: #808030;">==</span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #0000e6;">SPACE</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> </span><span style="background-color: white; color: purple;">{</span><span style="background-color: white;">
</span><span style="background-color: white; color: dimgrey;">// Skip some space</span><span style="background-color: white;">
</span><span style="background-color: white; color: maroon; font-weight: bold;">if</span><span style="background-color: white;"> </span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white;">debug</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white;"> console</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white; color: maroon; font-weight: bold;">log</span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #0000e6;">Skip some space.</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: purple;">}</span><span style="background-color: white;"> </span><span style="background-color: white; color: maroon; font-weight: bold;">else</span><span style="background-color: white;"> </span><span style="background-color: white; color: purple;">{</span><span style="background-color: white;">
console</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white; color: maroon; font-weight: bold;">log</span><span style="background-color: white; color: #808030;">(</span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #0000e6;">executeCDL encountered an unknown opcode: </span><span style="background-color: white; color: maroon;">'</span><span style="background-color: white; color: #808030;">+</span><span style="background-color: white;">op</span><span style="background-color: white; color: #808030;">.</span><span style="background-color: white;">op</span><span style="background-color: white; color: #808030;">)</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: purple;">}</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: purple;">}</span><span style="background-color: white; color: purple;">;</span><span style="background-color: white;">
</span><span style="background-color: white; color: purple;">}</span><span style="background-color: white; color: purple;">;</span></pre>I've done a couple of things here. First, I'm now passing the SVG element and the center of the compass into the interpreter so it will know where to draw. Second, I've added the radius and started it at 100. Where the interpreter handles the CIRCLE operation, I've added in a call to Draw.circle, which will be responsible for actually drawing the circle. Note the third argument to Draw.circle, which is the radius of the circle. Naively this is the current radius, but I have to back off by half of the line's width as well, or else the outside of the circle will go outside of radius. After the circle is drawn I move the radius in by the width of the circle line, so that the next operation will start in the right place.<div><br /></div><div>Over in draw.js is the code for Draw.circle:</div><div><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> circle<span style="color: #808030;">(</span>svg<span style="color: #808030;">,</span> center<span style="color: #808030;">,</span> radius<span style="color: #808030;">,</span> lineWidth<span style="color: #808030;">,</span> lineColor<span style="color: #808030;">,</span> fillColor<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">circle</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">cx</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">cy</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> center<span style="color: #808030;">[</span><span style="color: #008c00;">1</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">r</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> radius<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke-width</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineWidth<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">stroke</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> lineColor<span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> fillColor<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></div><div>This is straightforward; it simply adds the SVG command to draw a circle to the SVG element that was passed into the interpreter. If you're working in something other than SVG (say, canvas) then you can replace this with the appropriate circle drawing code for your display.</div><div><br /></div><div>Testing this with “CIRCLE(1.5, “black", “none")" produces this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQYSLjy0x60tomG1iG8TDAPyTiuDyyjgvR3lauYvSY12B8w9vaJRcE08J5AVzJxVYDVjdeE07eiXst3UiRqigTHpqMEMCkwBEjXYH2a8rEvCyKTnuZaZL6eAzfhTI6ae9iIFyPPDf9B54x/s431/Image10.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="319" data-original-width="431" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQYSLjy0x60tomG1iG8TDAPyTiuDyyjgvR3lauYvSY12B8w9vaJRcE08J5AVzJxVYDVjdeE07eiXst3UiRqigTHpqMEMCkwBEjXYH2a8rEvCyKTnuZaZL6eAzfhTI6ae9iIFyPPDf9B54x/s16000/Image10.png" /></a></div>Here's a more interesting example CIRCLE(2, “black", “none") CIRCLE(2, “red", “red") CIRCLE(2, “black", “none"): <div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoTRfpVNpKy3My5jo8XCXFqVL_47UU9mir0-MCLxmBIG2RqaqsNgfgz_k_d9oc7Yjedz0H48zKlR9IEgIN7lsUiGcpKU4kqyNiSLiat6Xnv_3O5DM-VlprlshrNEsoqrKLUyJSMd_Oznf8/s336/Image11.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="268" data-original-width="336" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoTRfpVNpKy3My5jo8XCXFqVL_47UU9mir0-MCLxmBIG2RqaqsNgfgz_k_d9oc7Yjedz0H48zKlR9IEgIN7lsUiGcpKU4kqyNiSLiat6Xnv_3O5DM-VlprlshrNEsoqrKLUyJSMd_Oznf8/s16000/Image11.png" /></a></div><div>I've set the fill color of the second circle to red, so the inside of that circle is entirely red. The next circle goes down on top of that with no fill color. But no fill color doesn't hide the red, it just doesn't cover it over. So how do you get a band of color instead of a whole circle of color? You use a very wide line width, like so CIRCLE(2, “black", “none") CIRCLE(20, “red", “none") CIRCLE(2, “black", “none"):</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibpGlJjcwJLaPyjqyWVI-T-VvMRNXbKkTurTybhwL-nbPLWFPxKlelFHfh6RCTf7CqUrl5MBTid1WH1DlJpHAA1UniSrcDVr2I_LppwVpS8iDd-BgeSZraXkx2lYGsaTmOFj3-MAcocp-z/s330/Image12.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="270" data-original-width="330" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibpGlJjcwJLaPyjqyWVI-T-VvMRNXbKkTurTybhwL-nbPLWFPxKlelFHfh6RCTf7CqUrl5MBTid1WH1DlJpHAA1UniSrcDVr2I_LppwVpS8iDd-BgeSZraXkx2lYGsaTmOFj3-MAcocp-z/s16000/Image12.png" /></a></div>What about the other command, SPACE? This adds space between elements of the compass. But the spacing of the elements of the compass is controlled by the current radius. So to add space between elements, we just need to subtract that space from the radius:<br /><div><pre style="background: rgb(255, 255, 255);"> <span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op<span style="color: #808030;">.</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">SPACE</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Skip some space</span>
radius <span style="color: #808030;">-</span><span style="color: #808030;">=</span> op<span style="color: #808030;">.</span>n<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Skip some space.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span></pre></div><div>So now we can do this: CIRCLE(2, “black", “none") SPACE(2) CIRCLE(2, “black", “none") SPACE(2) CIRCLE(2, “black", “none"):</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRysjFpRoVknXIHzPUT7Xv0N3vV5-eVFDeZeYswErHMbY31Hk4LcjCKhp8BaBhhvlTReqZySWPeAZXOG4gsaljLvbLHwr-A459mqVVBX9M_BTLqO8yam55QS58aWeIl3dezf6U9BPJUPPY/s326/Image13.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="264" data-original-width="326" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRysjFpRoVknXIHzPUT7Xv0N3vV5-eVFDeZeYswErHMbY31Hk4LcjCKhp8BaBhhvlTReqZySWPeAZXOG4gsaljLvbLHwr-A459mqVVBX9M_BTLqO8yam55QS58aWeIl3dezf6U9BPJUPPY/s16000/Image13.png" /></a></div>Interestingly, we can use negative numbers in the SPACE command to move “backwards" -- making the radius larger. So, for example, we can draw a large band of color, go backwards to draw a line in the middle of the band, and then go forward to where we started like this CIRCLE(2, “black", “none") CIRCLE(20, “red", “none") SPACE(-11) CIRCLE(2, “green", “none") SPACE(9) CIRCLE(2, “black", “none"):</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvHPGMIEDJYRVO3em2zLj1Gk6TMEQzgmRL7VlcXZ8hZ6w1RsXc9QX_K_9eJBM4jZyghZAp-83HDHGhvax4oFgojsudVGCTjzzzFqEyaWiTn5CNjvU2nj86EHLGlKdCiKHDRQhkdTWeg5j5/s327/Image14.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="266" data-original-width="327" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvHPGMIEDJYRVO3em2zLj1Gk6TMEQzgmRL7VlcXZ8hZ6w1RsXc9QX_K_9eJBM4jZyghZAp-83HDHGhvax4oFgojsudVGCTjzzzFqEyaWiTn5CNjvU2nj86EHLGlKdCiKHDRQhkdTWeg5j5/s16000/Image14.png" /></a></div>I'll leave it to the reader to puzzle out the CDL, but this ability to move in and out will be handy when we're doing the procedural generation of compasses.<br /><div><br /></div><div>If you want to follow along from home, you can download the Part 4 branch from the <a href="https://github.com/srt19170/Procedural-Map-Compasses" target="_blank">Procedural Map Compasses repository</a> on Github, get the test web page up and open the console. <a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">Part 1</a> has 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 4 directory, and then select the test.html link. Obviously you can browse the code and other files, and make changes of your own. Try writing some CDL, inserting it into the test function, and using the button to generate it. You can also try out this code on the web at <a href="https://dragonsabound-part4.netlify.app/test.html">https://dragonsabound-part4.netlify.app/test.html</a> although you'll only be able to run the code, not modify it.</div><div><br /></div><div>Next time I'll start on generating radial elements.</div><div><br /></div><div><span style="font-weight: 700;">Suggestions to Explore</span></div><div><ul style="text-align: left;"><li>The CIRCLE command draws a solid circle. It might be useful to draw a dashed circle as well. There are various ways to do this, but the easiest is to use the “stroke-dasharray" style in SVG. Modify the CIRCLE command to take another parameter with the value for stroke-dasharray, and then use stroke-dasharray in Draw.circle to implement dashed circles. How did you handle solid circles?<br /><br /></li><li>If you implemented the “no fill" version of the CIRCLE command in Part 2 in the parser, you can now implement that command in the interpreter. Did you change interpretCDL() or Draw.circle, or both? Why did you choose that approach?<br /><br /></li><li>My <a href="https://heredragonsabound.blogspot.com/2019/02/map-borders-part-1.html" target="_blank">Map Borders Description Language</a> also used the idea of a cursor and drawing from the outside inward, and also had a command like SPACE for moving the cursor. MBDL also implemented a command that would save the current value of the cursor and then return to it later. Implement that idea in CDL -- a command to REMEMBER() the current cursor value and a command to RECALL() the cursor value. What happens if you REMEMBER() twice before RECALL()? What happens when you RECALL() twice? How would you extend this idea so that you could REMEMBER and RECALL multiple different values?<br /></li></ul></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-81220296928392531912021-11-10T19:11:00.000-05:002021-11-10T19:11:44.401-05:00Map Compasses (Part 3): A Minimal CDL InterpreterWelcome back to my blog series on implementing procedurally-generated map compasses! You may recall that by the end of the previous blog post I had a parser in Nearley that could recognize compass descriptions consisting of two commands: CIRCLE and SPACE. Given the description 'SPACE(10) CIRCLE(1.5, "black", "none")' our parser confirms this is legal and returns a parse tree with two items at the top-level, corresponding to the SPACE and the CIRCLE commands. That's nice, but how does it get us closer to drawing a circle?<div><br /></div><div>The commands I've created actually look something like Javascript function calls. If I had a Javascript function called “CIRCLE" that drew a circle I could just about execute the CIRCLE command as soon as it was parsed and recognized. And in fact Nearley provides you the capability to do that. To any Nearley rule, you can attach some Javscript to be executed when the rule is (successfully) completed. So I can take the CIRCLE rule:</div><div><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div><pre style="margin-bottom: 0px; margin-top: 0px; text-align: left;"><b>circleElement -> “CIRCLE" “(" decimal “," dqstring “," dqstring “)"</b></pre></div></blockquote><p>and augment it with some Javascript that should be run when the rule is complete: </p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><p style="text-align: left;"><b style="font-family: monospace; white-space: pre;">circleElement -> “CIRCLE" “(" decimal “," dqstring “," dqstring “)" {% CIRCLE(data) %}</b></p></blockquote><p>The Javascript to be run is enclosed in “{%" and “%}" and must be a function that takes a single argument. In this case, I'm calling a Javascript function called CIRCLE and passing in “data" as an argument. What's this “data" thing? It's the parse tree result for this rule. In this case, it's an array with 8 items. The first item is the string “CIRCLE". The second item is the string “(". The third item is the string that matched the “decimal" rule. And so on. So the CIRCLE Javascript function could use that data array to decide how to draw the circle and draw it right as the description of the circle is parsed. If I extended that to all parts of our language, I could draw the complete compass as the CDL description was being parsed in Nearley.</p><p>That's pretty cool, but there are a couple of reasons I don't want to draw that way. First of all, the kind of Javascript allowed in the post-processing step of Nearley rules is pretty limited. For example, I probably want to draw that circle in a particular page element but there isn't any easy way to pass in that information. Second, I would be limited to drawing routines that are neatly encompassed in a single Nearley rule. Doing something like saving a color and reusing it later would be difficult.</p><p>A more flexible approach is to use the post-processing to translate into an “intermediate" language -- some kind of data structure that's easier to use than text -- and then have an interpreter that translates from the intermediate language into a drawing. For the intermediate language I'm going to use a Javascript object for each CDL command and I'll fill the object in with an operation type and the parsed arguments.</p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><p style="text-align: left;"> <b style="font-family: monospace; white-space: pre;">circleElement -> “CIRCLE" “(" decimal “," dqstring “," dqstring “)"<br />{% data => ({op: “CIRCLE", lineWidth: data[2], lineColor: data[4], fillColor: data[6]}) %}</b></p></blockquote><p>So now I have a Javascript <a href="https://www.javascripttutorial.net/es6/javascript-arrow-function/" target="_blank">arrow function</a> that takes the parse result from this rule and uses it to fill in a Javascript object. Nearley will now return this object instead of the parse result. Once I've done this for all the commands in the Compass Description Language the parser will return a list of parsed objects instead of the parse tree.</p><p>The rule for the space command is straightforward:</p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><pre style="margin-bottom: 0px; margin-top: 0px; text-align: left;"><b>spaceElement -> “SPACE" “(" decimal “)" {% data => ({op:SPACE, n: data[2]}) %}</b></pre></blockquote><p>There's one more change that I need to make in the top-level rule, mostly for convenience:</p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><pre style="margin-bottom: 0px; margin-top: 0px; text-align: left;"><b>compassBG -> BGelement:+ {% data => data[0].map(a => a[0]) %}</b></pre></blockquote><p>Without this fix, every object is returned as an array of a single element. (In general, there might be multiple return values for every rule, but this grammar always has only one.) This strips off all those unnecessary arrays. </p><p>With these changes in place, when I run the parser the result is in the intermediate language -- an array of objects:<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmJhUKGnw6h4AjHXvSwVetO1tRm9JszTI0IYZcq2wYEi5Zp1k7E9b2JlcKWeAlZJd7YrTNqEqxnMpM4LQcN8qRkbug-skx-hiHB9-6s_UwY0aFXGW7Ti_u9FSjM4_4sUiIR7xjPO77qxcM/s426/Image6.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="270" data-original-width="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmJhUKGnw6h4AjHXvSwVetO1tRm9JszTI0IYZcq2wYEi5Zp1k7E9b2JlcKWeAlZJd7YrTNqEqxnMpM4LQcN8qRkbug-skx-hiHB9-6s_UwY0aFXGW7Ti_u9FSjM4_4sUiIR7xjPO77qxcM/s16000/Image6.jpg" /></a></div>Okay, we have a program! Now we need an interpreter.<p></p><p>An interpreter is a computer program that takes a computer program in a (usually) different language and <i>interprets</i> it, i.e., makes the program do what it is supposed to do. Of course writing an interpreter for a language like Javascript is a difficult undertaking, but CDL is a very simple language and I'll be able to write a very simple interpreter.</p><p>A program in CDL is just a list of objects, so at a high level the interpreter will just be a loop that runs through the list of objects and interprets each object based upon the operation:</p><pre style="background: rgb(255, 255, 255); text-align: left;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"><span style="color: maroon; font-weight: bold;">function</span> interpretCDL<span style="color: #808030;">(</span>cdl<span style="color: #808030;">,</span> debug<span style="color: #808030;">=</span><span style="color: #0f4d75;">false</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">for</span><span style="color: #808030;">(</span><span style="color: maroon; font-weight: bold;">let</span> i<span style="color: #808030;">=</span><span style="color: #008c00;">0</span><span style="color: purple;">;</span>i<span style="color: #808030;"><</span>cdl<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">length</span><span style="color: purple;">;</span>i<span style="color: #808030;">++</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">const</span> op <span style="color: #808030;">=</span> cdl<span style="color: #808030;">[</span>i<span style="color: #808030;">]</span><span style="color: #808030;">.</span>op<span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">CIRCLE</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Draw a circle</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Draw a circle.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>op <span style="color: #808030;">==</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">SPACE</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Skip some space</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Skip some space.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span> <span style="color: maroon; font-weight: bold;">else</span> <span style="color: purple;">{</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">executeCDL encountered an unknown opcode: </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>op<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre></pre></pre><p>This doesn't do much yet, but it's enough code to test out. I'll modify the code on the Test button so that it passes the parsed CDL to the interpreter.</p><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> <span style="color: maroon; font-weight: bold;">test</span><span style="color: #808030;">(</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">About to test.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">const</span> result <span style="color: #808030;">=</span> interpretCDL<span style="color: #808030;">(</span>parseCDL<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">SPACE(10) CIRCLE(1.5, "black", "none")</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #0f4d75;">true</span><span style="color: #808030;">)</span><span style="color: #808030;">,</span> <span style="color: #0f4d75;">true</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre><p>And here's the result:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhR-N1z32F-H8eT5u4EnPn2NEfD8oedjDdJfhtB2zq4DGQ_eBLX51p0LA5V4RJd_e-vFgiEPtRta9DN2B5qywA62h53AvsgXzpv2hhPCpf_woPu64L4nwiuYltRRUu-c7Lu6GSVECx9WYnn/s427/Image7.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="271" data-original-width="427" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhR-N1z32F-H8eT5u4EnPn2NEfD8oedjDdJfhtB2zq4DGQ_eBLX51p0LA5V4RJd_e-vFgiEPtRta9DN2B5qywA62h53AvsgXzpv2hhPCpf_woPu64L4nwiuYltRRUu-c7Lu6GSVECx9WYnn/s16000/Image7.jpg" /></a></div>So we've taken a simple program in CDL, parsed it and interpreted it!<div><br /></div><div>That's all fine, but eventually we're going to want to do more than print log messages to the console, so let's work on setting up an area to draw to the screen. There are basically two steps to this: (1) establish an element on the web page to hold the drawing, and (2) get a pointer for this element to the Javascript program.</div><div><br /></div><div>To draw in SVG, you need to have an <SVG> element in the web page. I've already done this in the test.html page in the repository:</div><div><pre style="background: rgb(255, 255, 255);"> <span style="color: #808030;"><</span><span style="color: #603000;">div</span> id<span style="color: #808030;">=</span><span style="color: maroon;">"</span><span style="color: #0000e6;">map</span><span style="color: maroon;">"</span> style<span style="color: #808030;">=</span><span style="color: maroon;">"</span><span style="color: #0000e6;">margin-left:10px;</span><span style="color: maroon;">"</span><span style="color: #808030;">></span>
<span style="color: #808030;"><</span>svg id<span style="color: #808030;">=</span><span style="color: maroon;">"</span><span style="color: #0000e6;">svg</span><span style="color: maroon;">"</span> width<span style="color: #808030;">=</span><span style="color: maroon;">"</span><span style="color: #0000e6;">500</span><span style="color: maroon;">"</span> height<span style="color: #808030;">=</span><span style="color: maroon;">"</span><span style="color: #0000e6;">500</span><span style="color: maroon;">"</span><span style="color: #808030;">></span><span style="color: #808030;"><</span><span style="color: #808030;">/</span>svg<span style="color: #808030;">></span>
<span style="color: #808030;"><</span><span style="color: #808030;">/</span><span style="color: #603000;">div</span><span style="color: #808030;">></span></pre></div><div><p>The basic structure of test.html is stolen from <b style="font-variant-caps: small-caps;">Dragons Abound</b>. There's a column along the left side of the web page that is reserved for controls like the Test button, and the remainder of the page is the “map" area and is filled with a 500x500 SVG.</p><p>To pass the SVG element into the Javascript requires some more work in the HTML file for the web page. That part looks like this:</p><pre style="background: rgb(255, 255, 255);"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial;"> <span style="color: #808030;"><</span>script type<span style="color: #808030;">=</span><span style="color: maroon;">"</span><span style="color: #0000e6;">module</span><span style="color: maroon;">"</span><span style="color: #808030;">></span>
<span style="color: maroon; font-weight: bold;">import</span> Compass from <span style="color: maroon;">'</span><span style="color: #0000e6;">./compass.js</span><span style="color: maroon;">'</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> testButton <span style="color: #808030;">=</span> document<span style="color: #808030;">.</span>getElementById<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">testButton</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> svg <span style="color: #808030;">=</span> d3<span style="color: #808030;">.</span>select<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">#svg</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
testButton<span style="color: #808030;">.</span>onclick <span style="color: #808030;">=</span> <span style="color: maroon; font-weight: bold;">function</span> <span style="color: #808030;">(</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
Compass<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">test</span><span style="color: #808030;">(</span>svg<span style="color: #808030;">)</span><span style="color: purple;">;</span> <span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: #808030;"><</span><span style="color: #808030;">/</span>script<span style="color: #808030;">></span></pre></pre>I've used the HTML <span style="color: #808030;"><</span><span style="background-color: white;">script</span><span style="color: #808030;">></span> tag to add Javascript to the web page. After importing the compass code, I use <span style="background-color: white;">getElementById() to get a pointer to the Test button. </span><span style="background-color: white;">getElementById() is a function that walks through HTML to find an HTML element with the specified name. In this case, the “document" variable refers to the current web page, so </span><span style="background-color: white;">document</span><span style="color: #808030;">.</span><span style="background-color: white;">getElementById() searches the current web page to find a named element. Once I've found the Test button I can attach the test function from my compass code to the button so that it runs when the button is clicked.</span></div><div><span style="background-color: white;"><br /></span></div><div><span style="background-color: white;">For the SVG element I do something a little bit different. Here I use the D3 select function to find the SVG element. This returns a D3 selection instead of the actual SVG element. </span><a href="https://d3js.org/" target="_blank">D3</a><span style="background-color: white;"> is a fantastic Javascript library that makes working with SVG much simpler than trying to manipulate the SVG directly. D3 is really intended for making data visualizations, not fantasy maps. I'm using it largely because I started years ago with Martin O'Leary's <a href="https://mewo2.com/notes/terrain/" target="_blank">mapmaking code</a>, and he used D3 so I did too. But I've found it useful enough to stick with it.</span></div><div><span style="background-color: white;"><br /></span></div><div><span style="background-color: white;">Lastly, let's check to make sure the SVG is working (and demonstrate how D3 eases using SVG). I'll add some code to draw a purple circle to the test function. That looks like this:<br /></span><pre style="background: rgb(255, 255, 255);"> svg<span style="color: #808030;">.</span>append<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">circle</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">cx</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">25</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">cy</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">25</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>attr<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">r</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: #008c00;">25</span><span style="color: #808030;">)</span>
<span style="color: #808030;">.</span>style<span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">fill</span><span style="color: maroon;">'</span><span style="color: #808030;">,</span> <span style="color: maroon;">'</span><span style="color: #0000e6;">purple</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span></pre></div>D3 adds methods to SVG selections, so adding new elements to the SVG is a simple as appending the proper object type and setting attributes and styles as necessary.<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz2QK1Eot7yaoserGGouwhxKVxkyqYmoGIjPsVQa3bVEOKEA74rh3a4XMtTxDgi6RLQik2BYnEIQUG2KNKFeXfXlA_HA57mmPAckoehR4sV6rCpoYjWjqFR9dPjuHk8PXn8HhysOFwXq16/s384/Image9.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="253" data-original-width="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz2QK1Eot7yaoserGGouwhxKVxkyqYmoGIjPsVQa3bVEOKEA74rh3a4XMtTxDgi6RLQik2BYnEIQUG2KNKFeXfXlA_HA57mmPAckoehR4sV6rCpoYjWjqFR9dPjuHk8PXn8HhysOFwXq16/s16000/Image9.png" /></a></div>And that's all it takes.<div><br /></div><div>A reminder that if you want to follow along from home, you need to download the Part 2 branch from the <a href="https://github.com/srt19170/Procedural-Map-Compasses/tree/Part-3" target="_blank">Procedural Map Compasses repository</a> on Github and get the test web page up and open the console. <span style="background-color: white;"><a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">Part 1 of this series</a> 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 3 directory, and then select the test.html link.</span> You can also try out this code on the web at <a href="https://dragonsabound-part3.netlify.app/test.html">https://dragonsabound-part3.netlify.app/test.html</a> although you'll only be able to run the code, not modify it.<br /><div><br /></div><div>Next time I'll start implementing the interpreter routines for the CDL commands.</div><div><br /><div><h4>Suggestions to Explore</h4></div><div><ul><li>The printed output of the CDL parser isn't very useful because the returned values just print as “[object Object]". Write a function to “pretty print" the CDL objects in some format that shows the operation name, e.g., “{CIRCLE op}" and use this to improve the debug output.<br /><br /></li><li>Read about D3 and see if you can add lines to the purple circle from the center out to 12 o'clock, 3 o'clock, 6 o'clock and 9 o'clock.<br /><br /></li><li>The if-then-else at the heart of the interpreter exists to associate the operation name (e.g., SPACE, CIRCLE) with the code to implement that operation. Another way to go from a string to an associated value in Javascript is to build an object that you use as a code dictionary:<br /><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">let</span> code <span style="color: #808030;">=</span> <span style="color: purple;">{</span><span style="color: maroon;">'</span><span style="color: #0000e6;">CIRCLE</span><span style="color: maroon;">'</span><span style="color: purple;">:</span> <span style="color: maroon; font-weight: bold;">function</span> <span style="color: #808030;">(</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Draw a circle.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span> <span style="color: purple;">}</span><span style="color: #808030;">,</span>
<span style="color: maroon;">'</span><span style="color: #0000e6;">SPACE</span><span style="color: maroon;">'</span><span style="color: purple;">:</span> <span style="color: maroon; font-weight: bold;">function</span> <span style="color: #808030;">(</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span> console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Skip some space</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span> <span style="color: purple;">}</span> <span style="color: purple;">}</span><span style="color: purple;">;</span>
</pre>Modify interpretCDL() to use a code dictionary as above. What are the advantages / disadvantages of this approach compared to the if-then-else approach?</li></ul></div></div></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div><div><div><pre style="background: rgb(255, 255, 255); color: black; text-align: left;"><br /></pre></div></div></div></blockquote><div><div><div><br /></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-27459927726193655332021-11-03T11:17:00.000-04:002021-11-03T11:17:54.735-04:00Map Compasses (Part 2): Compass Design Language & ParsingWelcome back to my blog series on implementing procedurally-generated map compasses! Last time I looked at a variety of map compasses and saw how they broke down into combinations of pointers and circles and wrote a simple code framework. This time I'm going to talk about the high level design of the code, and start creating a language to describe compasses.<br />
<br />
I intend to organize the map compass code into two stages:<div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUzlVJFIpZ676Hc4Sz1tCHHT5sevFS7dAjcBwPtbmNDnRJdxmBE_M28tCDwg95Rcy0_KYGN4d1zxNNFGFqtaGdR_imSUWUU_AbTAVuOZURrFQA9CCxlmV1EqfWIh2CXxmmvlBXRBilNX-S/s376/Image4.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="347" data-original-width="376" height="184" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUzlVJFIpZ676Hc4Sz1tCHHT5sevFS7dAjcBwPtbmNDnRJdxmBE_M28tCDwg95Rcy0_KYGN4d1zxNNFGFqtaGdR_imSUWUU_AbTAVuOZURrFQA9CCxlmV1EqfWIh2CXxmmvlBXRBilNX-S/w200-h184/Image4.png" width="200" /></a></div>
(That's some high-quality software engineering, folks!)<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
In the first step, I will <i>generate</i> the compass. By this I mean deciding what pointers and circles to draw, how to size and order them, what size lines to use and so on. The output of this step will be a description of the compass, i.e., something like “A 50 pixel radius, 5 pixel wide circle of alternating black and white segments, then 60 pixel radius black and white intercardinal pointers, then 75 pixel radius black and white cardinal pointers, and then labels on the cardinal points":<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib4sKJhLong2mKlKfGBDDO1AXpaZ0coRLnOg-7cykhBzof9r9qRBfW3iep8ZjRd3dHjuO1RNyfzK2Bitk3eDIZZFDrqjrk1FetGiBVn8M_trAlBmdjqHtRDedHw7Z-a4Vajse677oLqZpY/s1600/Image29.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="401" data-original-width="385" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEib4sKJhLong2mKlKfGBDDO1AXpaZ0coRLnOg-7cykhBzof9r9qRBfW3iep8ZjRd3dHjuO1RNyfzK2Bitk3eDIZZFDrqjrk1FetGiBVn8M_trAlBmdjqHtRDedHw7Z-a4Vajse677oLqZpY/s320/Image29.png" width="307" /></a></div>
<br />
Of course, I won't use English for this description but rather “Compass Description Language (CDL)," a structured language much like my <a href="https://heredragonsabound.blogspot.com/2019/02/map-borders-part-2.html" target="_blank">Map Border Description Language (MBDL)</a>. As with MBDL, one use of the CDL will be to easily save and reuse compass designs I particularly like. Another reason to use CDL is that it will let me implement some complex compass designs that would <a href="https://heredragonsabound.blogspot.com/2019/03/map-borders-part-8.html" target="_blank">otherwise be difficult</a>. So my code will look like this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPyqx-c9Lqzv5nxwFU-an-vZjpBCxRW4vTl7lif1r3jR6DauXOaszcL7Ijdl5qFZP-mZYJbIpHij3-MfkCRU2F5MG9Qjef8oHUJHFrtG7z6Yab8uHpsF3JaOhVV7uS7hvp6FZF_noV7_Yw/s430/Image5.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="430" data-original-width="383" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPyqx-c9Lqzv5nxwFU-an-vZjpBCxRW4vTl7lif1r3jR6DauXOaszcL7Ijdl5qFZP-mZYJbIpHij3-MfkCRU2F5MG9Qjef8oHUJHFrtG7z6Yab8uHpsF3JaOhVV7uS7hvp6FZF_noV7_Yw/w178-h200/Image5.jpg" width="178" /></a></div><div>
In the second step I will <i>draw</i> the compass. This involves parsing the CDL produced by the generator, and translating it into commands to render the compass onto the screen. In a little bit more detail, it will look like this:</div><div><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpTnIEewZa_geUB6iCodd3nHUq2IMUHdq8l_F_-1QM7C0JKsjecX54XMy_l_crhpV-oJqI5K4xLEytTFw4j9iScLF5T3oFAY7xuKDZ5D4wT-TEHTEMmk1zdMh5C89lBUL72X-GhrYn4Ows/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="554" data-original-width="877" height="404" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpTnIEewZa_geUB6iCodd3nHUq2IMUHdq8l_F_-1QM7C0JKsjecX54XMy_l_crhpV-oJqI5K4xLEytTFw4j9iScLF5T3oFAY7xuKDZ5D4wT-TEHTEMmk1zdMh5C89lBUL72X-GhrYn4Ows/w640-h404/image.png" width="640" /></a></div></div></div>The Generate step will be built around a grammar written in a procedural generation tool called <a href="https://rednoise.org/rita/index.html" target="_blank">RiTa</a>. That grammar will create a compass description in the Compass Description Language (CDL). The CDL description will then be parsed by a tool called <a href="https://nearley.js.org/" target="_blank">Nearley</a> into an intermediate representation that is more “machine friendly." The intermediate representation will then be executed by a Javascript execution engine. The output of that will be the SVG commands to draw the compass.</div><div><br /></div><div>This seems convoluted but using a rules grammar for procedural generation is common, and the pattern of “parse to an intermediate representation and execute that" is a typical pattern for building a language interpreter. And because I'll be using a couple of parsing and grammar tools, less code will be required than you might think from looking at that diagram. This architecture will also enable me to design, develop and test each function separately.<br /><br /></div><div>While I'm using SVG for <b style="font-variant-caps: small-caps;">Dragons Abound</b>, other people use a variety of different graphic display technologies. So I'll try to isolate the code that actually does the rendering in SVG, so that other users can easily swap in code to render in Canvas or whatever.<br />
<br />
I'll actually implement middle-out. I'll start by defining (a subset of) the Compass Description Language, and then I'll build the code to draw that. This has a couple of advantages. First, it gets to a point where I'm actually seeing things on the computer screen quickly, and that's always nice. It helps keep me motivated to see progress. Also, if something doesn't look right, I can notice that and correct it early. Second, creating the CDL and trying out different things helps explore and understand how a compass is drawn and what works and what doesn't. This will be useful when I get to the building the procedural generation.<br />
<br />
Rather than tackle the whole CDL at once, I'm going to do it in parts. Because the compass will be drawn back-to-front, I'm going to start by looking at the circular ornaments that are behind the pointers. Here's an example compass with an interesting background ornamentation:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhth1wm_R22eRRlKVQtGWsRhEWg9JMMEiHPnFxiyvCpqzaB-mYrpu0YFMimhGu_pR3rzwPMT-ps-pokcZiTrHJLAA3uY_PlBFmbkSWGIgyIKEybvBB50S6Gc5ZRm304mDdORDPpfU0iFUEF/s1600/compass1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="239" data-original-width="239" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhth1wm_R22eRRlKVQtGWsRhEWg9JMMEiHPnFxiyvCpqzaB-mYrpu0YFMimhGu_pR3rzwPMT-ps-pokcZiTrHJLAA3uY_PlBFmbkSWGIgyIKEybvBB50S6Gc5ZRm304mDdORDPpfU0iFUEF/s1600/compass1.png" /></a></div>
So how would I describe the background ornamentation on this compass? I'll work my way outside in, and I'll take my starting point as the tips of the cardinal pointers. So starting there and working in, first there's some empty space, then there's a circle, more empty space, a scale, more empty space, a circular arrangement of dots, more empty space, another circle, more empty space and then a filled black circle. If I translate that into something a bit more formal, it might look like this:<br />
<blockquote class="tr_bq">
<span style="font-family: "courier new" , "courier" , monospace;">SPACE<br />CIRCLE<br />SPACE<br />SCALE<br />SPACE<br />DOTS<br />SPACE<br />CIRCLE<br />SPACE<br />FILLED-CIRCLE</span></blockquote>
However, I note that the space between the last two circles is about twice as big as the other spaces. I could put the SPACE command in there twice, but it seems more general to add a parameter to the space command indicating how many pixels of space to leave:<br />
<blockquote class="tr_bq">
<span style="font-family: "courier new" , "courier" , monospace;">SPACE(5)<br />CIRCLE<br />SPACE(5)<br />SCALE<br />SPACE(5)<br />DOTS<br />SPACE(5)<br />CIRCLE<br />SPACE(10)<br />FILLED-CIRCLE</span></blockquote>
<div>
Likewise, I can add parameters for the line width on the circles and the scales.<br />
<blockquote class="tr_bq" style="font-family: "times new roman";">
<div style="margin: 0px;">
<span style="font-family: "courier new" , "courier" , monospace;">SPACE(5)<br />CIRCLE(1)<br />SPACE(5)<br />SCALE(8)<br />SPACE(5)<br />DOTS<br />SPACE(5)<br />CIRCLE(1)<br />SPACE(10)<br />FILLED-CIRCLE</span></div>
</blockquote>
</div>
Circles and filled circles only differ in whether they are filled, so I can make that a parameter as well:<br />
<div>
<blockquote class="tr_bq" style="font-family: "times new roman";">
<div style="margin: 0px;">
<span style="font-family: "courier new" , "courier" , monospace;">SPACE(5)<br />CIRCLE(1, None)<br />SPACE(5)<br />SCALE(8)<br />SPACE(5)<br />DOTS<br />SPACE(5)<br />CIRCLE(1, None)<br />SPACE(10)<br />CIRCLE(0, Black)</span></div>
</blockquote>
</div>
Adding the fill color makes me realize that I've been assuming that all the lines in the compass will be drawn in black (and indeed they almost always are) but it might make sense to add that as a parameter as well, so that I at least have the option to use a different color:<br />
<div>
<blockquote class="tr_bq" style="font-family: "times new roman";">
<div style="margin: 0px;">
<span style="font-family: "courier new" , "courier" , monospace;">SPACE(5)<br />CIRCLE(1, Black, None)<br />SPACE(5)<br />SCALE(8)<br />SPACE(5)<br />DOTS<br />SPACE(5)<br />CIRCLE(1, Black, None)<br />SPACE(10)<br />CIRCLE(0, Black, Black)</span></div>
</blockquote>
</div>
I'll return later to scales and dots, but you start to see the structure of CDL for these kinds of ornaments. In particular, there are two CDL commands that look pretty complete:<br />
<ul>
<li>SPACE(<i>n</i>) -- Move inward <i>n</i> pixels before drawing the next element.</li>
<li>CIRCLE(<i>lineWidth, lineColor, fillColor</i>) -- Draw a circle with a line that is <i>lineWidth</i> pixels wide with color <i>lineColor</i>, and fill the circle with color <i>fillColor</i>.</li>
</ul>
<div>
That seems like enough specification to implement these two commands, so I'll start with this as the core of CDL. (Of course I might be wrong about this and have to update these commands later.)</div><div><br /></div><div>Now I need to be able to take a compass described in CDL and turn it into the actions to draw the compass on the screen. This is similar to getting from the written representation of a computer program (e.g., in Javascript) to actually executing the program. The first step is to <a href="https://en.wikipedia.org/wiki/Parsing">parse</a> the language -- to translate it from the original text directly to an actual compass, or into some intermediate representation that makes it easier to translate into a compass.<br />
<br />
When I worked on procedural generation of map borders, I created and <a href="https://heredragonsabound.blogspot.com/2019/02/map-borders-part-2.html" target="_blank">parsed a Map Border Description Language (MBDL)</a> and I'll do the same thing with CDL. Both MBDL and CDL are examples of what is called a <a href="https://en.wikipedia.org/wiki/Context-free_grammar" target="_blank">context-free grammar</a>. There are any number of <a href="https://www.google.com/search?q=context-free+grammar+parsing+tool+in+javascript&oq=context-free+grammar+parsing+tool+in+javascript">Javascript parsing tools</a> for parsing context-free grammars. I settled on using <a href="https://nearley.js.org/">Nearley.js</a>, which seems to be a mature and (more importantly) well-documented tool.<br />
<br />
<div>
</div>
I'm not going to provide a primer on context-free grammars, but the syntax of Nearley is pretty straightforward and you should be able to get the idea without too much trouble. A Nearley grammar consists of a number of rules. Each rule has a symbol on the left side, an arrow, and then right side of the rule, which can be a sequence of symbols or non-symbols, and different options separated by the pipe “|" (or) operator:<br />
<blockquote class="tr_bq">
<pre><b>compass -> Element:+
Element -> spaceElement | circleElement</b></pre>
</blockquote>
<div>
Each rule says that the left side can be replaced by any of the options on the right side. So the first rule here says that a compass is one or more elements. (The :+ sign after Element is the <a href="https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form">EBNF</a> shorthand for “one or more of the preceding symbol.") The second rule says that a background element can be a space or a circle.<br />
<br /></div>
By the way, if you want to play around with this (or any other) grammar in Nearley there's an online playground <a href="https://omrelli.ug/nearley-playground/">here</a>. You can type in a grammar and test cases to see what matches and what doesn't.<br />
<br />
As I indicated above, a space element has a number as a parameter:<br />
<blockquote class="tr_bq">
<pre><b>@builtin “number.ne"
spaceElement -> “SPACE" “(" decimal “)"</b></pre>
</blockquote>
<div>
<div>
There are a couple of new things here. First, there are some strings in this new rule. Strings are the non-symbols or terminals of the grammar, so they represent something that appears in exactly that way in the language. In this case, the rule says that a spaceElement consists of the literal string “SPACE" followed by the literal string “(" followed by a decimal followed by the literal string “)". The other new element is decimal. Nearley has a couple of common elements built-in -- “decimal" being one of them -- so I can use that to recognize the numeric width in the space element. Decimal is exactly what you think -- a decimal number.<br />
<br />
So the rule above would recognize these examples as spaceElements:<br />
<blockquote class="tr_bq" style="-webkit-text-stroke-width: 0px; color: black; font-family: "Times New Roman"; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
<pre style="margin: 0px;"><b>SPACE(1)
SPACE(11.5)
SPACE(-45)</b> </pre>
</blockquote>
However, it would not recognize the following examples:<br />
<blockquote class="tr_bq" style="font-family: "times new roman";">
<pre style="margin: 0px;"><b>MOVE(1)
space(1)
SPACE( 1 )</b> </pre>
</blockquote>
</div>
<div>
It's good that it doesn't recognize MOVE(1) as a spaceElement, but the other two failures are a little annoying. It would be nice to have a little more flexibility in formatting. To begin with, in Nearly you can add an “i" to the end of any string in a rule to indicate that it is case insensitive:<br />
<blockquote class="tr_bq" style="-webkit-text-stroke-width: 0px; color: black; font-family: "Times New Roman"; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
<pre style="margin: 0px;"><b>spaceElement -> “SPACE"i “(" decimal “)"</b></pre>
</blockquote>
</div>
<div>
The new rule will recognize “SPACE," “space," and “Space" as well as other variants. Ignoring whitespace is a little more work. First let me define a rule to recognize whitespace:<br />
<div>
<blockquote class="tr_bq">
<span style="font-family: monospace;"><span style="white-space: pre;"><b>WS -> [ \t\n\v\f]:*</b></span></span></blockquote>
</div>
This rule uses a number of new features. The square brackets indicate a character set, and matches any character in the set. For example, “[abc]" would match any single a, b or c character. In this case, the character set matches spaces, tabs (\t), newline (\n), vertical tabs (\v) and form feed (\f) characters. Finally, the “:*" means to match “zero or more" of the preceding expression. So put all together, this means “zero or more of any white space character." So this will match a space, two spaces, two spaces and a tab, and so on.<br />
<br />
Now I can put this whitespace symbol anywhere that I want to be able to ignore white space. For example:<br />
<div style="-webkit-text-stroke-width: 0px; color: black; font-family: "Times New Roman"; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
<blockquote class="tr_bq" style="-webkit-text-stroke-width: 0px; color: black; font-family: "Times New Roman"; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
<pre style="margin: 0px;"><b>spaceElement -> “SPACE"i WS “(" WS decimal WS “)" WS</b></pre>
</blockquote>
</div>
Now my spaceElement will ignore whitespace between the various parts of the element. (All the WS elements can make it difficult to read a rule, so I may leave it out for clarity in the blog posts.)<br />
<br />
Here's the rule for the circle element:<br />
<blockquote class="tr_bq" style="-webkit-text-stroke-width: 0px; color: black; font-family: "Times New Roman"; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
<pre style="margin: 0px;"><b>@builtin “string.ne"
circleElement -> “CIRCLE" “(" decimal “," dqstring “," dqstring “)"</b></pre>
</blockquote>
The new element here (dqstring) is another Nearley builtin, and it matches a string enclosed in double quotes. So a circleElement has a number and two strings as its parameters.<br />
<br />
So our full grammar looks like this:<br />
<blockquote class="tr_bq" style="-webkit-text-stroke-width: 0px; color: black; font-family: "Times New Roman"; font-size: medium; font-style: normal; font-variant-caps: normal; font-variant-ligatures: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-decoration-color: initial; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;">
<pre style="margin: 0px;"><b>@builtin “number.ne"
@builtin “string.ne"
Compass -> Element:+
Element -> spaceElement | circleElement
WS -> [\s]:*
spaceElement -> “SPACE" WS “(" WS decimal WS “)" WS
circleElement -> “CIRCLE" WS “(" WS decimal WS “," WS dqstring WS “," WS dqstring WS “)" WS</b></pre>
</blockquote>
</div>
</div>
</div>
This grammar should recognize the following compassBG description:<br />
<div>
<blockquote class="tr_bq" style="font-family: "times new roman";">
<div style="margin: 0px;">
<span style="font-family: "courier new" , "courier" , monospace;">SPACE(5)<br />CIRCLE(1, "Black", "None")<br />SPACE(5)<br />CIRCLE(1, "Black", "None")<br />SPACE(10)<br />CIRCLE(0, "Black", "Black")</span></div>
</blockquote>
</div>
Using Nearley is a little more complicated than most Javascript libraries because you must pre-compile your Nearley grammar before using it. To do this, I put the above grammar into a file named “cdl.ne" and compiled it with “nearleyc cdl.ne -o cdl.js". This creates a Javascript file that defines the grammar and can be included in your code with<br />
<pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">import</span> CDL from <span style="color: maroon;">'</span><span style="color: #0000e6;">./cdl.js</span><span style="color: maroon;">'</span><span style="color: purple;">;</span></pre>
I've included the compiled file in the repository for this project, but if you want to compile your own Nearley grammars (or modify this one), you'll need to install the <a href="https://nearley.js.org/docs/getting-started#installation" target="_blank">Nearley compiler</a> on your own machine. (If you're content to use the compiled files I provide then you don't need to bother.) You also have to include the Nearly library in your program. I've put this in the Libraries folder of this project. The easiest way to include it is to add this line to the HTML for the web page:<div><pre style="background: rgb(255, 255, 255);"><span style="color: #808030;"><</span>script src<span style="color: #808030;">=</span><span style="color: maroon;">"</span><span style="color: #0000e6;">Libraries/nearley.js</span><span style="color: maroon;">"</span><span style="color: #808030;">></span><span style="color: #808030;"><</span><span style="color: #808030;">/</span>script<span style="color: #808030;">></span></pre>To use the parser, you create a new Parser object based upon the grammar, and then feed it a string:<br /><pre style="background: rgb(255, 255, 255);"><span style="color: maroon; font-weight: bold;">function</span> parseCDL<span style="color: #808030;">(</span>text<span style="color: #808030;">,</span> debug<span style="color: #808030;">=</span>false<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
<span style="color: dimgrey;">// Create a Parser object from our grammar.</span>
<span style="color: maroon; font-weight: bold;">const</span> parser <span style="color: #808030;">=</span> <span style="color: maroon; font-weight: bold;">new</span> Nearley<span style="color: #808030;">.</span>Parser<span style="color: #808030;">(N</span>earley<span style="color: #808030;">.</span>Grammar<span style="color: #808030;">.</span>fromCompiled<span style="color: #808030;">(</span>CDL<span style="color: #808030;">)</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: dimgrey;">// Parse text</span>
<span style="color: maroon; font-weight: bold;">const</span> result <span style="color: #808030;">=</span> parser<span style="color: #808030;">.</span>feed<span style="color: #808030;">(</span>text<span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">if</span> <span style="color: #808030;">(</span>debug<span style="color: #808030;">)</span> <span style="color: purple;">{</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Parse of </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>text<span style="color: #808030;">+</span><span style="color: maroon;">'</span><span style="color: #0000e6;"> is </span><span style="color: maroon;">'</span><span style="color: #808030;">+</span>parser<span style="color: #808030;">.</span>results<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">return</span> parser<span style="color: #808030;">.</span>results<span style="color: #808030;">[</span><span style="color: #008c00;">0</span><span style="color: #808030;">]</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre>
feed() returns an array of results because the grammar might be ambiguous, resulting in multiple ways to parse an input. However, the CDL grammar is not ambiguous so I'm only concerned with the first result.</div><div><br /></div><div>A reminder that if you want to follow along from home, you need to download the Part 2 branch from the <a href="https://github.com/srt19170/Procedural-Map-Compasses/tree/Part-2" target="_blank">Procedural Map Compasses repository</a> on Github and get the test web page up and open the console. <span style="background-color: white;"><a href="https://heredragonsabound.blogspot.com/2020/02/map-compasses-part-1.html" target="_blank">Part 1 of this series</a> 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 2 directory, and then select the test.html link.</span> You can also try out this code on the web at <a href="https://dragonsabound-part2.netlify.app/test.html">https://dragonsabound-part2.netlify.app/test.html</a> although you'll only be able to run the code, not modify it.<br />
<br />Either way, once you have the web page open you can hit the Test button on the web page, open the browser console, and you should see something like this:<br /></div><div><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFlt9p2teIIgwhXnhTsC5Mlr0chTrBYvwWL_7Lrq8vCChNbmWGPMbMxPU5pmpO83C2NicT7fzHCc1f4H5xxDrxJsWKQm9WPS7QydjtzWrGvPbpdVlRVLuoVnqZyqklOxwaxU5z_y-_FmTN/s484/Image1.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="484" data-original-width="429" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFlt9p2teIIgwhXnhTsC5Mlr0chTrBYvwWL_7Lrq8vCChNbmWGPMbMxPU5pmpO83C2NicT7fzHCc1f4H5xxDrxJsWKQm9WPS7QydjtzWrGvPbpdVlRVLuoVnqZyqklOxwaxU5z_y-_FmTN/s16000/Image1.jpg" /></a></div><div style="text-align: left;">Ignoring the actual result for the moment, you can see that the parse has succeeded and found all the elements of the compass description.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">If you edit compass.js and change the string being passed to parseCDL into something illegal (here I've added 'XX' at the front of the string), you'll see what happens when the parse fails:</div><div style="text-align: left;"><br /></div><div style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPGaRMv9wtBxtZVqz_Az1cUzbL0gGZeD4PHf_duWR3rWPovMHuRh34_3ekcPhVjjWog5lDK5KEilwbXFMFwbUaUh24mRA3Ziw7ZR1FVV7TASTcK_wZVtJSoGlGkgaijmVPi3cy24H-hnak/s485/Image2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="485" data-original-width="427" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPGaRMv9wtBxtZVqz_Az1cUzbL0gGZeD4PHf_duWR3rWPovMHuRh34_3ekcPhVjjWog5lDK5KEilwbXFMFwbUaUh24mRA3Ziw7ZR1FVV7TASTcK_wZVtJSoGlGkgaijmVPi3cy24H-hnak/s16000/Image2.png" /></a></div><br /><div class="separator" style="clear: both; text-align: left;">You get an error that tells you the character where the parse failed and what the parse was expecting to see instead.</div><div style="text-align: left;"><br /></div>To go back now to the actual result returned by the parser, if you look at it in the debugger you'll see it's an array with two items corresponding to the “spaceElement" and the “circleElement" from the input. And each of these items are themselves arrays with items corresponding to the parts of the space or circle element. This result corresponds to the <a href="https://en.wikipedia.org/wiki/Parse_tree" target="_blank">parse tree</a> of the input. I won't actually be using this (for reasons that will become clear) so we can safely ignore it.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">Now that we have a working grammar for the simple CDL subset, next time I'll tackle how to turn that into a drawing.</div><div style="text-align: left;"><br /></div><div style="text-align: left;"><div><h3 style="text-align: left;">Suggestions to Explore</h3></div><div><ul><li>Right now CDL requires commands to be in upper case. How would you modify the grammar to allow lower case and mixed case commands?<br /><br /></li><li>The SPACE command really moves inward and outward from the center. It might make more sense to call it the MOVE command, or at least permit MOVE as an alias. What's the best way to modify the grammar to allow both SPACE and MOVE as names for this command?<br /><br /></li><li>The CIRCLE command requires a fill color even if the color is “none" for no fill. Add a new rule for a version of the CIRCLE command that only takes two arguments. Can Nearley now parse both versions of the CIRCLE command? How does it tell them apart?</li></ul></div></div></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0tag:blogger.com,1999:blog-3367557180796013182.post-72785405203421145502021-10-28T17:06:00.002-04:002021-10-29T12:54:19.408-04:00Map Compasses (Part 1): IntroductionThis posting will start a new series on drawing map compasses. This topic has been on my TODO list since the great Oleg Dolya (aka <a href="https://watabou.itch.io/" target="_blank">watawatabou</a>) did <a href="https://www.reddit.com/r/proceduralgeneration/comments/b4tzxw/random_compass_roses/" target="_blank">a compass generator</a> about a year ago. Oleg's generator is very good and Oleg was kind enough to share his code with me, so honestly I could probably adapt that code for my own use. But for a few different reasons, I'd like to write my own generator.<br />
<br />
First, I want the learning experience of breaking down map compasses and figuring out how to generate them. This process helps me understand a lot about what makes something work and what doesn't, and hopefully also gives me insights into the creative process. Second, I want my compass generator to be based on a procedural grammar (as I did with map borders) so that it is easier to expand the generator to create new kinds of compasses. Third, I want to use this as an opportunity to share code and give some deeper insight into my development process.<div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG96H4bj-hHbPwlBmrCBlAZtwp8rxzo2dcLlYJ909q0hH-Q_KrKaYHu0vyjKS2bZJWhfiY3S2cZE_vWKSqrqdulAePZgb0f39hhR6BkNY687XBH3KkZCfxcmeizQXDp2oqAqwTd0Q7Oq6R/s640/Image12.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="400" data-original-width="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG96H4bj-hHbPwlBmrCBlAZtwp8rxzo2dcLlYJ909q0hH-Q_KrKaYHu0vyjKS2bZJWhfiY3S2cZE_vWKSqrqdulAePZgb0f39hhR6BkNY687XBH3KkZCfxcmeizQXDp2oqAqwTd0Q7Oq6R/s16000/Image12.png" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div>Sharing code like this will be a new adventure for me. Details are below, but each week I'll release both a new blog posting as well as the code that it discusses so that you can download it and play with it yourself. I've tried to make this as simple as possible, so that each download is complete and runs right out of the box without any further installs. (I guess you will need to supply your favorite text editor!) I'm going to start from bare ground, so even if you don't have any previous experience with building procedural generation or Javascript apps you should be able to follow along. (And you more expert readers will be bored initially.) Hopefully we'll end up with a fun and working compass generator, but the idea is really to walk through the whole process of development and give you a chance to understand and participate. To that end, each post will also end with some suggested topics that you can explore using that week's code.</div><div>
<br />
As always when I'm tackling a new topic, I like to gather up some good examples from existing fantasy maps as a guide. I've collected the compasses from all my reference maps as well as from maps on the <a href="https://www.cartographersguild.com/content.php" target="_blank">Cartographers' Guild</a> and from Oleg's generator. (All of the examples are available in the code repository for you to peruse.) Like map borders, compasses are an artistic element and there are a wide variety of styles and approaches. I couldn't find any definitive guide to the parts of a compass rose, so I'll use my own (hopefully self-descriptive) terminology.<br />
<br />
The simplest compasses are nothing more than an arrow and a letter:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirXKKoFdmAH0-v0GLinIRGyUQ0QAT4WM56zxHXuzcCcKFy9kVQth1YRO_SJJB4AjjzPTZXQfdeGTMT9DnyL8tALRsSCWLK7_17tbrSpLWvsVCX1pBu_Tn9dvVuTmwsYx448TfZ7MLDGnL6/s1600/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="176" data-original-width="143" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirXKKoFdmAH0-v0GLinIRGyUQ0QAT4WM56zxHXuzcCcKFy9kVQth1YRO_SJJB4AjjzPTZXQfdeGTMT9DnyL8tALRsSCWLK7_17tbrSpLWvsVCX1pBu_Tn9dvVuTmwsYx448TfZ7MLDGnL6/s1600/Image1.png" /></a></div>
The most complex are works of art:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_ss0qVbSXj1nZZp2Jua3FOS3hlyrSs6Z3fvaWPeuHgw6nqh6K4Df_r_N2NaIimecTU1Y7rxBceTkNJQfhh4zJuBfeQ0rBx76fT_mejuUC7bDRx1zGHO9KZWqY7BJ1F0Y7EJGJUb9ikIVk/s1600/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="340" data-original-width="276" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_ss0qVbSXj1nZZp2Jua3FOS3hlyrSs6Z3fvaWPeuHgw6nqh6K4Df_r_N2NaIimecTU1Y7rxBceTkNJQfhh4zJuBfeQ0rBx76fT_mejuUC7bDRx1zGHO9KZWqY7BJ1F0Y7EJGJUb9ikIVk/s1600/Image1.png" /></a></div>
Obviously these are beyond my capability to generate. However, there are a large number of compasses based upon “circles and arrows":<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3XnLw8Pr5-rZ7UzuZ43CDyMLzsS9HDOa6xNQxJaiEe2Vg5_n-A7vuswFl1XMzO51EeM1vs4rzV6FUjnVgU7uBoLc9YlzIjMzlllq8kSvGCEbgxAtrTh4OTP9L231BmSMEBXKoSht4aldR/s1600/compass5.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="600" data-original-width="604" height="317" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3XnLw8Pr5-rZ7UzuZ43CDyMLzsS9HDOa6xNQxJaiEe2Vg5_n-A7vuswFl1XMzO51EeM1vs4rzV6FUjnVgU7uBoLc9YlzIjMzlllq8kSvGCEbgxAtrTh4OTP9L231BmSMEBXKoSht4aldR/s320/compass5.png" width="320" /></a></div>
This style of compass has various sorts of arrows toward the different map directions (these are called compass points) and then various circular ornaments interleaved with the pointers. The parts of these style compasses are fairly simple, but can be combined in many ways to create a nice variety. For what I imagine are many of the same reasons, these are also the sorts of compasses Oleg's generator produces:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxk6oPGeHwqZv74CSd_F96ZSxaQh1HsOj_8p2HAWc_8J0V3hMfLDE2Uw0bP5-1G_ZnKxGEXxrOQEEpZUNaNfZT9bCUJ0VeSSPhHzOHxr-x_nX7VOVNqkIC1PBcQLO_UgvyouE6BelxUuzX/s1600/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="301" data-original-width="591" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxk6oPGeHwqZv74CSd_F96ZSxaQh1HsOj_8p2HAWc_8J0V3hMfLDE2Uw0bP5-1G_ZnKxGEXxrOQEEpZUNaNfZT9bCUJ0VeSSPhHzOHxr-x_nX7VOVNqkIC1PBcQLO_UgvyouE6BelxUuzX/s400/Image1.png" width="400" /></a></div>
So let's talk about pointers.<br />
<br />
The most common pointer by far is the kind of triangle/diamond shape seen above. It is often split into two halves with one half black and the other white, but entirely black and outlined white are common as well. Typically near the center of the compass it narrows back to a point to create a diamond shape, but this is often covered by a circle ornament, so it appears to be a triangle.<br />
<br />
One variant of triangle style pointers is to make them wavy as in this example:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-hzEmAhbsDX63-mO6nNr_WnryulPIng4d7eC_eiJQHCDNwjJyhxKEPQkvDc04ueyq545V2bV_Bct0f4IaPwKiQESL53dO4FSz4Ost_D8l1u5dhLo4HTCXK6Fr3RTOEVKjCt-ZtZynE9cr/s1600/compass4.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="770" data-original-width="770" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-hzEmAhbsDX63-mO6nNr_WnryulPIng4d7eC_eiJQHCDNwjJyhxKEPQkvDc04ueyq545V2bV_Bct0f4IaPwKiQESL53dO4FSz4Ost_D8l1u5dhLo4HTCXK6Fr3RTOEVKjCt-ZtZynE9cr/s320/compass4.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
A very particular variant of the triangle pointer style turns all the pointers into rays and adds a central sun:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoLKOhuxuMjU1WBDv46nJVW5AN0jXHYAQ2MAuzRpf0kLc2N1yXCgkDSc2JtGgSR_Qk8ZLTZTjwjcjLfnRupLNUOQZalNKjMh_Vc8TgLi4hnKzDibKcD0NPwqH7K9c6AFzmItzoxlcnovaV/s1600/Image31.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="510" data-original-width="512" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoLKOhuxuMjU1WBDv46nJVW5AN0jXHYAQ2MAuzRpf0kLc2N1yXCgkDSc2JtGgSR_Qk8ZLTZTjwjcjLfnRupLNUOQZalNKjMh_Vc8TgLi4hnKzDibKcD0NPwqH7K9c6AFzmItzoxlcnovaV/s200/Image31.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6qRg2qA2rjnYySFh_ohyNZDKLHJrO2ivSVXInHXf5VgCQZlrbkNORbiYrjGXucHgo1qiGQqwu-cDzEDAS0aHuZyQfTbqdAgab4ZaEKHp9D0bVI_I2QWAPpJ1KVbmB5VSl46rrBFrdKm0N/s1600/Image36.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="670" data-original-width="671" height="199" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6qRg2qA2rjnYySFh_ohyNZDKLHJrO2ivSVXInHXf5VgCQZlrbkNORbiYrjGXucHgo1qiGQqwu-cDzEDAS0aHuZyQfTbqdAgab4ZaEKHp9D0bVI_I2QWAPpJ1KVbmB5VSl46rrBFrdKm0N/s200/Image36.png" width="200" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Apart from the triangle style, the next most common pointer is some variant on an arrowhead:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd-dZedvFN4o7E4cPaBbYgeNULVb9GgG2122qGCyGjEkUye8QygBPodNy6mC_60U3IH22I-xZuhWlA0Cm7Hjs2jvqoTJZB6gGx3OdWd3HJHTiT4bN6T9ETEUrRRHeAtr6zJ0RXGIdziFOf/s1600/Image12.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="380" data-original-width="404" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd-dZedvFN4o7E4cPaBbYgeNULVb9GgG2122qGCyGjEkUye8QygBPodNy6mC_60U3IH22I-xZuhWlA0Cm7Hjs2jvqoTJZB6gGx3OdWd3HJHTiT4bN6T9ETEUrRRHeAtr6zJ0RXGIdziFOf/s320/Image12.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
Occasionally there will be a more artistic version of a pointer, although typically still some variant on an arrowhead:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZLlaDN3bKcWHyDZvKpkhbjThpW5XyWwivBJBecJm4XmjMsXIBFFeL81_s4Xv-f_aa7aQdz2xXfegwNRLmyHiuczoeNZy-StLiWM5ukmlz07iIBhvai_sKq_A8B6V-JvoNkdSKorK2SL22/s1600/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="284" data-original-width="564" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZLlaDN3bKcWHyDZvKpkhbjThpW5XyWwivBJBecJm4XmjMsXIBFFeL81_s4Xv-f_aa7aQdz2xXfegwNRLmyHiuczoeNZy-StLiWM5ukmlz07iIBhvai_sKq_A8B6V-JvoNkdSKorK2SL22/s1600/Image1.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
In very artistic compasses, the pointers may be swords or some other figurative element.<br />
<br />
The arrangement of the pointers always includes the four cardinal directions (N/S/E/W). (Very occasionally a compass will have only a North pointer, but this is usually a very simple compass or a very artistic one.) The pointers for the cardinal directions are always the longest pointers in the compass. Other pointers are usually shorter than the cardinal pointers, but sometimes they're all the same length. Very occasionally the North pointer is longer than the other cardinal directions:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4fHgzHPReK2cXBqLMMLKQ_FGosWJMmLDN5yiUUnn0JVLutcpDnBS4d2Jag6U_yE4Pcv3PWj6uMHYWSHT7AcELhwfnPa9pO0_MjHwZ2kc83d-iF1qjjpan9ynbBqF6WxQV_twfAUigQwpN/s1600/Image13.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="270" data-original-width="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4fHgzHPReK2cXBqLMMLKQ_FGosWJMmLDN5yiUUnn0JVLutcpDnBS4d2Jag6U_yE4Pcv3PWj6uMHYWSHT7AcELhwfnPa9pO0_MjHwZ2kc83d-iF1qjjpan9ynbBqF6WxQV_twfAUigQwpN/s1600/Image13.png" /></a></div>
<br />
Or the North pointer may be distinguished with an ornament, such as a fleur de lis:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCkBKNmjNvc86jR9iBliGHmTD7R2VzNtLaYelrtgARFgY2rx3r8A6tQ5aV_m8wJ_uazzcY5k1U4L_rGplrr0VtnxIRjhun7WxaReW4sp4_dVz9m6hOW2-J0lWPxCKEX29BR1iV443Hcyky/s1600/Image20.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCkBKNmjNvc86jR9iBliGHmTD7R2VzNtLaYelrtgARFgY2rx3r8A6tQ5aV_m8wJ_uazzcY5k1U4L_rGplrr0VtnxIRjhun7WxaReW4sp4_dVz9m6hOW2-J0lWPxCKEX29BR1iV443Hcyky/s1600/Image20.png" /></a></div>
There can be many pointers between the cardinal pointers, but they're always symmetric. In some cases the finer pointers are just lines:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhBubw5JgRkaOhnNDQ2PWxKZx2kFPigyKkGPkUN770jxPpwhdramTpJTsVeynsYZW2dYE0vifn5Qp-kPdk6S9cMQ5ZyAhxZknGtIIut2HM0IXovAQdCchdEsMV2q9adpXlSkeFeNrnoAiK/s1600/Image11.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="444" data-original-width="428" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhBubw5JgRkaOhnNDQ2PWxKZx2kFPigyKkGPkUN770jxPpwhdramTpJTsVeynsYZW2dYE0vifn5Qp-kPdk6S9cMQ5ZyAhxZknGtIIut2HM0IXovAQdCchdEsMV2q9adpXlSkeFeNrnoAiK/s400/Image11.png" width="385" /></a></div>
The pointers are often labeled with directions. Most commonly the cardinal directions are labeled, but sometimes the intercardinal directions (NE/SE/SW/NW) are labeled as well. Very occasionally even finer divisions are labeled:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIX-LKCkgcd9yQDaTgDUpGC_WSqwJ4EG2-cP-Dzuh-kAM_5Wp8SQWWlHbU6E5IqBxLiCmgBHLBMdE5MhhSxYE1asv1B2Vb0H1zZXRsHOceRcmimmhhkm9b3iUP5UE4-4FYjOjTG3bsDxO_/s1600/Image37.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="444" data-original-width="452" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIX-LKCkgcd9yQDaTgDUpGC_WSqwJ4EG2-cP-Dzuh-kAM_5Wp8SQWWlHbU6E5IqBxLiCmgBHLBMdE5MhhSxYE1asv1B2Vb0H1zZXRsHOceRcmimmhhkm9b3iUP5UE4-4FYjOjTG3bsDxO_/s200/Image37.png" width="200" /></a></div>
There are a <a href="https://en.wikipedia.org/wiki/Points_of_the_compass" target="_blank">variety of different names</a> for the compass points:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjx-tK2F56wr74jmqBxT0Hq5LLBPL_-P2gPm0ClerrcUwOjk7_k5Z417o5-itTpSdDyvjsvVz8nR_ceLpW2IyfNl7rUAt6LSgXexZtNhbZZiTvEE6rcadOLpkR2mIJ_9fBdthEyYYWL5rMU/s1600/compass13.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="475" data-original-width="475" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjx-tK2F56wr74jmqBxT0Hq5LLBPL_-P2gPm0ClerrcUwOjk7_k5Z417o5-itTpSdDyvjsvVz8nR_ceLpW2IyfNl7rUAt6LSgXexZtNhbZZiTvEE6rcadOLpkR2mIJ_9fBdthEyYYWL5rMU/s320/compass13.jpg" width="320" /></a></div>If the directions are labeled, they are (almost always) the outermost elements of the compass. They often stand alone, but are sometimes placed in a ring as in this example:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwfj8C6ZnvMOV1RT0d1vG_x29Tiey7Ys_ESiQUpCiP2qbk2kAQO663IXbPifVSUl8anUSdeSbgVYQA0TrpAunAgsaixNAgjgCDr7oEyORZGttKlYwRgRsQ_tkXtdpsT7wltTg87CfqkgA_/s1600/Image26.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="639" data-original-width="646" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwfj8C6ZnvMOV1RT0d1vG_x29Tiey7Ys_ESiQUpCiP2qbk2kAQO663IXbPifVSUl8anUSdeSbgVYQA0TrpAunAgsaixNAgjgCDr7oEyORZGttKlYwRgRsQ_tkXtdpsT7wltTg87CfqkgA_/s320/Image26.png" width="320" /></a></div>
If a map contains windrose (or compass) lines, at least one set originates at the compass:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqclEcaznhLARmRgwkxGQos440vy5RfgHweIhOa1jS-TcCCH-lU0IcWmfSJ_PZab_iB7qZXrJH-SMSLK1kTHp2s1p9Pg4NOTAQU__OMTqF_fXJO2Y9n-U0xanPNU9HintzowF4V5O9MfdC/s1600/compass4.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="416" data-original-width="378" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqclEcaznhLARmRgwkxGQos440vy5RfgHweIhOa1jS-TcCCH-lU0IcWmfSJ_PZab_iB7qZXrJH-SMSLK1kTHp2s1p9Pg4NOTAQU__OMTqF_fXJO2Y9n-U0xanPNU9HintzowF4V5O9MfdC/s320/compass4.png" width="289" /></a></div>
But these aren't properly part of the compass and I won't be concerned with generating them.<br />
<br />
It's not unusual for a compass rose to be only pointers (as in the example immediately above) but they often include circular elements as well. So let's talk about circle elements.<br />
<br />
One simple case is a circle or two set behind the pointers:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5Ay7IehgAd8yik2YrvnntUlA9uGKyQDWC4fPSrroGMN5kLF4RimgMPpHwCl3In7uvyPf3Wl9d0_uOySKE0fLVzWiUxYqctH70LMvJ-WsVma70mTzeMyIiSs5VGA_TETiskyCUXeZedbEg/s1600/compass14.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="415" data-original-width="440" height="301" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5Ay7IehgAd8yik2YrvnntUlA9uGKyQDWC4fPSrroGMN5kLF4RimgMPpHwCl3In7uvyPf3Wl9d0_uOySKE0fLVzWiUxYqctH70LMvJ-WsVma70mTzeMyIiSs5VGA_TETiskyCUXeZedbEg/s320/compass14.jpg" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
A variant of this replaces the circle with a scale of alternating black and white bars, similar to the scales used in map borders:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPQj_m8vHzBWi0jfKEKI2dJJ6jCsGQENjNf61ZYrUmAxdZF0_OCKq9Q18j_tK7zuL9OKEJxvNZhNViAXBcKQdjfjAdsIj1p4qfH3hykJATL9yuBVB-DsZD-7nI93KoBjPdZ-7oIujiG9Vk/s1600/Image29.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="401" data-original-width="385" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPQj_m8vHzBWi0jfKEKI2dJJ6jCsGQENjNf61ZYrUmAxdZF0_OCKq9Q18j_tK7zuL9OKEJxvNZhNViAXBcKQdjfjAdsIj1p4qfH3hykJATL9yuBVB-DsZD-7nI93KoBjPdZ-7oIujiG9Vk/s200/Image29.png" width="191" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
The scale serves much the same purpose as the pointers, breaking up the circumference of the rose into additional directions.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
A more complex version of the scale is a kind of vernier scale that provides finer graduations:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLM8tg9g3xETjYqdUIDfv0D3auqKMPpfaqkkkVenh6Vvm4KseT0VF41GO3z-Nt9lC1r3qiqVK0QVSlRTuL4CN8Q5VBm5zT4HsfJRlhLvC-j6IdIr-U-CS-ZQqx6g0MWGfY9P1gN7k42QYR/s1600/Image38.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="491" data-original-width="488" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLM8tg9g3xETjYqdUIDfv0D3auqKMPpfaqkkkVenh6Vvm4KseT0VF41GO3z-Nt9lC1r3qiqVK0QVSlRTuL4CN8Q5VBm5zT4HsfJRlhLvC-j6IdIr-U-CS-ZQqx6g0MWGfY9P1gN7k42QYR/s320/Image38.png" width="318" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
As in this case, the vernier scale may be marked as well, although this is a modern invention and not usually seen on fantasy maps.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Generally a scale or vernier scale is outside of the pointers but occasionally (as in one of the examples above) the scale is inside the pointers. Here's an example where there is a vernier scale of sorts inside the pointers:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9-T0A195vOy03Yt8ogiipBuu5gMwQD3yngtzlluwEgjLffcFOa0dq7T-SvuhRNBc9B2VuacDzLxqfSszogpBlFI8SnSSwczAptoJydKAs-6RHF0qZwKbrPNasRhVIfEdWK7rheFhGGpmL/s1600/compass8.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="610" data-original-width="459" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9-T0A195vOy03Yt8ogiipBuu5gMwQD3yngtzlluwEgjLffcFOa0dq7T-SvuhRNBc9B2VuacDzLxqfSszogpBlFI8SnSSwczAptoJydKAs-6RHF0qZwKbrPNasRhVIfEdWK7rheFhGGpmL/s320/compass8.png" width="240" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Another common design is a sequence of circles of differing radii:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixLIc9YB6AMchyLRlulRKiOW2uC_mGsubmB66wuySC3t4wFD1t4PO_9KLzutZ-nEc-ZEWg18jaA3d3ip22zSK7rGYgPUMVSmCinOt0ZKRcgk2kofP99rk8-wYbxCn9wTRZgjkr2LGDANVd/s1600/Image39.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="505" data-original-width="489" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixLIc9YB6AMchyLRlulRKiOW2uC_mGsubmB66wuySC3t4wFD1t4PO_9KLzutZ-nEc-ZEWg18jaA3d3ip22zSK7rGYgPUMVSmCinOt0ZKRcgk2kofP99rk8-wYbxCn9wTRZgjkr2LGDANVd/s320/Image39.png" width="309" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
A variant is to draw the circles as bands -- two lines with a contrasting color between:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj34Pa0Gw4LYx7luQPXNBdqAHY9XePyBoosK1oV0ZvdsPizIusYphEgDM6JFsWJcGdeELlPg1RDQR4ll6vr6RbZ33d_dmygWDaL3J0nBzp2kkPEalxIlgRaoF-pnOfva2o_ysIYSVMmV8g/s1600/compass15.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="824" data-original-width="689" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj34Pa0Gw4LYx7luQPXNBdqAHY9XePyBoosK1oV0ZvdsPizIusYphEgDM6JFsWJcGdeELlPg1RDQR4ll6vr6RbZ33d_dmygWDaL3J0nBzp2kkPEalxIlgRaoF-pnOfva2o_ysIYSVMmV8g/s320/compass15.jpg" width="267" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
This may be more common in colored compass roses, where it is easy to distinguish the band. In a mono-color rose, something of the same effect can be accomplished by filling the space between two circles with a repeated ornament, as in this rose:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOZwQnyX-8-Mnou_Aycu26MqEMYmfqmRoEMNNNiAoIkHtF8Y5YDKYZVHqnx9sFHoxuf10VhqV3d0Ox2kzGCc-lqcSE_9_xeiSAe-H-6UND0ZisXi-CemfxxSB_wx_sBodN5AKlGT8h8huW/s1600/compass.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="425" data-original-width="425" height="319" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOZwQnyX-8-Mnou_Aycu26MqEMYmfqmRoEMNNNiAoIkHtF8Y5YDKYZVHqnx9sFHoxuf10VhqV3d0Ox2kzGCc-lqcSE_9_xeiSAe-H-6UND0ZisXi-CemfxxSB_wx_sBodN5AKlGT8h8huW/s320/compass.jpg" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
The addition of simple dots between two lines makes it read as a band. This example has a more complex pattern within a band:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2AGgV_QmNsl9udIYGghzrIuiqd7ZUiShWVRbJ6mO5YVo7VxnlhW1JzPHE0BqO4CWFBNHVGQFpiV3PpcDebfwmP1dc-cU0QDFVMoACXSiK7uoBUjixImfvVtKvEs8kzQHYocP1HwEJTU05/s1600/compass1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="400" data-original-width="422" height="303" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2AGgV_QmNsl9udIYGghzrIuiqd7ZUiShWVRbJ6mO5YVo7VxnlhW1JzPHE0BqO4CWFBNHVGQFpiV3PpcDebfwmP1dc-cU0QDFVMoACXSiK7uoBUjixImfvVtKvEs8kzQHYocP1HwEJTU05/s320/compass1.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
While it is common for all of the circle elements to lie entirely behind the pointers, it is also common for some of the circle elements to lie atop the pointers:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixP6u-z2LyiOW6NwtVHtWMhVPvSK2Fs0DRkqODvBUZO70LZ4b7MkknaxgiuVOweZmCjaJw08N2aLjaXuWrvOz5Q46ep7SUCl5Kasil1svdqNld8PnqK20pZjGLJvhKPaJzfckdUfsjxdOA/s1600/Image13.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="270" data-original-width="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixP6u-z2LyiOW6NwtVHtWMhVPvSK2Fs0DRkqODvBUZO70LZ4b7MkknaxgiuVOweZmCjaJw08N2aLjaXuWrvOz5Q46ep7SUCl5Kasil1svdqNld8PnqK20pZjGLJvhKPaJzfckdUfsjxdOA/s1600/Image13.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
Here the outer band is atop the pointers while the inner band is behind the pointers.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Occasionally a circular element lies atop some pointers and below others:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPAhokOfXgCUtyftFqJTLGB1D2NeMLq95nmjt0s5-i7rHmAgO82Z1EGIBFC7H1Jk81I0XAwY-cbARqWJjw6x1k5OdlZX_xn_Jd4nbnEmFsM8Zqk1hhe_YaUmbTGl4HO_ZNFs7MSTxLKGHc/s1600/Image20.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="244" data-original-width="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPAhokOfXgCUtyftFqJTLGB1D2NeMLq95nmjt0s5-i7rHmAgO82Z1EGIBFC7H1Jk81I0XAwY-cbARqWJjw6x1k5OdlZX_xn_Jd4nbnEmFsM8Zqk1hhe_YaUmbTGl4HO_ZNFs7MSTxLKGHc/s1600/Image20.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
In these cases, the band is almost always “between" the cardinal pointers and the intercardinal pointers.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
It is very common to have circle elements covering the middle part of the rose where the pointers meet:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQUzP6Q0srwsQK2sdrKeXhsIcFD-xee4IXlS1RVFm-CPN-oIR71TRF_klh1-gv9ruUl-YQMlpUBMTrTTCVuAaXFAEP0lWb5JjHW7VNqb3ixo-pvmVnZvzdsPLXiDqmqiPXXGD3oVB6L1ww/s1600/Image17.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="169" data-original-width="175" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQUzP6Q0srwsQK2sdrKeXhsIcFD-xee4IXlS1RVFm-CPN-oIR71TRF_klh1-gv9ruUl-YQMlpUBMTrTTCVuAaXFAEP0lWb5JjHW7VNqb3ixo-pvmVnZvzdsPLXiDqmqiPXXGD3oVB6L1ww/s1600/Image17.png" /></a> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhk-80dReNOyGTLS3ZhKzato5N5XtvTBkTmjt9A0oDarbKt4-EWwvA4gGMcmptiVN46BkM9Wl67OYWELk0rTmGQEmq6K67IS2vM4rI7e7JdvvhR1CaVXy80JHOjbxokncDTjZvA5-RELExu/s1600/compass1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="239" data-original-width="239" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhk-80dReNOyGTLS3ZhKzato5N5XtvTBkTmjt9A0oDarbKt4-EWwvA4gGMcmptiVN46BkM9Wl67OYWELk0rTmGQEmq6K67IS2vM4rI7e7JdvvhR1CaVXy80JHOjbxokncDTjZvA5-RELExu/s200/compass1.png" width="200" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
It's not unusual for the center to be filled with a decorative element of some sort:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTSj6mU67DKaos3IsGv24NsPBd-m0e3mt-kLxsHUQzmo76btp-4RcPccJ3Fyu5nfkJ0lOHr1D48o0kEELgi2WPdPeO1dpXB6ggMzo6QDpHJaFWbRc8YIznWrZoXb0jGqilqpBC8ZUbqT49/s1600/Image16.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="458" data-original-width="415" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTSj6mU67DKaos3IsGv24NsPBd-m0e3mt-kLxsHUQzmo76btp-4RcPccJ3Fyu5nfkJ0lOHr1D48o0kEELgi2WPdPeO1dpXB6ggMzo6QDpHJaFWbRc8YIznWrZoXb0jGqilqpBC8ZUbqT49/s200/Image16.png" width="180" /></a> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj151AhZhyhgC0s4iM7MsiP1qKsaP4ITYNaW6069kKR1wx7AlelGW-Ulx6yljKuluJZn-J6AjmmLuAZUQ4YXeq2faQlRM4fdICMGbQffvux1c9Sz8RPL76DSWPsm2lwtW78iEtdrjpicHwe/s1600/Image6.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="435" data-original-width="439" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj151AhZhyhgC0s4iM7MsiP1qKsaP4ITYNaW6069kKR1wx7AlelGW-Ulx6yljKuluJZn-J6AjmmLuAZUQ4YXeq2faQlRM4fdICMGbQffvux1c9Sz8RPL76DSWPsm2lwtW78iEtdrjpicHwe/s200/Image6.png" width="200" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
There are many variants of this vocabulary, but the basic elements of pointers and circles is sufficient to create a wide variety of compasses, as this sample from Oleg's generator shows:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQyvfFuA6Qqymi8U_OZFIJMiSVYlm8cAbQiCF69QMp6GiU24cjl2dkg6sUfbMa3ujJCUsfhtyltCDLpipZlZW9OvSMqy4gl4xMIZ5opYIHYNfujD1RJOhwhueYGCNv6U5mPcLLNyHbHemi/s1600/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="135" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQyvfFuA6Qqymi8U_OZFIJMiSVYlm8cAbQiCF69QMp6GiU24cjl2dkg6sUfbMa3ujJCUsfhtyltCDLpipZlZW9OvSMqy4gl4xMIZ5opYIHYNfujD1RJOhwhueYGCNv6U5mPcLLNyHbHemi/s1600/Image1.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
These are all stylistically quite different, but all made up from the same base elements of pointers and circles.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
For this posting I'm going to share the code to set up a basic web page with a button on it to run some Javascript code. I gave some thought to the different ways I might share code. There are some online playgrounds that let you mix code and text in interesting ways, and I considered using one of these but I thought that might create some limitations in how I could structure the code. I also thought that might make it difficult for people to incorporate the code into their own projects. So I decided to do a more traditional approach and provide code that you can download and run on your own computer. My plan is to do a separate code release along with every posting in the series, so no matter when you're reading through this you should be able to see the code associated with this posting. At the suggestion of the inimitable <a href="https://azgaar.github.io/Fantasy-Map-Generator/" target="_blank">Azgaar</a>, I will also use Netlify to build a website from each code release, so you can visit this code on the web at <a href="https://dragonsabound-part1.netlify.app/test.html">https://dragonsabound-part1.netlify.app/test.html</a>. (You'll be able to run the code and examine it in the browser, but you obviously won't be able to modify it yourself.) So this will hopefully provide the best of both worlds.</div><div><div><br /></div><div>After consulting with some (presumably more experienced) voices on my <a href="https://twitter.com/AboundDragons" target="_blank">Twitter feed</a>, I've decided to keep the code in a GitHub repository called <a href="https://github.com/srt19170/Procedural-Map-Compasses" target="_blank">Procedural Map Compasses</a>. That repository will have a branch for each part in this series, so the release for this posting can be found in the <a href="https://github.com/srt19170/Procedural-Map-Compasses/blob/Part-1/README.md" target="_blank">“Part 1" branch</a>. Each release should be self-contained and include all the code necessary to recreate what I've done in the blog post. I'll use this posting to establish a basic framework for loading the code into the browser and running it. </div><div><br /></div><div>Note that I develop on Windows and do no testing on MacOS or Linux! I've made a small attempt to accommodate those users by including MacOS and Linux versions of the Mongoose web server (see below) but I'd welcome contributions from Mac and Linux savvy folks to make the repository better for those users. Contact me in one of the usual ways if you can help!<br />
<br />
To start with, you should download the code repository by <a href="https://www.howtogeek.com/451360/how-to-clone-a-github-repository/" target="_blank">cloning the Git repository</a> at “https://github.com/srt19170/Procedural-Map-Compasses.git" and selecting the “Part 1" branch. (Cloning a repository means making a new copy of the repository, usually on your own computer. The link above contains instructions if you don't know how to do this.) That should get you a folder that looks like approximately like this:<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7yN3Kejldc0hhe6WwbrVTzo3qXtpON9LVfsvemon3DiXcrSr8YlA1uYtFpzm5vq-lTnnuI4Ag2WgeOi_9UlwDCkuOjaxVddOxLuy4dz0aNXzNoWA1iC8uTdD7gMQ03pvpf6B52wf4ZAu3/s481/Image14.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="277" data-original-width="481" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7yN3Kejldc0hhe6WwbrVTzo3qXtpON9LVfsvemon3DiXcrSr8YlA1uYtFpzm5vq-lTnnuI4Ag2WgeOi_9UlwDCkuOjaxVddOxLuy4dz0aNXzNoWA1iC8uTdD7gMQ03pvpf6B52wf4ZAu3/s16000/Image14.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
The main element in the framework is the HTML file called “test.html". In a web server, this will load the Javascript code and create a simple web page that will provide a button for executing the code. To make things simple, the Javascript code for generating compasses will live in the same folder as the HTML file. Javascript libraries (code I did not write myself) will live in a sub-folder called Libraries (although there are no libraries in the code as of yet).<br />
<br />
Also in the main folder I have included a simple Web server. I use <a href="https://cesanta.com/" target="_blank">Mongoose</a>, and since that's a single file that doesn't require any installation, I will include it in the project code so that you don't have to download it separately. (I've included versions of Mongoose for Windows, MacOS and Linux, but as noted above, I've only tested these instructions on Windows. For Linux, you can also see <a href="https://gist.github.com/willurd/5720255" target="_blank">this page</a> for some alternate ways to run a web server. Remember that you need to run it in the directory with test.html!) Double-clicking on the Mongoose executable should open up a page in your Web browser that shows the current directory:<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgV_BIlRnR9CBaD8l9lI9NFIQDMS3DMTuU0YoaLVVhg2f_ahIUrryQkyJepNZRXNF-BS7Po1LoKW9yjMlKfywF6-CIr_AI0QHGaaSEpGwIp8aVzsKVPdVZZ80x_fVGHzUan0-vdM7FCVwbG/s510/Image13.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="378" data-original-width="510" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgV_BIlRnR9CBaD8l9lI9NFIQDMS3DMTuU0YoaLVVhg2f_ahIUrryQkyJepNZRXNF-BS7Po1LoKW9yjMlKfywF6-CIr_AI0QHGaaSEpGwIp8aVzsKVPdVZZ80x_fVGHzUan0-vdM7FCVwbG/s16000/Image13.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
In some cases I've had trouble with the URL that Mongoose tries to open. If you have any trouble, try using “http://127.0.0.1:8080." If that doesn't work, you're on your own!<br />
<br />
At this point, you can click on test.html to open that page, which should display a blank page with a Test button in the upper left:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXiKv9SQoGBkmwahfUgY_HEXNULRdZAFJ6IqG60Mqzj4-CpvOFkyryVx0UmNK-Xr0WqGzj8Gnkgp5QD1DwLDGTBuW4QTDCOkYaAnBiXIqu1qucr3LJDvX2ZDmEEN7OM4uatCgRPFBxKmAF/s1600/Image1.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="445" data-original-width="576" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXiKv9SQoGBkmwahfUgY_HEXNULRdZAFJ6IqG60Mqzj4-CpvOFkyryVx0UmNK-Xr0WqGzj8Gnkgp5QD1DwLDGTBuW4QTDCOkYaAnBiXIqu1qucr3LJDvX2ZDmEEN7OM4uatCgRPFBxKmAF/s1600/Image1.jpg" /></a></div>
Let's take a look at what is inside the test.html page:<br /><pre style="background: rgb(255, 255, 255);"><span style="color: #a65700;"><</span><span style="color: maroon; font-weight: bold;">html</span><span style="color: #274796;"> </span><span style="color: #074726;">lang</span><span style="color: #808030;">=</span><span style="color: #0000e6;">"en"</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"><</span><span style="color: maroon; font-weight: bold;">body</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"><</span><span style="color: maroon; font-weight: bold;">div</span><span style="color: #274796;"> </span><span style="color: #074726;">id</span><span style="color: #808030;">=</span><span style="color: #0000e6;">"all"</span><span style="color: #274796;"> </span><span style="color: #074726;">style</span><span style="color: #808030;">=</span><span style="color: #0000e6;">"</span><span style="color: #bb7977; font-weight: bold;">display</span><span style="color: #808030;">:</span><span style="color: #274796;"> flex</span><span style="color: purple;">;</span><span style="color: #274796;"> </span><span style="color: #bb7977; font-weight: bold;">flex-direction</span><span style="color: #808030;">:</span><span style="color: #274796;"> </span><span style="color: #074726;">row</span><span style="color: purple;">;</span><span style="color: #0000e6;">"</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"><</span><span style="color: maroon; font-weight: bold;">div</span><span style="color: #274796;"> </span><span style="color: #074726;">id</span><span style="color: #808030;">=</span><span style="color: #0000e6;">"controls"</span><span style="color: #274796;"></span>
<span style="color: #274796;"> </span><span style="color: #074726;">style</span><span style="color: #808030;">=</span><span style="color: #0000e6;">"</span><span style="color: #bb7977; font-weight: bold;">display</span><span style="color: #808030;">:</span><span style="color: #274796;"> flex</span><span style="color: purple;">;</span><span style="color: #274796;"> </span><span style="color: #bb7977; font-weight: bold;">flex-direction</span><span style="color: #808030;">:</span><span style="color: #274796;"> column</span><span style="color: purple;">;</span><span style="color: #274796;"> </span><span style="color: #bb7977; font-weight: bold;">flex</span><span style="color: #808030;">:</span><span style="color: #274796;"> </span><span style="color: #008c00;">0</span><span style="color: #274796;"> </span><span style="color: #008c00;">0</span><span style="color: #274796;"> </span><span style="color: #008c00;">30</span><span style="color: #006600;">px</span><span style="color: #0000e6;">"</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"><</span><span style="color: maroon; font-weight: bold;">button</span><span style="color: #274796;"> </span><span style="color: #074726;">id</span><span style="color: #808030;">=</span><span style="color: #0000e6;">"testButton"</span><span style="color: #a65700;">></span>Test<span style="color: #a65700;"></</span><span style="color: maroon; font-weight: bold;">button</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"></</span><span style="color: maroon; font-weight: bold;">div</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"><</span><span style="color: maroon; font-weight: bold;">div</span><span style="color: #274796;"> </span><span style="color: #074726;">id</span><span style="color: #808030;">=</span><span style="color: #0000e6;">"map"</span><span style="color: #274796;"></span>
<span style="color: #274796;"> </span><span style="color: #074726;">style</span><span style="color: #808030;">=</span><span style="color: #0000e6;">"</span><span style="color: #bb7977; font-weight: bold;">margin-left</span><span style="color: #808030;">:</span><span style="color: #008c00;">10</span><span style="color: #006600;">px</span><span style="color: purple;">;</span><span style="color: #0000e6;">"</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"><</span><span style="color: maroon; font-weight: bold;">svg</span> <span style="color: #074726;">id</span><span style="color: #808030;">=</span><span style="color: maroon;">"</span><span style="color: #0000e6;">svg</span><span style="color: maroon;">"</span> <span style="color: #074726;">width</span><span style="color: #808030;">=</span><span style="color: maroon;">"</span><span style="color: #008c00;">500</span><span style="color: maroon;">"</span> <span style="color: #074726;">height</span><span style="color: #808030;">=</span><span style="color: maroon;">"</span><span style="color: #008c00;">500</span><span style="color: maroon;">"</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"></</span><span style="color: maroon; font-weight: bold;">svg</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"></</span><span style="color: maroon; font-weight: bold;">div</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"></</span><span style="color: maroon; font-weight: bold;">div</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"><</span><span style="color: maroon; font-weight: bold;">script</span><span style="color: #274796;"> </span><span style="color: #074726;">type</span><span style="color: #808030;">=</span><span style="color: #0000e6;">"module"</span><span style="color: #a65700;">></span>
<span style="color: maroon; font-weight: bold;">import</span> Compass from <span style="color: maroon;">'</span><span style="color: #0000e6;">./compass.js</span><span style="color: maroon;">'</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> testButton <span style="color: #808030;">=</span> document<span style="color: #808030;">.</span>getElementById<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">testButton</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">var</span> svg <span style="color: #808030;">=</span> document<span style="color: #808030;">.</span>getElementById<span style="color: #808030;">(</span><span style="color: maroon;">"</span><span style="color: #0000e6;">svg</span><span style="color: maroon;">"</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
testButton<span style="color: #808030;">.</span>onclick <span style="color: #808030;">=</span> <span style="color: maroon; font-weight: bold;">function</span> <span style="color: #808030;">(</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
Compass<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">test</span><span style="color: #808030;">(</span>svg<span style="color: #808030;">)</span><span style="color: purple;">;</span> <span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: #a65700;"></</span><span style="color: maroon; font-weight: bold;">script</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"></</span><span style="color: maroon; font-weight: bold;">body</span><span style="color: #a65700;">></span>
<span style="color: #a65700;"></</span><span style="color: maroon; font-weight: bold;">html</span><span style="color: #a65700;">></span></pre>
This is a stripped down vanilla web page. First it sets up some <div>s for controls and the map (really just a compass, but this is copied from <b style="font-variant-caps: small-caps;">Dragons Abound</b>). The controls div contains a simple button labeled “Test." The map div contains an SVG element that will actually display the compass.<br />
<br />
It also includes a <script> that does a couple of things. First the script loads the Compass module from the same folder as the web page. (Note that <b style="font-variant-caps: small-caps;">Dragons Abound</b> uses <a href="https://hacks.mozilla.org/2015/08/es6-in-depth-modules/" target="_blank">ES6 module syntax</a>; this should work in all modern browsers.) Second, the script attaches the “test" function from the Compass module to the onclick action of Test button. (This means that when you click the Test button, the “test" function will get executed. And eventually that function will draw a compass into the SVG element so that it will show up on the page.) The SVG element gets passed into the test function so that it will know where to draw the compass.</div><div>
<br />
Now let's take a look inside the test.js file:<br /><pre style="background: rgb(255, 255, 255);"><span style="color: dimgrey;">//</span>
<span style="color: dimgrey;">// Top level file for Map Compass Procedural Generation</span>
<span style="color: dimgrey;">//</span>
<span style="color: maroon; font-weight: bold;">function</span> <span style="color: maroon; font-weight: bold;">test</span><span style="color: #808030;">(</span><span style="color: #808030;">)</span> <span style="color: purple;">{</span>
console<span style="color: #808030;">.</span><span style="color: maroon; font-weight: bold;">log</span><span style="color: #808030;">(</span><span style="color: maroon;">'</span><span style="color: #0000e6;">Well, at least we got this far.</span><span style="color: maroon;">'</span><span style="color: #808030;">)</span><span style="color: purple;">;</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span>
<span style="color: maroon; font-weight: bold;">export</span> <span style="color: maroon; font-weight: bold;">default</span> <span style="color: purple;">{</span>
<span style="color: maroon; font-weight: bold;">test</span><span style="color: purple;">:</span> <span style="color: maroon; font-weight: bold;">test</span>
<span style="color: purple;">}</span><span style="color: purple;">;</span></pre>
There's not much here yet. The test() function is defined, but all it does is print a message to the console. Then test function is exported so that it can be used outside of the module.<br />
<br />
To actually see the code work, you will have to open the browser console. To see the console in Chrome, you need to open the Developer Tools with CTRL + SHIFT + I or F12 and then hit the Escape key if the console is not showing. On Firefox you can open the console from the Web Developer sub-menu or by hitting CTRL + SHIFT + J. (Just different enough to be confusing!) On Chrome you'll see something like this on the left side of your browser window:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKFBYyPa8u6jKkmTQde7iEVlYJip60oQHehrMVRYcoBamGXV12dA01YZRabV1IQ6OAFykIxrn3IgSiiHq234Y37taTQqNZxNCh-6LFEXLezUI4lrabGOQOzqdBCwO-jtFE2ZeqhKud4HsG/s1600/Image1.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="831" data-original-width="520" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKFBYyPa8u6jKkmTQde7iEVlYJip60oQHehrMVRYcoBamGXV12dA01YZRabV1IQ6OAFykIxrn3IgSiiHq234Y37taTQqNZxNCh-6LFEXLezUI4lrabGOQOzqdBCwO-jtFE2ZeqhKud4HsG/s1600/Image1.jpg" /></a></div>
The console is the part at the bottom showing the two error messages. Your messages and layout might be different. There are many tutorials on the Web to help you understand and use the Web Developer tools in Chrome and Firefox, so please refer to those if you have further questions.<br />
<br />
Meanwhile, try clicking on the Test button. The console should show the test message:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit1_6xZoPG-aPKZTJjnK5UyJ8NBhaTIrGSLFUK16YqotYNgVT7X2lMUvgma6mr0NO2aEW8E0I25N-HB5aU74nctXR3jx4UdAaMby10ADEa7ltARXFM99ftxzEBth_rfmAWOjz1XqjrodEd/s1600/Image1.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="215" data-original-width="449" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit1_6xZoPG-aPKZTJjnK5UyJ8NBhaTIrGSLFUK16YqotYNgVT7X2lMUvgma6mr0NO2aEW8E0I25N-HB5aU74nctXR3jx4UdAaMby10ADEa7ltARXFM99ftxzEBth_rfmAWOjz1XqjrodEd/s1600/Image1.jpg" /></a></div>
The “onclick()" function I attached to the test button in the HTML page is calling the test() function from the compass.js file. And that function is using console.log() to print a message to the console. The console is very handy for these sorts of debug messages and testing. </div><div><br /></div><div>In addition to the code files, the compass examples I used above (and others) can be seen in a folder called “Examples" in the repository. I'd be happy to expand the collection, so please submit any examples you think should be added. I'm not exactly sure how to best do this, but please submit a change request to that branch with your contribution and we'll see how that works. Please try to submit compasses that are different from the existing examples and demonstrate some new variant of compass style. Include your explanation of the value of the new compass in your submission.</div><div>
<br />And as mentioned above, I'll also end each posting with “Suggestion to Explore" with some ideas on how you can expand or play with the ideas from the posting. (You'll see that below.) Next time I'll start on drawing a compass from a description.</div><div><br /></div><div><div><h4>Suggestions to Explore</h4></div><div><ul style="text-align: left;"><li>Test.html puts a 500x500 SVG element on the web page. How would you put a border around that element to make it easier to see?<br /><br /></li><li>The test function prints a message to the console log. That requires having the console open. It would nice to be able to put a message on the web page. Modify the code so that test() returns a string and display that on the web page below the Test button.</li></ul></div></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com7tag:blogger.com,1999:blog-3367557180796013182.post-86088221960221059642021-10-20T11:16:00.000-04:002021-10-20T11:16:43.487-04:00Creating a Pencil Effect in SVG (Part 2)<p>Quite some time ago, I wrote a post about <a href="https://heredragonsabound.blogspot.com/2020/02/creating-pencil-effect-in-svg.html" target="_blank">creating a pencil effect in SVG</a>. It's challenging to do a pencil effect in SVG because SVG is fundamentally a vector graphics format, and pencils are fundamentally not. I ended up applying an SVG filter that added some texture and other effects to create a line that looks at least somewhat like a pencil:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihSX-Tk-HeAwR0GCO0_gYRrId4HN-TjDWrDYmg2YlJ_KgzqNcLZ0mDUqhm5xqslMZHYSRCqezT2_MiVR6UoiYnaV8UUN18QQ3BtswwlV2f4hpDl-ZlnmWXnnUEQL_tUe3vs26imhww5-QM/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="315" data-original-width="512" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihSX-Tk-HeAwR0GCO0_gYRrId4HN-TjDWrDYmg2YlJ_KgzqNcLZ0mDUqhm5xqslMZHYSRCqezT2_MiVR6UoiYnaV8UUN18QQ3BtswwlV2f4hpDl-ZlnmWXnnUEQL_tUe3vs26imhww5-QM/s16000/image.png" /></a></div><p>At the time, I left it at that and skipped over a couple of other characteristics of pencil-drawn lines that I didn't have a good way to replicate. One of those characteristics is that pencil doesn't completely cover whatever is underneath it. So when two pencil lines overlap, they build up to a darker color. You can see an example in the drawing below, where a single pencil makes a spectrum of shades, in part by building up graphite in areas where lines are drawn repeatedly:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiMZMdQITH7KJn4NGdETUxkpzRc0Y9avxYZSbMHFUrXqwg4Fe68bLDol6AqaVWsm8gBMLgb2iZq7iRtCVxshXGRRsSfoUtSm9F38VV_M-jweKSI6ly-ESzgQ4g4jWftVDFaUg2bwVE-RGT/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="285" data-original-width="421" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiMZMdQITH7KJn4NGdETUxkpzRc0Y9avxYZSbMHFUrXqwg4Fe68bLDol6AqaVWsm8gBMLgb2iZq7iRtCVxshXGRRsSfoUtSm9F38VV_M-jweKSI6ly-ESzgQ4g4jWftVDFaUg2bwVE-RGT/s16000/image.png" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: left;">Admittedly, artists control darkness by pressure and density of line as well, and the color of the graphite limits how dark you can go. But in general if we want to have a good “hand-drawn pencil" look, we'd like to get some darker textures where lines overlap.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">In SVG, when one line crosses another line, the top line complete covers the bottom line. So when you have two lines of the same color that overlap, they just seamlessly blend into each other. Here's an example of a map where I've filled in a forest area with a bunch of scribbled lines in a pencil color:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6m729qaqPMMOhq2Zmc2_LKAMBKv6JXORvuSEcFJ29EquNwT959jSYBUdgijze08obDiUrfNz3v8_JHqePwjoP1NQC2yeLoy7VMV7807Kl-s1tx3PDSr5_Djs3O2HrtSGIKP48FN8YrXzQ/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="326" data-original-width="537" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6m729qaqPMMOhq2Zmc2_LKAMBKv6JXORvuSEcFJ29EquNwT959jSYBUdgijze08obDiUrfNz3v8_JHqePwjoP1NQC2yeLoy7VMV7807Kl-s1tx3PDSr5_Djs3O2HrtSGIKP48FN8YrXzQ/s16000/image.png" /></a></div></div><div class="separator" style="clear: both; text-align: left;">It has a little bit of texture thanks to the pencil effect, but all the lines just blend into one flat area of color. That's pretty unsatisfactory. So how do we get the colors to “build up" where lines cross?</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">You might think (I certainly did) that opacity is the answer. If we set the opacity of our lines to (say) 50%, then 50% of the underlying color will show through, and that will let us build up a darker shade where lines cross. But unfortunately, it doesn't work that way. If the colors and opacities are the same, then the overlap area looks just like the non-overlapped areas. No matter how many layers you build up, it always looks the same.</div><p></p><p>There are other work-arounds that I might consider, but if you have any experience with computer graphics you might have heard of <a href="https://en.wikipedia.org/wiki/Blend_modes" target="_blank">blend modes</a>. Blend modes control how two colors interact when they overlap. The default is alpha compositing, which is what SVG does where the top color completely obscures the bottom color. But there's another mode called multiply that blends two colors when they overlap in a way that would be useful for pencil strokes.</p><p>The bad news is that SVG doesn't support multiply blend mode except in filters. There's no way to tell regular SVG elements to multiply with colors below. (Except perhaps by abusing filters in horrible ways that I don't want to contemplate.) The good news is that <a href="https://en.wikipedia.org/wiki/CSS" target="_blank">CSS</a> does support a multiply blend mode!</p><p>CSS is a styling mechanism for browser pages that provides a flexible way to control the appearance of page elements. CSS was invented around the same time as SVG, but unlike SVG has continued to grow and evolve and has added capabilities like blend modes. In modern browsers you can apply CSS styles even to individual SVG elements, which will enable us to <a href="https://stackoverflow.com/questions/11448556/how-to-use-blend-mode-in-svg-for-vector-shapes" target="_blank">borrow the multiply blend mode from CSS and use it in SVG</a>. </p><p>Here's what the flat fill example from above looks like when I turn on CSS multiply blend mode on all the individual pencil strokes:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-zcp5UpIUQCZ8ocovpKDJJk0Gt6PLBqaSFJTHFU_-dnpOLMob2GqKS-9QiGAOHs8FjKFK1d22R9b8LpzkUYTcbMdUtWc_k5t6wLeXqFFKhyX2rTyl5GJKHDOLaXh3lXTPfW7HhwjgX9AQ/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="337" data-original-width="597" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-zcp5UpIUQCZ8ocovpKDJJk0Gt6PLBqaSFJTHFU_-dnpOLMob2GqKS-9QiGAOHs8FjKFK1d22R9b8LpzkUYTcbMdUtWc_k5t6wLeXqFFKhyX2rTyl5GJKHDOLaXh3lXTPfW7HhwjgX9AQ/s16000/image.png" /></a></div>Needs some tuning, but you can see that the color now gets darker where strokes overlap.<div><br /></div><div>(There are some reasons not to mix CSS and SVG, the primary one being that the CSS effects will be lost when you isolate the SVG -- say to import it into Inkscape for some manual editing. But in this case, there's no real work-around that would allow me to create this effect in “pure" SVG.)<br /><p></p><p>There are a number of improvements I can make to the first effort above. One is to break up the long fill strokes into shorter segments. An amateur human artist likely wouldn't draw single lines all the way across the area. He'd fill in using shorter strokes and work in patches across the area. Splitting up the fill area into realistic patches is a little too challenging, but it isn't too hard to break up the lines going across the fill area into smaller segments:<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5EcB2P67OP6HZT4HSArjFGks3QI5sYgdIMi5IIWOtlJ7DR11tLarTdLieiPcaf-E2mfUQ6xlpQ5TYwdK9CTEN0xMLSzsNJYMeUbshf5Rn49umoPpNsrh7cR5kN6g2KvjDMu8Tvwz5LKjk/" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img alt="" data-original-height="377" data-original-width="743" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5EcB2P67OP6HZT4HSArjFGks3QI5sYgdIMi5IIWOtlJ7DR11tLarTdLieiPcaf-E2mfUQ6xlpQ5TYwdK9CTEN0xMLSzsNJYMeUbshf5Rn49umoPpNsrh7cR5kN6g2KvjDMu8Tvwz5LKjk/s16000/image.png" /></a></p><p>I've circled an area where you can (faintly) see where long lines have been broken up into consecutive line segments. Because both ends of each segment have rounded ends, and one segment ends on the start point of the next segment, you get a sort of circle where they overlap. That's a little too regular to be pleasing, so let I'll make the start and end points a bit more random:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCUeNYHsnT2w1YF0UhhmQupPkZZ64VLo0XhJwGqjbOFlHdflD3284I9b7DCJ6zCOg99kCf6H-DJsr9fMBvKtEg9fVP7A6-EeC8NDI_8ZKuZ6hLp0M3EgN-yOUxkLJVK5rEGrrr90f7vXR3/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="349" data-original-width="553" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCUeNYHsnT2w1YF0UhhmQupPkZZ64VLo0XhJwGqjbOFlHdflD3284I9b7DCJ6zCOg99kCf6H-DJsr9fMBvKtEg9fVP7A6-EeC8NDI_8ZKuZ6hLp0M3EgN-yOUxkLJVK5rEGrrr90f7vXR3/s16000/image.png" /></a></div>This goes a long way towards making the strokes more evident. In the same area as above, you can clearly see a stroke ending and an overlap just above. This example is a bit too obvious for my tastes, so I'll tone it down and adjust it to be a little more subtle:<p></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3J89x3daO2VFLSI6FAjbfxujrWU32QrvZ5Cp_LjhQgoJi9uaJ_LNN6se8KHv-MrCCnYoHMXeWh0NkiU23UuKi86PV_liQsR2s8v4Uc9AlTdcmDbbO-33RScZRtqhWFpBLJZPcbaRSf96-/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="291" data-original-width="509" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3J89x3daO2VFLSI6FAjbfxujrWU32QrvZ5Cp_LjhQgoJi9uaJ_LNN6se8KHv-MrCCnYoHMXeWh0NkiU23UuKi86PV_liQsR2s8v4Uc9AlTdcmDbbO-33RScZRtqhWFpBLJZPcbaRSf96-/s16000/image.png" /></a></div>Another aspect of hand-drawn pencil strokes that I didn't include the first time around is gradient. Pencil strokes are often darker at the beginning, or where they change direction. This is partly because humans aren't very precise and don't always hit exactly the right shade when they make a pencil stroke. If you start too light you can just go over it again, but if you start too dark all you can do is ease up quickly, leaving a darker start to the line. And it's partly because physiologically it's easier to trail off a line than to trail in a line. At any rate, you often see strokes like on the right side of this image:<div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLpZUCuu_xT3dVhO8lo1J3WkyMdEjre83Ql_S9Nm20Cr8C9RgJ4HxpDPOsGpPQbeTGbYucXZPq1gRFQSDFsfEbqh32z8e9BLtCToR65fFZsuMo2QNsrgmy3lVz1fk9EqbiU8beG_gaj5TG/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="171" data-original-width="570" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLpZUCuu_xT3dVhO8lo1J3WkyMdEjre83Ql_S9Nm20Cr8C9RgJ4HxpDPOsGpPQbeTGbYucXZPq1gRFQSDFsfEbqh32z8e9BLtCToR65fFZsuMo2QNsrgmy3lVz1fk9EqbiU8beG_gaj5TG/s16000/image.png" /></a></div><div class="separator" style="clear: both; text-align: left;">where some strokes start dark and end lighter. Compared to the pencil strokes on the left side, these seem more realistic. (Although somewhat exaggerated here.) </div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">The bad news is that, like blend modes, SVG doesn't implement gradient colors on lines. The good news is that I already tackled this problem back when I was <a href="https://heredragonsabound.blogspot.com/2019/01/various-miscellany-part-4.html" target="_blank">making river borders work better</a>. So it's just a matter of reusing that code to occasionally mix in a stroke that starts dark and ends lighter.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">But in the process of doing that, I discovered some problems in the code that fills an area with lines. It was drawing some lines in one direction and some in the other. And the code itself was complex enough that I didn't really understand how it worked anymore. So I stopped and reimplemented the code to “sweep" a polygon in a more straightforward way. There are some efficient algorithms for doing this, but I was content with a simpler implementation based on <a href="http://alienryderflex.com/polygon_hatchline_fill/" target="_blank">the description here</a>. (The basic idea of drawing lines across a polygon at an arbitrary angle by first rotating the polygon and then drawing vertical lines is very clever!)</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">This change means re-tuning the various parameters, but eventually I have this:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCTX5lsBD-raint0uQIzHxFLHy2-Z2UQXUbF3SiDkyWUdCS30Ou-XKYtQgfekCcXotCtzjxH99PRY0SxOZx1_SRKc1vBMtyRh6RhrzxY6flB8Xq2xuBYifddtD8EeTYd751gXYI543D-6Z/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="377" data-original-width="586" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCTX5lsBD-raint0uQIzHxFLHy2-Z2UQXUbF3SiDkyWUdCS30Ou-XKYtQgfekCcXotCtzjxH99PRY0SxOZx1_SRKc1vBMtyRh6RhrzxY6flB8Xq2xuBYifddtD8EeTYd751gXYI543D-6Z/s16000/image.png" /></a></div><div class="separator" style="clear: both; text-align: left;">Which you can see now contains the occasional line that starts dark and gets lighter. I've kept this fairly infrequent. In this example I've used a fairly wide “pencil" and kept a fair amount of erratic placement. I think this looks good, but you might prefer an artist with a better “hand" who is closer to that flat machine fill ideal:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh12R6vsW372abHHGZbydDF4oc17j6rVPtgQOMoQwVzNavdbTbyLZJuVNu27hr3eya4rtIHlQEtgZ8uFXOGfv5sRye8w7_lC7oYPVn3muMEe4jLo1V54iTioD7F3PISCYoGTD09V1JOqTqi/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="370" data-original-width="638" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh12R6vsW372abHHGZbydDF4oc17j6rVPtgQOMoQwVzNavdbTbyLZJuVNu27hr3eya4rtIHlQEtgZ8uFXOGfv5sRye8w7_lC7oYPVn3muMEe4jLo1V54iTioD7F3PISCYoGTD09V1JOqTqi/s16000/image.png" /></a></div></div><div class="separator" style="clear: both; text-align: left;">Or perhaps a narrow pencil:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkOfXOv6N8gLWa9gUa1vDtqiS7xfOCGd_39l-Lo8UbyGl-slbwGhSddS2O_4IVhHzbSHkSYW0HGJdmK3zz6rS0LZ0l3jLYgX3lupEf03uKdT2GovN7fkX6fwf7zAmuFjUUjSAaGxg1LpN_/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="368" data-original-width="610" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkOfXOv6N8gLWa9gUa1vDtqiS7xfOCGd_39l-Lo8UbyGl-slbwGhSddS2O_4IVhHzbSHkSYW0HGJdmK3zz6rS0LZ0l3jLYgX3lupEf03uKdT2GovN7fkX6fwf7zAmuFjUUjSAaGxg1LpN_/s16000/image.png" /></a></div><div class="separator" style="clear: both; text-align: left;">I prefer the first version, but all the styles seem pretty realistic to my eye. Here's a look at the entire map with these changes in place (click through for a hopefully full-sized image):</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfk8od3Qs7tF52HTqh8eBO5DrDSPgG3gXuVTvR-FVSnwYbdSRC-EQ-O5iEzoz-n1d8ZCiyRh19Anb2mmxGHRluF_RFodZJwkw3RDqJZoP4It7hM6lr5kGd45YddZdR-HMZ5PEYo5qTQ-UI/s1625/map+%25289%2529.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1625" data-original-width="1625" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfk8od3Qs7tF52HTqh8eBO5DrDSPgG3gXuVTvR-FVSnwYbdSRC-EQ-O5iEzoz-n1d8ZCiyRh19Anb2mmxGHRluF_RFodZJwkw3RDqJZoP4It7hM6lr5kGd45YddZdR-HMZ5PEYo5qTQ-UI/w640-h640/map+%25289%2529.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">One area that could still be improved is the line direction. Especially when seen on a full map, the very consistent line direction doesn't look realistic. I've done some simple experiments to try to address this and haven't been entirely happy with the results, but I'll continue to think about it. Maybe that will be in (Part 3) in a year or so!</div><div class="separator" style="clear: both; text-align: left;"><br /></div></div><div><p></p></div></div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com5tag:blogger.com,1999:blog-3367557180796013182.post-51458256645207161182021-10-06T12:31:00.001-04:002021-10-06T12:31:26.840-04:00Haunted Forests<p><i>(After a long break to pursue other interests, I've returned to Dragons Abound with some new material. I have at least a few months of postings lined up and we'll see where it goes after that. Special thanks to the folks who reached out to ask if I was okay! I was fine, just off doing other things, but the care you showed meant a lot to me.)</i></p><p>The forests I implemented for “Knurden" style maps (<a href="https://heredragonsabound.blogspot.com/2021/02/knurden-style-forests-part-5.html" target="_blank">here</a>) are essentially a background color filled with scattered trees, like this:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxRIXU1nyeR_JXVFerIgvjZTsFIBuywsGK6dCHSRLy1C0NuCHsnIK9bOQ0rEVwJ6VMS02qQmy7vvlKdzjpkHRBnE7NF6pisz4iVo7_jJCrj7nPgPh_EccCp-2DzX9DxSghqC7oYTzcdull/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="306" data-original-width="537" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxRIXU1nyeR_JXVFerIgvjZTsFIBuywsGK6dCHSRLy1C0NuCHsnIK9bOQ0rEVwJ6VMS02qQmy7vvlKdzjpkHRBnE7NF6pisz4iVo7_jJCrj7nPgPh_EccCp-2DzX9DxSghqC7oYTzcdull/s16000/image.png" /></a></div><p>Partway through implementing these forests, it occurred to me that I could create a “haunted forest" by replacing the forest background color with a blurry grey and making skeletal tree icons. I set that thought aside, but I did parameterize the top-level routine so that it could work with any kind of tree style and make something like haunted forests easy to implement. Famous last words! Let's see how right or wrong they are.</p><p>The first step is to pick a clump of forest to make haunted. I want a clump that is fairly small, like the forest north of the river on this map:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilBEnbVFiAJFz-gtU-LvMu2jGlLAav9W5BNlGalMq7Ef26E-Rmbz259-Z14e-Uc49FUIqry2TH1uFhHzq_4kW0Y-szuWgUaky9WPvn7_GWhvrB0wLTOUg7-C0ga8iBiWdHgbjkXLi36Wxb/s516/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="267" data-original-width="516" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilBEnbVFiAJFz-gtU-LvMu2jGlLAav9W5BNlGalMq7Ef26E-Rmbz259-Z14e-Uc49FUIqry2TH1uFhHzq_4kW0Y-szuWgUaky9WPvn7_GWhvrB0wLTOUg7-C0ga8iBiWdHgbjkXLi36Wxb/s16000/Image1.png" /></a></div>I use the debugger to get the size of that forest, and then just pick a forest of roughly that size to make the haunted forest. For the moment I draw it as a fir forest to check the logic. On this map I got lucky and got that same forest:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhf_PAnzu8CpLn-AMvXygONvKdYbrhwAPdSf5Ij7WMC3_fDiTFBI6WDrsy61zy73n11WWEMEHbUPOVygz0p8fQq-OiYeEr2WF5jCh4-bQAuDRqlYdyCJpI0T7GIirk6Mp4aFuplm-zut-_x/s390/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="232" data-original-width="390" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhf_PAnzu8CpLn-AMvXygONvKdYbrhwAPdSf5Ij7WMC3_fDiTFBI6WDrsy61zy73n11WWEMEHbUPOVygz0p8fQq-OiYeEr2WF5jCh4-bQAuDRqlYdyCJpI0T7GIirk6Mp4aFuplm-zut-_x/s16000/Image1.png" /></a></div>Eventually I might want to use some additional criteria to pick the forest (such as picking one away from towns) but for now I'll just take the first forest I find that is the proper size.<div><br /></div><div>The next step is to draw in the gray fog. I'll start with just filling in the forest shape with gray:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNDbuSQ2VaXOAQXzvH5pLHJNYC1464xYlZb5kBqPHByTRZOF-873Oo0NzDXDULk6wBT-mRiiaJZsNqOE2NBQcUaxMNsedZu9BEEBmR6DHmggO73YZkdHAfAMiwzZFpbaMlB-oEDPf1PVI8/s456/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="228" data-original-width="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNDbuSQ2VaXOAQXzvH5pLHJNYC1464xYlZb5kBqPHByTRZOF-873Oo0NzDXDULk6wBT-mRiiaJZsNqOE2NBQcUaxMNsedZu9BEEBmR6DHmggO73YZkdHAfAMiwzZFpbaMlB-oEDPf1PVI8/s16000/Image1.png" /></a></div>This is supposed to be fog, so I'll make the edges indistinct. Maybe it should have tendrils or wisps as well, but I can always add that later. And I'll make it somewhat translucent as well.</div><div style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh8jIKK3UDwIFX0tQWoGTYRsyieP8meLpKX_u4DXCSSTOS_XtmWGtrtJ-0m-5BvXbA7Ubq7Bd1k01N-dpLeNuUMoyQpBhzNnjG_WwpzsZ56ljOI7XJ6oH1IvcqXT9aJPS9Q6tBPN7UGvRK/s440/Image1.png"><img border="0" data-original-height="258" data-original-width="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh8jIKK3UDwIFX0tQWoGTYRsyieP8meLpKX_u4DXCSSTOS_XtmWGtrtJ-0m-5BvXbA7Ubq7Bd1k01N-dpLeNuUMoyQpBhzNnjG_WwpzsZ56ljOI7XJ6oH1IvcqXT9aJPS9Q6tBPN7UGvRK/s16000/Image1.png" /></a></div>I like the edges here, but overall this is too subtle. This might be a good place for a radial gradient, so that I can make it heavier and darker in the center and fading out to the edges. SVG only supports circular gradients, so the fade will not be consistent but might still look good. I also played around with using some different colors. I ended up with this:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji1aAyvJXFRZuMrULZsntm8JVO0L6e_d6nWpDEcaUFINdl5kCRRxdgKyRJ9_U-4Hp5_2q8DTfuYEpgMfUMsbvT2PMha1SaEwxMQXG_Y0I9lBB3xpGa8zoNUqKzB01lC3SDdKQCkIuMubfR/s600/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="273" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji1aAyvJXFRZuMrULZsntm8JVO0L6e_d6nWpDEcaUFINdl5kCRRxdgKyRJ9_U-4Hp5_2q8DTfuYEpgMfUMsbvT2PMha1SaEwxMQXG_Y0I9lBB3xpGa8zoNUqKzB01lC3SDdKQCkIuMubfR/s16000/Image1.png" /></a></div>This looks pretty nicely fog-like, although it isn't perhaps as menacing as I might like for a haunted forest.<div><br /></div><div>The next step is to add some trees to the mist. As you'll find if Google it, there are a lot of ways to procedural generate tree skeletons. A common one is “Lindenmayer systems" (or L systems) which is a family of grammars that can be used to generate tree skeletons as well as other phenomenon. The trees I'm going to generate are so small and simple that an L system seems like overkill. So I'll just roll my own and see how it goes; I can always do something more complex later if this doesn't work out.</div><div><br /></div><div>We'll start with the trunks as a smoothly tapered line:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtYYPTqnMrUIpkbaS9jN9LiYw3KSKpO3NiTjaU2egmKRsnxWFqkXqQ4yEMYxvQlqsRSax6L6bCjApfAfJK1dm0xiZTtBymHz2STxQRWSPmXQPHtzywXkR3pocr6vpYtFoJ4cYMtYs9kYqt/s287/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="241" data-original-width="287" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtYYPTqnMrUIpkbaS9jN9LiYw3KSKpO3NiTjaU2egmKRsnxWFqkXqQ4yEMYxvQlqsRSax6L6bCjApfAfJK1dm0xiZTtBymHz2STxQRWSPmXQPHtzywXkR3pocr6vpYtFoJ4cYMtYs9kYqt/s0/Image1.png" /></a></div>Of course, scary trees shouldn't grow straight like that, so let me add some jitter:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-QZEBY3svigCdBfCzsG3w0se2aSid8z251BoGfnxAWNhGPg8kWUW1lbXGB0fMdgH25Im7ZIck0E9AVZ0k3b2d9Xxheh-kh7xmB1Vcs5pqwssxCq8khyI4oN6h6_m9qgta-p5DwtY8siPy/s250/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="244" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-QZEBY3svigCdBfCzsG3w0se2aSid8z251BoGfnxAWNhGPg8kWUW1lbXGB0fMdgH25Im7ZIck0E9AVZ0k3b2d9Xxheh-kh7xmB1Vcs5pqwssxCq8khyI4oN6h6_m9qgta-p5DwtY8siPy/s0/Image1.png" /></a></div>For tree trunks, I want the jitter to go back and forth (I don't want trees shaped like a big C for example) and I want the perturbations to be pretty sharp, not smoothed out as when I create a “hand-drawn" line. So I had to create a new perturbation function that alternated the direction to perturb the line.<div><br /></div><div>While I'm at it, I also want to apply a mask that fades out the tree towards the bottom, as if it is disappearing in the fog. I can do this by applying a mask filled with a linear gradient from white to black.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRlGLHrxqa6AXbvWefP5SaTnyMjIPr4FklOpJUPUAe8zYW5XZg3XBRW-cJUHQNezef7KehsoRiwm1rYMWOzBJ1wRhcgke5E9yxp5kOci0Vv9ZLnLhyphenhyphennC38Yo026uwrA0HpNgzWk8l-36ZA/s294/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="294" data-original-width="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRlGLHrxqa6AXbvWefP5SaTnyMjIPr4FklOpJUPUAe8zYW5XZg3XBRW-cJUHQNezef7KehsoRiwm1rYMWOzBJ1wRhcgke5E9yxp5kOci0Vv9ZLnLhyphenhyphennC38Yo026uwrA0HpNgzWk8l-36ZA/s0/Image1.png" /></a></div>Note that I've adjusted the mask so that the tree doesn't completely fade away.<div><br /></div><div>Now I need to add some branches to the tree. I'll alternate sides on the tree and make the branches shorter as they get near the top. Maybe I'll need branches on the branches, but we'll see. These icons are pretty small. To make branches, I'll pick a point on the tree, draw a line straight up, and then rotate it either left or right. After playing around a bit:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQfqxBUlG3-1Yxx_Gl7dhXDIx0xT5GRsdH_4nQ5WE-skv6jV0clOWn-NQ_gU_rI0hx2M5ZTIjYGR5PefLPR5568gLnImrEHaX0vBHI3vnyW-zRSP0PIlBkgdACV5oDJLiNNYcKRhc3vtza/s298/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="298" data-original-width="294" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQfqxBUlG3-1Yxx_Gl7dhXDIx0xT5GRsdH_4nQ5WE-skv6jV0clOWn-NQ_gU_rI0hx2M5ZTIjYGR5PefLPR5568gLnImrEHaX0vBHI3vnyW-zRSP0PIlBkgdACV5oDJLiNNYcKRhc3vtza/s0/Image1.png" /></a></div>(This is at 150% size.) This looks sort of okay -- good enough to move forward with putting these on the map to see how they really look.<div><br /></div><div>To sprinkle the trees throughout the fog area, I use Poisson sampling, which I've talked about <a href="https://heredragonsabound.blogspot.com/2018/10/lord-of-rings-map-style.html" target="_blank">previously</a>. It takes a few tries to get a good sampling distance, but that gives me this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8IEjBxcYWZv4Iw9l0BPtJnzolBnbgm3KZwmGlI7_qCm-PqtNH3Or8dG07uExkUw-_1hBvzYwBco2gTAgQaatUvtrePVfvVWxU1IcxsFhaoBu_Mcvi3wx_L4tuoAHXFeFRyDJ7kUjbOt6v/s463/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="281" data-original-width="463" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8IEjBxcYWZv4Iw9l0BPtJnzolBnbgm3KZwmGlI7_qCm-PqtNH3Or8dG07uExkUw-_1hBvzYwBco2gTAgQaatUvtrePVfvVWxU1IcxsFhaoBu_Mcvi3wx_L4tuoAHXFeFRyDJ7kUjbOt6v/s16000/Image1.png" /></a></div>I'm not sure whether I like this or not. At this scale it is hard to be artistic, but these look (to my eye anyway) to blotchy and clumsy. I made some adjustments to try to keep white space between the branches and to generally thin down the lines.<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiz05Bwq1T9WOSxVSYY8LBTKGPNbZ-CPHdFI0WCNodz074BdNjIulsknGHKzfjo6h2QlSoKtshQKoKBwIuZESozEsh57sPMf0ajmELb9vwT5fPPwQP6ji9wSTp8u7y-tem-smtIPqYAqG-k/s511/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="247" data-original-width="511" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiz05Bwq1T9WOSxVSYY8LBTKGPNbZ-CPHdFI0WCNodz074BdNjIulsknGHKzfjo6h2QlSoKtshQKoKBwIuZESozEsh57sPMf0ajmELb9vwT5fPPwQP6ji9wSTp8u7y-tem-smtIPqYAqG-k/s16000/Image1.png" /></a></div>The “trees" look a bit like scraggly Christmas trees, but there's a limit to what can be done at this scale. The trees are also too regular in the fill, but that I can address; I tried a couple of different approaches and settled on this:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMQWshX7K9_S0-zN_KCuvQpS61UHQ0jxgTnqn1nLgAE18EqNgBSqglPjo4cYF7mEFPtOlbGQDrY1wP9omn6FsngcrWgdT1JGTvwRTT8pWyqiyQ4NYmi9ucAbD_HY3_Z58LUKKvp3McneDg/s600/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="339" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMQWshX7K9_S0-zN_KCuvQpS61UHQ0jxgTnqn1nLgAE18EqNgBSqglPjo4cYF7mEFPtOlbGQDrY1wP9omn6FsngcrWgdT1JGTvwRTT8pWyqiyQ4NYmi9ucAbD_HY3_Z58LUKKvp3McneDg/s16000/Image1.png" /></a></div><div>Now that I'm more-or-less happy with the look of the haunted forest, I want to put an appropriate label onto the forest. And this is where I run into a problem.</div><div><br /></div><div>As it turns out, the haunted forest above isn't really a separate forest. It's actually part of the larger forest south of the river. But maps are often drawn with <a href="https://heredragonsabound.blogspot.com/2017/04/take-me-to-rivers.html" target="_blank">the forests pulled back from the rivers</a> to show how they run, and so this forest gets split by the Flotilla River and displays as if it is two separate forests. This becomes apparent if I turn off the option to draw the forest back from the river:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwjIGJ6iR63-e01qRGhE3GxDjLoHgXll4ps3CzJstRUAxAQprFPldlXzn-J2wOn0pN7mKldKKhW8MVG2AWDBrfN-LkGtETZZ9wzbauZX8DBKH3VxIWd_5qgboBFlw9NbiDT34Uz2Al31TG/s600/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="339" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwjIGJ6iR63-e01qRGhE3GxDjLoHgXll4ps3CzJstRUAxAQprFPldlXzn-J2wOn0pN7mKldKKhW8MVG2AWDBrfN-LkGtETZZ9wzbauZX8DBKH3VxIWd_5qgboBFlw9NbiDT34Uz2Al31TG/s16000/Image1.png" /></a></div>*Poof* no more haunted forest. (The river should be under the forest, but that's a different problem.)<div><br /></div><div>This matters because naming occurs earlier in the map generation process and works on the intact forests. So there isn't any way to give the haunted forest segment its own name. The haunted forest really needs to be its own separate forest. The best way to accomplish this is to split off the haunted forest earlier in the generation process, and modify map so that it is separated from other forests even if the forests aren't pulled back from the rivers. That's not so easy to do (and my approach is not particularly efficient) but eventually I have this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBiMd9vRkIQkZsvMJBuGt2Z42RhSVB-vOkWyMk9IoIb1duzOHKTlEVvINOozy0jxhHEo39kFL5vu249S5tfaEzWHVCGIqPFOIo8WNB1NSqP_j96Xz1GI8gFTJftNA5viR5MDs61-VM4-IN/s580/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="351" data-original-width="580" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBiMd9vRkIQkZsvMJBuGt2Z42RhSVB-vOkWyMk9IoIb1duzOHKTlEVvINOozy0jxhHEo39kFL5vu249S5tfaEzWHVCGIqPFOIo8WNB1NSqP_j96Xz1GI8gFTJftNA5viR5MDs61-VM4-IN/s16000/Image1.png" /></a></div>The haunted forest is now on a different part of the map, As you can see I fixed the error so that the forests are now obscuring the rivers, but I've still pushed the forest back away from the haunted forest even though that's not on for display purposes. So now I can go back to trying to label the haunted forest.<div><div><br /></div><div>First I'll just put a placeholder name on the forest. I'm just reusing the existing forest label code, so this is straightforward.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqXhPpEYZbFg_t4prUEPFCHNUZZUp-CvvPYslNqhjZpt9leVdSsJwgs2rzCbdEsLWKgGe9nDXQ12ALK_8yzfFS4M3N4CN1BdoG1p6zMJxLYtCzrr72pR5ld3khP0QoAQXhww2YA981gxQi/s538/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="318" data-original-width="538" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqXhPpEYZbFg_t4prUEPFCHNUZZUp-CvvPYslNqhjZpt9leVdSsJwgs2rzCbdEsLWKgGe9nDXQ12ALK_8yzfFS4M3N4CN1BdoG1p6zMJxLYtCzrr72pR5ld3khP0QoAQXhww2YA981gxQi/s16000/Image1.png" /></a></div>Next I need to generate a good name for a haunted forest. I haven't done <a href="https://heredragonsabound.blogspot.com/2018/06/the-naming-of-places-part-1.html" target="_blank">place names</a> in a while, so I have to go back and re-familiarize myself with the code. I use a library called <a href="https://github.com/dhowe/RiTaJS">RiTa.js</a> which I've modified somewhat to meet my needs. Names are generated using a (mostly) context-free grammar, which I explained in detail <a href="https://heredragonsabound.blogspot.com/2018/06/the-naming-of-places-part-4-using-tool.html" target="_blank">here</a>. But I don't need to create an entirely new grammar; it should be sufficient to borrow the grammar for naming forests, use a lexicon suited for scary haunted places and throw out any name forms that aren't appropriate. I can borrow some of the lexicon from the similar Lost Coast names. With some editing, here's a list of ten random haunted forest names:<div><div><ul style="text-align: left;"><li>Milky Forest</li><li>Wailing Forest</li><li>Wasted Woods</li><li>Bloodstained Woods</li><li>The Calamity Forest</li><li>The Apparition Forest</li><li>Cruel Woods</li><li>Unearthly Aberration Forest</li><li>Sepulchral Talons Forest</li><li>Foggy Toll</li></ul></div><div><div>And for my example map:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNfhS39AjMWZ9h5QBU6MexFVlZ0sN4wKlBxNerI6UTCpMZsCOrUlSk0-Ilg_-2cLv0hq_-eE12ikxtAPoNHHVBS_yeZb2NEbPnHLlwxHigy2CZyPfRGW8ZiggJwHfeq2Q8UA9HfsYLSI5a/s524/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="361" data-original-width="524" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNfhS39AjMWZ9h5QBU6MexFVlZ0sN4wKlBxNerI6UTCpMZsCOrUlSk0-Ilg_-2cLv0hq_-eE12ikxtAPoNHHVBS_yeZb2NEbPnHLlwxHigy2CZyPfRGW8ZiggJwHfeq2Q8UA9HfsYLSI5a/s16000/Image1.png" /></a></div>The “Sunless Forest". This label style is a little difficult to read on the gray of the haunted forest, so that might need to be tweaked.</div><div><br /></div><div>The last thing I've added is some code to place the haunted forest as far away from the nearest city as possible. Although I could see some interest in having a haunted forest right outside of a town, I think on balance it probably makes more sense to have it far away.</div><div><br /></div><div>And that's about it for the haunted forest, at least for now. One thing I'm trying to do is stop development of a feature at a reasonable point and let it sit for a while. Later new ideas might occur to me, or, after having seen it on maps a few times, I might have an idea on how to improve it. So I'll let this age and possibly revisit it later. (And if anyone has any good ideas on drawing a skeletal tree better at this scale let me know!)</div></div></div><div><br /></div><div>Next time I'll return to the topic of pencil effects in SVG.</div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com4tag:blogger.com,1999:blog-3367557180796013182.post-36182272409297011682021-03-02T22:25:00.024-05:002021-03-03T20:29:18.331-05:00Knurden Style: Completed Maps (Part 7)<p>As promised last time, some completed maps. These are 3250x3250 so you will have to click through or download to view full size. (You may use the maps for non-commercial purposes, if you'd like.)</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwdBQoZDReP9n_aFtsybAHcxSNXaFp5zDr0U5Yp3Zyy8iKnR80aasmWzlU-FT2Cpzb5G199DgDRgMzj63OTDireLx7UUI9XXHmY3azyJiEPd3NrLWEHwvsthskR_lAqfKFWyI_vGaFeezr/s2048/map.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2040" data-original-width="2048" height="638" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwdBQoZDReP9n_aFtsybAHcxSNXaFp5zDr0U5Yp3Zyy8iKnR80aasmWzlU-FT2Cpzb5G199DgDRgMzj63OTDireLx7UUI9XXHmY3azyJiEPd3NrLWEHwvsthskR_lAqfKFWyI_vGaFeezr/w640-h638/map.png" width="640" /></a></div>This was my primary test map. The city names that lie across islands are difficult to read; I will probably tweak the city label style some to try to address that.<div><br /></div><div>I tried a map using the Mediterranean template that creates a central sea:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQChgX4FgxkuUOuFCUp7u5ut1lq_g7rP9pIciqY5RwLSPX0dgtob9ZWD1vjey-q8TRkR52c-m4tXKZar1r_Wb9AzcdJ08ncZIVN8uMgKgnEtY4y5RUDsjF7y0h2tOJTdHRkpstyje0mShD/s2048/map+%25281%2529.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2040" data-original-width="2048" height="638" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQChgX4FgxkuUOuFCUp7u5ut1lq_g7rP9pIciqY5RwLSPX0dgtob9ZWD1vjey-q8TRkR52c-m4tXKZar1r_Wb9AzcdJ08ncZIVN8uMgKgnEtY4y5RUDsjF7y0h2tOJTdHRkpstyje0mShD/w640-h638/map+%25281%2529.png" width="640" /></a></div><div>With this configuration the central rhumblines become a pretty striking feature. It's not the sort of thing you'd want to see on every map, but it look pretty good here. There are a couple of other nice features on this map. I like the big enclosed bay at the lower right (although “Obvioussland" is not a great name -- I'm not even sure how the name generator came up with that). The snaky river in the upper left looks very good, although for some reason it got two names.</div><div> </div><div>Here's a final map, this time a “coast" map showing one coast of a larger continent:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnzbzjakjSiG5ir8NClFUCYNCnS5KYmDxnCwaVbemiMM6b6OGQ6hx9FXXc1Zj31qMcdg99pXuv7TRtHibn89wOBdzUHC1yeVGf44v67suB_i_4g2yfJQBtdL3-8gGn-VnLAV-YiKj8Lptx/s2048/map+%25283%2529.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2040" data-original-width="2048" height="638" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnzbzjakjSiG5ir8NClFUCYNCnS5KYmDxnCwaVbemiMM6b6OGQ6hx9FXXc1Zj31qMcdg99pXuv7TRtHibn89wOBdzUHC1yeVGf44v67suB_i_4g2yfJQBtdL3-8gGn-VnLAV-YiKj8Lptx/w640-h638/map+%25283%2529.png" width="640" /></a></div>This one happened to generate a Lost Coast and I've modified the label style for that to fit in with the general style of these maps. There's also a weird coincidence on this map where one of the rhumblines comes out of the mouth of river which looks strange.<div class="separator" style="clear: both; text-align: center;"><br /></div>Here's a side-by-side comparison:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOeSWL3wZtGeNgXSfgjbIAepecdENF8aQkfMbl9Fg4xlWFnA6kJrobDCN4NaQABflYxs5a9tC11lko0sEoFTBTB-eJ-a21qtQ4C3pwvj5PW5xdiyd3gCnBQxLbThpIjPiikG6Tx91rcPSl/s599/Image1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="430" data-original-width="599" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOeSWL3wZtGeNgXSfgjbIAepecdENF8aQkfMbl9Fg4xlWFnA6kJrobDCN4NaQABflYxs5a9tC11lko0sEoFTBTB-eJ-a21qtQ4C3pwvj5PW5xdiyd3gCnBQxLbThpIjPiikG6Tx91rcPSl/s16000/Image1.png" /></a></div>You can certainly see differences in the scale and color palette, but overall, I think the finished maps capture a lot of the style of the original map. <br /><div><div><br /></div><div>So that's it for Knurden-style maps. Next time ... haunted forests.</div></div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com7tag:blogger.com,1999:blog-3367557180796013182.post-35652719898381067982021-02-14T13:45:00.000-05:002021-02-14T13:45:01.326-05:00Knurden Style: Labels & Etc (Part 6)<p>In previous postings, I implemented the major portions of Daniel Hasenbro's Knurden map style, the mountains and the forests. I also previously handled the shoreline and the basic colors. There remain a few more elements to implement.<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHmJFWIbcgprmkXvISWKDhoRsNKzjDKQ4AebjgpZioGtGuRpW49lvIWo8y0hzoTRNrBFs-L5vOq8yodUQ-6by6wVSBaVXAaoklZx2m-UIo4OyzHeRoDETNv2mmYBHVlcDmCFgA4DqUqXBU/s600/Image1.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="354" data-original-width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHmJFWIbcgprmkXvISWKDhoRsNKzjDKQ4AebjgpZioGtGuRpW49lvIWo8y0hzoTRNrBFs-L5vOq8yodUQ-6by6wVSBaVXAaoklZx2m-UIo4OyzHeRoDETNv2mmYBHVlcDmCFgA4DqUqXBU/s16000/Image1.png" /></a></p><div class="separator" style="clear: both; text-align: left;">One important element is the labeling. The excerpt above shows examples of the important label types.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">The font used for the labels is one of the <a href="https://www.dafont.com/im-fell-types.font" target="_blank">IM Fell fonts</a> (DW pica). The city labels are filled in a light reddish brown, and stroked in a dark red brown. There are two other potential elements to labels. The first is a mask that blocks out the background around the letters; these labels do not appear to have any masking. The second is a halo. This is usually a white blur that surrounds the letters and also helps separate them from the background. The Knurden city labels have a narrow, somewhat transparent white blur. Matching the style is then a matter of trying different colors, sizes, blurs and so on until I get something that looks approximately the same.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy_V3rvsSF4T2ZHYfp4DKPYKQfySLRNYDZvlNXch1Pnzq5pjjz_NijbHbStF9HHafjKqF9A4pYDBNQCLuk3ox7fIKk0nrAh_G6AWcTtBfz1ywqw3B_g5l7OOJnEKvKvKfUXezS3fonkQ-q/s342/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="285" data-original-width="342" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy_V3rvsSF4T2ZHYfp4DKPYKQfySLRNYDZvlNXch1Pnzq5pjjz_NijbHbStF9HHafjKqF9A4pYDBNQCLuk3ox7fIKk0nrAh_G6AWcTtBfz1ywqw3B_g5l7OOJnEKvKvKfUXezS3fonkQ-q/s16000/Image1.png" /></a></div>The colors are still a little bit off because fonts are treated somewhat different in SVG than in Photoshop, but it's a fairly close match.<div><br /></div><div>The labels for woods use an italic version of the IM Fell font. The letters are outlined in the same dark brown but filled with white, and there does not seem to be a mask or halo. The forest labels are also fairly small, about 60% or so the size of the town labels. But here I run into a problem.</div><div><br /></div><div>The italic version of the IM Fell font has characters that are a little too skinny at the size I need for the forest labels. In theory, that's not a problem, because you can change the thickness of fonts by using the CSS “font-weight" style. By setting the font-weight appropriately, I can get a thickness that is readable and roughly matches the Knurden map. However, I discovered that setting font-weight breaks the font stroke (outline). You can make the font fatter, or you can have an outline, but you can't have both.</div><div><br /></div><div>This is probably because font outlining is done using the generic SVG shape stroking capability. The text is just treated as an SVG shape and then the perimeter of the shape is stroked to create an outline. Font weight, on the other hand, is a CSS style feature that presumably happens later in the display pipeline. If the shape is already stroked, it can't then be made fatter.</div><div><br /></div><div>As it turns out, there is a CSS style to stroke a font as well, called “<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-text-stroke" target="_blank">-webkit-text-stroke</a>" So you might guess that it would be possible to use this in combination with font-weight to get weighted, stroked text. Unfortunately you'd be wrong, as -webkit-text-stroke doesn't seem to work on SVG text.</div><div><br /></div><div>Another possibility for creating an outline effect is to put two copies of the same text on top of each other, and make the back copy thicker using font-weight so that it shows around the edges of the top copy. Then make the top copy the fill color and the bottom copy the stroke color, and it will appear that you have stroked the outside of the text in a different color. And while this does work, it turns out that the difference in thickness between the heaviest and the lightest font weights is still pretty minimal, so that the stroke is very thin. </div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOzX6tgrmxi02U7IZojqJB4mwqAETE1nuKwqMddiRbcmbS1AM802K2_hmRLihXnTwwSmCAU945jhTNrdsVlMYNFwe5N8TTrGSjoi5THAVjBJuFKKTeonH7ZmtXKTmdrwyqu1IEbFXhQCeN/s262/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="101" data-original-width="262" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOzX6tgrmxi02U7IZojqJB4mwqAETE1nuKwqMddiRbcmbS1AM802K2_hmRLihXnTwwSmCAU945jhTNrdsVlMYNFwe5N8TTrGSjoi5THAVjBJuFKKTeonH7ZmtXKTmdrwyqu1IEbFXhQCeN/s0/Image1.png" /></a></div><div>Just a hair of black peeking out. So on a practical level, this doesn't work.</div><div><br /></div><div>Yet another possibility is to make the bottom copy bigger by changing it's size. The problem with this idea is that when the font changes size, the individual characters don't stay centered on each other. The space between characters also gets bigger, and this throws everything off.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTAOsSWnKpwxsfcirRrLFyVEpNS3JhqUVsNWP0O8F5XWUGoEscrg1Nl3jq9U0asP_DfyACVV_xd1bQHGqLqzDj_TzBMNZXaFMZHLv78nkejPrtGTtzDG2_oMe2Nhtv0imkSbKI-418N_py/s248/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="126" data-original-width="248" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTAOsSWnKpwxsfcirRrLFyVEpNS3JhqUVsNWP0O8F5XWUGoEscrg1Nl3jq9U0asP_DfyACVV_xd1bQHGqLqzDj_TzBMNZXaFMZHLv78nkejPrtGTtzDG2_oMe2Nhtv0imkSbKI-418N_py/s0/Image1.png" /></a></div>In theory I suppose you could handle every character separately and line them up correctly, but ... not going to go down that rabbit hole!<div><br /></div><div>After much experimentation, I found yet another way to get the desired effect. There's a little-known attribute for SVG text called “paint-order" which can be used to modify the order in which the fill, stroke and markers get drawn. It turns out that when the stroke is drawn first (instead of the fill), “font-weight" starts working! </div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2xpmUHBOHRXNN4zS5592xqBAu-5pwSLdJQUSHuUqdnrSz8peaNHvjK9Q9_2ETM1cO4SDI_1GYKpbvmsZSFyArvXv8SW9GW3RiYBkf13DgjQjhjC1AebauXgu1NEE0rfsYwPg2cLbwEyIB/s262/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="101" data-original-width="262" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2xpmUHBOHRXNN4zS5592xqBAu-5pwSLdJQUSHuUqdnrSz8peaNHvjK9Q9_2ETM1cO4SDI_1GYKpbvmsZSFyArvXv8SW9GW3RiYBkf13DgjQjhjC1AebauXgu1NEE0rfsYwPg2cLbwEyIB/s0/Image1.png" /></a></div>I'm not entirely sure of the reasoning here, but I'll take it!<div><br /></div><div>(I later thought of another way to achieve this effect. Draw the text first with a thick outline, and then draw the text with no outline over the top. That might work. It seems there are many ways to skin this particular cat -- I'm glad at least one of them works!)</div><div><br /></div><div>With this fix in place it's straightforward to get a reasonably close match:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEificxlPTxrYS-m5cA3d7L_3hMFcUd203ev731wc9w_6osATStatsXsx5lpbW9DWzxrTfV24ffrp7oOtE9ba75F2rExTwD6RRm3goztO4dJZakTChn_ESgWoDKR69RNQbmMhGLnBD_Mva-6/s237/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="237" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEificxlPTxrYS-m5cA3d7L_3hMFcUd203ev731wc9w_6osATStatsXsx5lpbW9DWzxrTfV24ffrp7oOtE9ba75F2rExTwD6RRm3goztO4dJZakTChn_ESgWoDKR69RNQbmMhGLnBD_Mva-6/s16000/Image1.png" /></a></div>There are only two river labels on the Knurden map, but they use an interesting technique that is used on other labels on the map as well:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho_HxwyEdWJueghmodJlWkmg_V6QWS80R3Q-sXlOIm1yCZ0W2THyjH_qD0f5lpLqYDG-OnxqR2zH9uI8i6UGkfCYTOeNBtXDRhHtwimhim_VNRU_AuZ-0eLQPdhoyPWk082dnj9Q_MmXai/s421/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="238" data-original-width="421" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho_HxwyEdWJueghmodJlWkmg_V6QWS80R3Q-sXlOIm1yCZ0W2THyjH_qD0f5lpLqYDG-OnxqR2zH9uI8i6UGkfCYTOeNBtXDRhHtwimhim_VNRU_AuZ-0eLQPdhoyPWk082dnj9Q_MmXai/s16000/Image1.png" /></a></div>For these labels, Daniel gets a kind of “negative" effect by using a transparent light color for the font and surrounding it with a dark halo. This is fairly straightforward in <b style="font-variant-caps: small-caps;">Dragons Abound</b>, although it takes a lot of tuning to get the colors close (if not exactly the same):<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgealGvhIHiK-2Ntde4w5W4vLFxMoLgEfmyviLmmM-FHwsWcc_y8englSTQ0hAgIqhy7dJkj0qdXy6Ab20Euo64RSsPNpOyzpR2Mqzh6tqqT2rlH3t71oe3OX2xuyaq3i8Xm2K5RoYG-mZj/s389/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="275" data-original-width="389" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgealGvhIHiK-2Ntde4w5W4vLFxMoLgEfmyviLmmM-FHwsWcc_y8englSTQ0hAgIqhy7dJkj0qdXy6Ab20Euo64RSsPNpOyzpR2Mqzh6tqqT2rlH3t71oe3OX2xuyaq3i8Xm2K5RoYG-mZj/s16000/Image1.png" /></a></div>One problem with this style of label is that it can be hard to read on a busy background:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkvYiQbhTi6C3opw9z_1FN3y_OL7DrZ8V6JSfLS7GlnLd7nxOdlPAekKNarJeR1KA73u2hws6f3iOwXu5hLE1h7t4kSEoeuklkbOvo_7hvxvEJea3RHQWwOPZjDWpQ8U2a14bK5ul6QHRD/s400/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="211" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkvYiQbhTi6C3opw9z_1FN3y_OL7DrZ8V6JSfLS7GlnLd7nxOdlPAekKNarJeR1KA73u2hws6f3iOwXu5hLE1h7t4kSEoeuklkbOvo_7hvxvEJea3RHQWwOPZjDWpQ8U2a14bK5ul6QHRD/s16000/Image1.png" /></a></div>So I may tweak it a bit to address that at some point.<div><br /></div><div>Ocean labels are much as river labels, adjusted for the ocean colors, so I won't go into any big detail on them. Region labels are like forest labels, but not italic and in all-caps.</div><div><br /></div><div>With labels done, I want to go back to forests and pick up an interesting little trick from the Knurden map. Although forests are done as large masses, Daniel also scatters some solitary trees around the map:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggW7IYx45fh8KMrAKNA5xQk3iR0KziD4zKk74J52zXv-vFDhOb9DKA1bb8eEe8FwLbvQNNK1sjgPWpTk33KRKdvfsokzz_J7sc30rNt74v1WCFAzlZks8LKxjCruGJgJhd8dshLBobBqw0/s400/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggW7IYx45fh8KMrAKNA5xQk3iR0KziD4zKk74J52zXv-vFDhOb9DKA1bb8eEe8FwLbvQNNK1sjgPWpTk33KRKdvfsokzz_J7sc30rNt74v1WCFAzlZks8LKxjCruGJgJhd8dshLBobBqw0/s16000/Image1.png" /></a></div>As in this example, the trees are normally scattered around the edges of forests and also along rivers. The solitary trees sometimes appear as clumps of two, or less frequently, three.<div><br /></div><div>Implementing this was a bit more challenging than I expected. Finding the border around a shape is not very easy or efficient, and in this case often creates a torus shape (a polygon with a hole in it) which is tricky to work with. Eventually I settled on iterating over all the underlying Voronoi polygons in the land and deciding for each one whether or not it was in the border area, and then whether or not it should contain a tree clump. At any rate, after that I have isolated trees around the edges of the forest masses:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh358EbzSR-HxWse7tZb8u517aBZ4LT349uJK-xW2f6AU8cvjnCduoljI0AwJ60njlweJ02Ne227KbXSA4Hka0kDRLc-m7cBdv2klV8Z292kfcLLsQ07vGR_Xjo9NU1Vh17NgaaMHwfvB43/s400/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="278" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh358EbzSR-HxWse7tZb8u517aBZ4LT349uJK-xW2f6AU8cvjnCduoljI0AwJ60njlweJ02Ne227KbXSA4Hka0kDRLc-m7cBdv2klV8Z292kfcLLsQ07vGR_Xjo9NU1Vh17NgaaMHwfvB43/s16000/Image1.png" /></a></div><div>Now I can do something similar for rivers. I'll look for locations near rivers that also have above average precipitation. (As an aside: I don't actually keep the precipitation values by the time I'm generating these trees, but I do have the biomes for each location, so I can use the biomes as a proxy for the level of precipitation.) That adds a scattering of trees alongside fertile rivers that get a lot of rainfall:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0CRPIqFEeXMOdJ0ULfzCBkdVcB3Lr9AFo00-7o36kdrAGab983kggGg-5uHpr2pEAq8dR7e0QMmqdssMvLSRlEYvcAwm11kWWOr-BwVu98fXCUCZy9L4iADSMn1TA-Q1q-U73ipxdCGPD/s400/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="278" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0CRPIqFEeXMOdJ0ULfzCBkdVcB3Lr9AFo00-7o36kdrAGab983kggGg-5uHpr2pEAq8dR7e0QMmqdssMvLSRlEYvcAwm11kWWOr-BwVu98fXCUCZy9L4iADSMn1TA-Q1q-U73ipxdCGPD/s16000/Image1.png" /></a></div>The last thing I want to replicate from Daniel's map are his <a href="https://en.wikipedia.org/wiki/Rhumbline_network" target="_blank">rhumblines</a>. Normally rhumblines radiate from spots in the ocean, or from a compass. Daniel has done something interesting by making them come from the middle of the land. This makes them useless as navigational aids, but it looks pretty cool. It looks a little odd to have these radiate from the exact center of my (square) maps, so I'll offset the lines upward a bit.</div><div><br /></div><div>The last thing I want to replicate is something Daniel does on the larger islands:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOkwg6YEB4Qg0ghT8HFC8ieE3q5utuCBB9LyzWq6k6sr4_qexk0At3HYNzv4NZoM4KiTXdf7Zfqf6Iu7JlNSzmXwGv5-EM1pV4YQF54M_T9nyAC9s0Y_wH4x0YHDcBIg_0qYQZ97v2a-qm/s400/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOkwg6YEB4Qg0ghT8HFC8ieE3q5utuCBB9LyzWq6k6sr4_qexk0At3HYNzv4NZoM4KiTXdf7Zfqf6Iu7JlNSzmXwGv5-EM1pV4YQF54M_T9nyAC9s0Y_wH4x0YHDcBIg_0qYQZ97v2a-qm/s16000/Image1.png" /></a></div>He puts a white circle around the island, and fills the circle with transparent white to lighten the background. I don't think this means anything; it appears to be just a decoration. Still, it looks neat and I'd like to be able to duplicate it.<div><br /></div><div>Picking the islands to decorate is an interesting problem. Daniel doesn't decorate all of the islands, generally avoiding those that are in clusters with other large islands or too close to the mainland. He also doesn't do this decoration on two islands close to each other. Here's my first attempt to pick out islands:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiLwLsGuLho-sl1JFjWJIabyCbRHqAmjZGy4JRZP94vJWntdjtpnHpeNHQeYuXxf6O6Y1Vygd3PVpPbR7n4gG1izhZeXMjkWLY1DXBGVUxiqmexEJShkn6XjkiRIEEthYO3Ip8-8xEpzED/s515/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="311" data-original-width="515" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiLwLsGuLho-sl1JFjWJIabyCbRHqAmjZGy4JRZP94vJWntdjtpnHpeNHQeYuXxf6O6Y1Vygd3PVpPbR7n4gG1izhZeXMjkWLY1DXBGVUxiqmexEJShkn6XjkiRIEEthYO3Ip8-8xEpzED/s16000/Image1.png" /></a></div>I have a test to make sure an island is not too close to the mainland, but I've got the polarity of the test reversed, so I'm picking islands that are close instead of far. Here's a second attempt:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLeetwwVdAF0n4JKTb_JJ7wz6sadwu42Fhqy40V0xnnQ4Z4GMZ_prcu_BiIDTlKC-MiGCmy7DPzz-17rmZhKXsAPkDOprWxB0tUIl59Lru01_ivNQg8GFuHYoAIC9PXzbT6CIo5GH0rS2P/s440/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="299" data-original-width="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLeetwwVdAF0n4JKTb_JJ7wz6sadwu42Fhqy40V0xnnQ4Z4GMZ_prcu_BiIDTlKC-MiGCmy7DPzz-17rmZhKXsAPkDOprWxB0tUIl59Lru01_ivNQg8GFuHYoAIC9PXzbT6CIo5GH0rS2P/s16000/Image1.png" /></a></div>This is better, but the upper island is too close to another big island. I want these to be somewhat isolated islands (at least from other big islands). Filtering for that gets me this:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj19Cu3ivtpFLLzmRotjrJW93i7Nx3GJiHSuZ4zQNmavzIvVwzw3e_-AMvLK-g_m8XBmmlsevpHPqcXeZCxiBfVZlTLykCdaT_qur_kBMxwdc6sbfivTXqnw8hYEZBJvf2u_s1CcxKswEcK/s440/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="279" data-original-width="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj19Cu3ivtpFLLzmRotjrJW93i7Nx3GJiHSuZ4zQNmavzIvVwzw3e_-AMvLK-g_m8XBmmlsevpHPqcXeZCxiBfVZlTLykCdaT_qur_kBMxwdc6sbfivTXqnw8hYEZBJvf2u_s1CcxKswEcK/s16000/Image1.png" /></a></div>Now I just need to adjust the circle and fill:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCTQ2dKz4oUzq9lWtW1ZFPvcXrD_CHFrcis9Y9045IAK2GeVTs-XRggZybQPGiOKp4_NSgxSvJFqmPvnMNEXLTbCOZR9F2Y_huqVAs3vc55qXTuCowpifqbJQfmMLJltnM8nemYq2afnI7/s392/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="252" data-original-width="392" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCTQ2dKz4oUzq9lWtW1ZFPvcXrD_CHFrcis9Y9045IAK2GeVTs-XRggZybQPGiOKp4_NSgxSvJFqmPvnMNEXLTbCOZR9F2Y_huqVAs3vc55qXTuCowpifqbJQfmMLJltnM8nemYq2afnI7/s16000/Image1.png" /></a></div>That's a pretty reasonable approximation.<div><br /></div><div>There are a few other features of Daniels' map that I won't be reproducing. His city icons are little works of art:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEincrdVvVKqZLndohAzn8tNiwnqaIJ0-WBTfRoDNWqgj6YqJDqHHJkMF-7dGQy3sMrDgocCDajy2hH3BYwSoeBpzrWMyy7E9JvEElWU9WyohNJ_ECnyFSZXL_HHbGZq17LvhOMDY174Arec/s194/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="165" data-original-width="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEincrdVvVKqZLndohAzn8tNiwnqaIJ0-WBTfRoDNWqgj6YqJDqHHJkMF-7dGQy3sMrDgocCDajy2hH3BYwSoeBpzrWMyy7E9JvEElWU9WyohNJ_ECnyFSZXL_HHbGZq17LvhOMDY174Arec/s0/Image1.png" /></a></div>And while <b style="font-variant-caps: small-caps;">Dragons Abound's</b> city icons are not terrible, they're not nearly as nice as this. (But that's something I hope to revisit!)<div><br /></div><div>Daniel also does something nice with his country borders by adding a halo effect:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5XAVc8pPQpeHeVwVZDE0om8pe-9VHfpEExl5_01nOpnsvXKhAROf_JFzgTlTB8V7fjyyDxa3M1ckjxYOkXXlM1xAoAEzJcteqyJJJBlIPszrDUhgZ_HIQ2WaCF7_TindjQDLJfnCZrYUn/s343/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="267" data-original-width="343" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5XAVc8pPQpeHeVwVZDE0om8pe-9VHfpEExl5_01nOpnsvXKhAROf_JFzgTlTB8V7fjyyDxa3M1ckjxYOkXXlM1xAoAEzJcteqyJJJBlIPszrDUhgZ_HIQ2WaCF7_TindjQDLJfnCZrYUn/s16000/Image1.png" /></a></div>I rather like that, and stylistically it fits in well with his labels. <b style="font-variant-caps: small-caps;">Dragons Abound</b> does do country borders and I could add this effect fairly easily, but I'm not happy with how <b style="font-variant-caps: small-caps;">Dragons Abound</b> places borders so I don't use that at the moment.<div><br /></div><div>Finally, there's a coast decoration Daniel does with a looping line on the inside of the coast:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKaNYBpYwsaGmxPL1XtPaaNYKFkl5DG5LnXTBDOEa6NwSpdeVSyPoBGWmCUvSXtqtGuoHkeOfripA-G3o5iu_Itd4fHE_dhur5_o0GAIiswt4PsxzEFphDc78WuwIv8IFkiLrXqWqsTcVz/s605/Image1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="231" data-original-width="605" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKaNYBpYwsaGmxPL1XtPaaNYKFkl5DG5LnXTBDOEa6NwSpdeVSyPoBGWmCUvSXtqtGuoHkeOfripA-G3o5iu_Itd4fHE_dhur5_o0GAIiswt4PsxzEFphDc78WuwIv8IFkiLrXqWqsTcVz/s16000/Image1.png" /></a></div>That's very nice but I don't have any good ideas on how to easily recreate it, so I'm going to pass on that as well.<div><br /></div><div>Next time, some complete maps!</div>Scott Turnerhttp://www.blogger.com/profile/03393071448515738228noreply@blogger.com0