Radial Basis Functions (rbf)
! If @David_Tschumperle wants to draw my attention to a singularly obscure G’MIC code passage, he only has to throw in a rbf
call. Works like a charm. My obsessions are too well known here.
S = [ cos(tilt), sin(tilt) ];
An aside: Leonhard Euler said I could do this:
foobar : skip ${1}
ang:=$1°
eval " const tilt=$ang;
S = [ cos(tilt), sin(tilt) ];
print(S);
U = cexp([0,tilt]);
print(U)"
A nifty way of getting sine/cosine pairs, without explicit calls to the same.
gosgood@bertha ~ $ gmic -m /dev/shm/foobar.gmic foobar 57
[gmic]./ Start G'MIC interpreter (v.3.3.3).
[gmic]./ Import commands from file '/dev/shm/foobar.gmic', with debug info (1 new, total: 4711).
[gmic_math_parser] S = (uninitialized) (mem[37]: vector2)
[gmic_math_parser] U = (uninitialized) (mem[43]: vector2)
[gmic_math_parser] S = [ 0.5446390350150272,0.83867056794542394 ] (size: 2)
[gmic_math_parser] U = [ 0.5446390350150272,0.83867056794542394 ] (size: 2)
[gmic]./ End G'MIC interpreter.
gosgood@bertha ~ $ gmic -m /dev/shm/foobar.gmic foobar -134
[gmic]./ Start G'MIC interpreter (v.3.3.3).
[gmic]./ Import commands from file '/dev/shm/foobar.gmic', with debug info (1 new, total: 4711).
[gmic_math_parser] S = (uninitialized) (mem[37]: vector2)
[gmic_math_parser] U = (uninitialized) (mem[43]: vector2)
[gmic_math_parser] S = [ -0.69465837045899703,-0.71933980033865141 ] (size: 2)
[gmic_math_parser] U = [ -0.69465837045899703,-0.71933980033865141 ] (size: 2)
[gmic]./ End G'MIC interpreter.
Thanks be to Euler’s Identity.
S = [ cos(tilt), sin(tilt) ];
…
pP = J[-1];
P = I;
T = unitnorm(P - pP);
N = lerp([ -T[1],T[0] ],S,tilt_strength)*thickness;
C = round(P - N);
D = round(P + N);
…
Now. This is a gem. But — I must confess — I didn’t tumble onto it immediately. Dotless (crossless) calligraphy? Had to draw a little diagram:

In the routine case, your approach offsets points C and D along courses parallel to vectors ±N. Depending on the blending parameter tilt_strength
, this orients +N somewhere in the midsts of the pink arc, being — segment by segment — oriented through a mix between the globally fixed direction S and the locally varying perpendicular vectors ±T. –N obediently follows along 180° out of phase.
Points C and D are offset at most by the distance 2×thickness
, when ±N are perpendicular to the segment P—Pp and coincident (parallel) to the perpendiculars ±T. That particular case arises when (a.) the user has set tilt_strength
to the blending extreme of zero, so that the vectors ±N are, in practice, aliases of perpendiculars ±T, the direction S being blended out entirely. By definition, ±T are always perpendicular to the segment P—Pp, their orientations are invariant with respect to the segment, so, segment-by-segment, C and D are always offset by the same distance from segment P—Pp. This is the “non-calligraphic case”. I could be drawing with a felt pen that has a circular cross section.
Then there is case (b.). The user has set tilt_strength
to the other blending extreme of one, so that the vectors ±N always coincide with globally invariant S. Should a particular segment, P—Pp, run perpendicular to S then the situation is cut from the same cloth as (a.), in that vectors ±N parallel ±T and those circumstances offset points C and D to the maximum separating distance of 2×thickness
. That obtains the maximum calligraphic width. The minimum arises on those occasions when the orientation of P—Pp is exactly that of S. ±N is locked to S, so is coincident with P—Pp. Points C and D extrude along the length of P—Pp, so the green quadrilateral becomes, perhaps, a triangle, if the priors A and B had non-zero perpendicular offsets, or a line otherwise. In any case, we witness the minimum calligraphic width. In the general run of affairs ±N coincides with a blended direction betwixt the orientations of S and ±T and exhibits the blended behavior of both. It is, all and all, a nice bit of work. Permit me to admire it for a spell.
Now, what sets me to glancing out the window and sighing a bit is this affair of feeding polygon()
over-and-over-and-over again, four-point data sets per throw, for hundreds of times. That makes me a bit sad. You are a good deal better acquainted with its internals than I am, so, perhaps my performance worries regarding polygon()
are founded on sandy substraits, but in the run-up to the Arabesques article last year, I learned that if I could fashion up some way to feed polygon()
an entire curve plotting data set in one polygon()
call, it performed roughly 2× to 3× faster over plotting points pairs at a time.
But, of course, perhaps that is the rent to pay for getting around the self-intersection quibble: hundreds of tiny quadrilaterals instead of one big one (that self-intersects). In any case, it is probably cheaper than hundreds of calls to thicklines
, which happened to be the first thing I saw on the shelf yesterday afternoon.
Now I see that you have posted a bevy of new things whilst I’ve been working my way through this. Perhaps I should wait a bit before posting this.
Then again, perhaps not.