Day 10: (there are no good puns with the word "stroke")
Part of the Advent of Grok {Shan, Shui}*. See blog post for intro and table of contents.
So, let’s continue to dig into the box_
function—now, to the stroke
call.
As usual, casually going through “beautiful” pictures during debugging the refactored…
(the arrow shows where the box is supposed to be; but that’s nothing of interest, I just noticed the stroke
has its own x_offset
and y_offset
parameters and passed them from box_
, but forgot to remove their second addition in the box_
itself)
And here we go:
function stroke_(points,
{x_offset = 0, y_offset = 0, width = 2, color = rgba(200,200,200,0.9), noise = 0.5, outline = 1,
shaper = (x) => Math.sin(x * Math.PI)}
) {
if (points.length == 0) return "";
var n0 = Math.random() * 10;
var transitions = points.eachCons(3).map( ([[x_prev, y_prev], [x, y], [x_post, y_post]], idx) => {
i = idx + 1
var distance = width * shaper(i / points.length);
distance = distance * (1 - noise) + distance * noise * Noise.noise(i * 0.5, n0);
var angle_before = Math.atan2(y - y_prev, x - x_prev)
var angle_after = Math.atan2(y - y_post, x - x_post)
var angle = (angle_before + angle_after) / 2;
if (angle < angle_after) angle += Math.PI;
return [distance, angle]
})
var vertexes_top = transitions.zip(points.slice(1)).map(
([[distance, angle], [x, y]]) => [x + distance * Math.cos(angle), y + distance * Math.sin(angle)]
)
var vertexes_bottom = transitions.zip(points.slice(1)).map(
([[distance, angle], [x, y]]) => [x - distance * Math.cos(angle), y - distance * Math.sin(angle)]
)
return [
points[0],
...vertexes_top,
points.last(),
...vertexes_bottom.reverse(),
points[0]
].
map(([x, y]) => [x + x_offset, y + y_offset]).
and_then( vertexes => poly_(vertexes, {fill: color, stroke: color, width: outline}))
}
…it should be more or less obvious now: given a line of points (x, y)
, it calculates two sets of pairs of other dots, being somewhere around the line, but randomly shifted, and then draws a filled polygon with outline to imitate “humane” brush stroke. If we’ll increase box_
’s weight
parameter, it becomes a bit more visible:
Somewhat jumping into the future (this particular paragraph is written on day 23, generic cleanup day): by the end of the investigation I allowed myself much more “Ruby-like” shortcuts. So, the array used in
return
was turned into this (which is definitely clearer for me, but YMMV):return [ points.first, ...vertexes_top, points.last, ...vertexes_bottom.reverse(), points.first ]. // and so on, as before
Now, the last mystery of our box_
call in original house was the dec
parameter, where that house passed deco
function (I adore short names). Let’s add it to debug script and see.
Our call to box_
will now look this way (mimicking the code from the arch02
):
var hsps = [[], [1, 5], [1, 5], [1, 4]]
var vsps = [[], [1, 2], [1, 2], [1, 3]]
var sty = 3
var box = box_(100, 100,
{
height: 60,
rotation: 0.3,
weight: 3,
perspective: 20,
transparent: false,
dec: (a) => deco(sty, {...a, hsp: hsps[sty], vsp: vsps[sty]})
})
And the result would be this (with sty=3
)
Here are other possible styles: 1 and 2 (because, looking into deco
for some time, we can understand that’s what sty
means!)
Style (2) we’ve seen a lot on our “test house”, here are styles 1 and 3 in the same picture I am using as a test one, other side of it:
The call to dec
parameter (which I’m renaming decoration
) in box_
looks this way:
var surf = (rotation < 0.5) * 2 - 1;
lines = lines.concat(
decoration({
pul: [surf * width, top],
pur: [front_x, top + perspective],
pdl: [surf * width, bottom],
pdr: [front_x, front_y],
}),
);
Basically, it passes four corners to the provided decoration function, and it can return some additional lines (sets of coordinates), adding, well… “decorations” to the wall!
Now, this (baffling for me! My brain processes arithmetic slower than code) surf
variable is actually a fancy way to say “we choose either left, or right wall, depending on the rotation”: if rotation is < 0.5
, then (rotation < 0.5) * 2 - 1
evaluates to 1 * 2 - 1
, e.g. just 1, and 1 * width
would be the rightmost part of what we are drawing, and if the rotation is higher…
…another wall is choosen.
So we can simplify the call to:
var side = (rotation < 0.5) ? right : left;
lines = lines.concat(
decoration({
pul: [side , top],
pur: [front_x, top + perspective],
pdl: [side , bottom],
pdr: [front_x, front_y],
}),
);
Note also that for pur
(“point upper right”, I guess!) we are passing top + perspective
so the decoration will know the “proper” coordinate of the front upper corner, unlike the line of the post:
(red line is drawn by me on top of the screenshot).
Now, for the deco
function, I want to do only a shallow rewrite, so it would have more civilian signature. Inside it, we have a ton of simple “calculate point” arithmetic, but as I am almost at half of my advent, and it is pretty easy to understand nothing non-trivial happens inside, I’ll not bother.
The singature will look this way now:
var decorator = function(style,
{
up_left = [0, 0], up_right = [0, 100], down_left=[100, 0], down_right=[100, 100],
horz_spacing = {padding: 1, count: 3}, vert_spacing = {padding: 1, count: 2}
}
)
…and I updated style
choice to use words instead of numbers, so the call to box with decoration will look this way:
var horz_spacings = {
t_shapes: {padding: 1, count: 5},
fence: {padding: 1, count: 5},
grid: {padding: 1, count: 4}
}
var vert_spacings = {
t_shapes: {padding: 1, count: 2},
fence: {padding: 1, count: 2},
grid: {padding: 1, count: 3}
}
var style = 'grid'
var box = box_(100, 100,
{
height: 60,
rotation: 0.3,
weight: 3,
perspective: 20,
transparent: false,
decoration: (points) =>
decorator(style, {...points, horz_spacing: horz_spacings[sty], vert_spacing: vert_spacings[sty]})
})
…and inside box_
:
var side = (rotation < 0.5) ? right : left;
lines = lines.concat(
decoration({
up_left: [side , top],
up_right: [front_x, top + perspective],
down_left: [side , bottom],
down_right: [front_x, front_y],
}),
);
Phew! All mysteries of the box are solved! Tomorrow, we’ll return to the house itself, which also has rail
and roof
. To be completely honest, I don’t expect a lot of further insights there: it would be probably more combinations of lines, coordinates and points. But at least one house needs to be finished to say “we are now getting how it is done”!