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).
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.
It's important to note that the marker indicates the outside 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.
I'll begin by putting in the basic structure for drawing a circle:
function interpretCDL(svg, cdl, center=[0,0], debug=false) { let radius = 100; for(let i=0;i<cdl.length;i++) { const op = cdl[i]; if (op.op == 'CIRCLE') { // Draw a circle Draw.circle(svg, center, radius-op.lineWidth/2, op.lineWidth, op.lineColor, op.fillColor); radius -= op.lineWidth; if (debug) console.log('Draw a circle.'); } else if (op.op == 'SPACE') { // Skip some space if (debug) console.log('Skip some space.'); } else { console.log('executeCDL encountered an unknown opcode: '+op.op); }; }; };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.
function circle(svg, center, radius, lineWidth, lineColor, fillColor) { svg.append('circle') .attr('cx', center[0]) .attr('cy', center[1]) .attr('r', radius) .style('stroke-width', lineWidth) .style('stroke', lineColor) .style('fill', fillColor); };
} else if (op.op == 'SPACE') { // Skip some space radius -= op.n; if (debug) console.log('Skip some space.'); }
- 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?
- 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?
- My Map Borders Description Language 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?
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.