In this posting I'll talk about making these scribbles more natural looking. I'll use my usual simple mountain as an illustration:
The first refinement I want to try is to make the lines of the scribble into gentle arcs. When people scribble lines back and forth, they typically pivot their drawing hand around their wrist. As a result, they draw shallow arcs rather than straight lines.
To make each segment of the scribble into an arc, I will add a new point at the midpoint of the segment, and offset that midpoint a short distance along the perpendicular to the line:
The new (blue) line from `P_0` to `P_1` passing through `M'` will then form a shallow arc (because I'm using D3's ability to interpolate between points on a polyline with smooth curves). One point to note here is that every other stroke of the scribble I have to swap the offset from side to side. This is necessary because the first stroke goes up from bottom to top (in the Y direction) and the following stroke comes down from top to bottom. It's also a good idea to scale the offset based upon the length of the stroke, or short strokes will have a stronger curve than long strokes.
Aside: As I was working on this I discovered a bug in the code I borrowed from the XKCD plot in Javascript. I fixed that problem, sent a patch off to the author, and then found a problem in my code that had apparently been masked by the problem in the other code (!).
Here's a comparison of the original straight stroke to a (strongly) curved stroke:
And here's a version with slightly less curve and the hand-drawing jitter turned on:
This doesn't look good. There's a kink in the middle of each arc, particularly noticeable in the longer lines. What's going on here?
Remember when I said the arc was created using D3js's curve interpolation? It made a smooth arc between the three points defining each stroke, because there was a lot of "room" between the points to fill in a curve. But when I turn on hand-drawing jitter, those three points are interpolated into many points, much closer together. And since those points are fixed, D3js's curve interpolation can't move them to make a smoother curve.
The solution is to do the curve interpolation first, and then the jitter interpolation. Unfortunately, D3js doesn't provide any (easy) way to do this. After an email exchange with Mike Bostock, it looks like the best approach is to draw the original curve, and then interpolate it by measuring along the curve and sampling points. These points can then be jittered to make a new line. Here's the arc+jitter version along with the straight and the arc versions:
It's a minor difference at this step but worth keeping.
The next refinement I want to implement is to break up the regularity of the distance between the scribbles. I'll do this by varying the step size as I sweep the finder line through the polygon. I randomly offset the step size by up to half the step size smaller or larger. Here's an example alongside the previous versions:
Now the scribbles vary in spacing, but the upward strokes all have precisely the same angle. I can address that by varying the angle `mu` of the finder line as it steps through the polygon. I'll also turn on the code the varies the width of the line. Some examples:
And some denser examples, probably more similar to what I might use on a map:
(The densest of these takes about 3 seconds to render on my desktop machine, by the way.)
One problem with the examples above is that the shading precisely fills a very regular shading area -- this is particularly glaring in the denser examples. The shading area is defined as the left side of the triangle:
I can make the outline of the shading area less precise using the same perturbation that I use for making hand-drawn lines -- here's what that looks like with increasing amounts of perturbation:
That looks better than the very precise shading area, but notice that the shading area now sometimes goes outside of the mountain. That's not entirely unpleasing, but that rarely (if ever) happens in the hand-drawn examples I've looked at. So I'd like to modify the perturbations so that they can only go inward.
The perturbations in the hand-drawn line code come from this:
// Generate some perturbations.The function d3.randomNormal generates numbers with a mean of 0 and a standard deviation of 1. It turns out that if you are perturbing a polyline that is in counter-clockwise orientation, the negative numbers perturb the line inward and the positive numbers perturb the line outward. So to perturb only inward, I can just use the negative numbers. (And ensure my polyline is in counter-clockwise orientation!). This is the result:
var perturbations = smoothLine(points.map(d3.randomNormal()), 3);
You can see that the shading now generally avoids going "outside the lines". As the last two examples show, it can still happen in sharp corners -- because the inward perturbation actually pushes the line back so far it crosses back over the other line, creating a pocket outside the shading area, e.g.,
I don't see offhand any straightforward way to deal with this, so I'll just live with it for the moment. It may be that when I get around to creating the shading area for real, I'll use some better approach to perturb it and this feature won't be needed for shading anyway.
So I'm now able to make a "hand-drawn" line and scribble within an area. In the next posting, I'll start looking at creating a more convincing mountain shape than the simple triangle I've used so far.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.