Tuesday, January 9, 2018

City Symbols (Part 6): Fixing Some Problems

I'm going to pause development of the city symbols for a posting in order to address a couple of problems in the current implementation.



The first problem has to do with drawing lines.  Some time ago, I switched to a new method for drawing lines.  Rather than draw a line the traditional way (by moving a "pen" along a path), I draw a line by creating a polygon outline around the path, like this:

This has the advantage that I can change the width of the line.  In the example above, the width goes from near zero on the left to wide on the right.  I can then draw the line in a pleasing way by running a smooth curve (e.g., a series of Bezier curves) around the outline of the polygon.

Generally speaking this works very nicely and is a big improvement.  However, it sometimes runs into problems at the ends of the lines.  In the example above, notice that at the right end of the line, there are two sharp right-angle turns as the polygon goes around the end of the line.  If I'm drawing the outline of the polygon with straight lines this isn't a problem.  But if I'm using some sort of smooth curve, the curve might have to distort quite a bit to go smoothly around these turns.  This results in a bulge at the end of the line, as with this example:
Here I'm drawing an elliptical arc from left to right, and where the outline goes around the sharp turns the curve bulges out to smooth those angles.

Initially I addressed this by using straight lines to draw the polygon around the line.  As long as I use enough segments in the line, this looks fine, but that does eliminate one of the advantages of drawing lines this way.  

Somewhat belatedly, I realized that I don't have to use the same curve to draw the ends of the lines as I do for the rest of lines.  At the SVG level where the line is actually being drawn, I just need a connected path around the line; it doesn't all have to be similar curves.  So in theory I should be able to switch to a straight line for the small segment at the end of the line.

D3 -- which I use for most of my graphics -- doesn't seem to have a way to switch curves in the middle of a path without sticking in an extraneous M(ove) command.  (It might be possible to implement this form of line drawing with D3 Areas but it doesn't look like that's exactly right for arbitrary lines.)  So I have to generate the paths for the two sides of the line and the two ends separately and then stitch them together manually.  That's not terrible difficult to do:
And voila!  The problem is fixed.  This method creates a flat end to the line.  That can create an issue where two lines join, as is (barely) visible in this highly magnified example:
This often looks better if the lines have rounded ends.  Naively, you might think that this just requires connecting the two sides of the line with a semicircle rather than a straight line.  But doing that would actually cause the line to extend slightly (by the radius of the semicircle) past its endpoint.  To do this correctly, I have to back off the endpoint of the line by the radius of the semicircle and then connect it with a semicircle.  I'll leave this on the TODO list until it looks like I really need that capability.



The second problem has to do with how I'm drawing round houses like these:
I'm using a quadratic Bezier curve for the round parts of these houses.  This looks okay by itself, but a problem becomes apparent when I try to draw in the back half of one of these roofs:
The outline of the roof is too pointy at the ends -- it's a football when it should be an ellipse.  I should be drawing a (half-)ellipse for the round houses instead of the curve I'm using.

The curve-creating function I'm using is very handy because it allows me to specify the starting and ending points of a curve, and the height it should curve out at the midpoint and then step along the curve generating points at regular intervals.  Doing the same thing for an arbitrary ellipse turns out to be considerably more complex, but the case I need is a little simpler.  In my case, the ellipse is axis-aligned.  Consulting the Wolfram Alpha ellipse page, I find a parametric equation for points on an ellipse that looks like this:

`x = a*cos(t)+C_x`
`y = b*sin(t)+C_y`

In these equations a and b are the X and Y axises of the ellipse, t is an angle from the center of the ellipse, and `C_x` and `C_y` are the coordinates of the center of the ellipse.  Using this equation I can write a curve-creating function that produce half-ellipses between two axis-aligned points.  Substituting the new function into my building-drawing code gives me this:
That looks a little better.  Next time I'll fix up the faux 3D view for buildings with battlements and parapets.

No comments:

Post a Comment