Tutorial
Wheelies | Interlude | Deep Dive | From Wheelies, Arabesques |
From Arabesques, Wheelies | Wheelie Animations |
"Durga's idle arabesques" | During brief interludes, when tranquility prevails and the peoples of the world are in complete accord, Durga might take ease from her duties and find serenity through simple pursuits. It is a small pleasure to swing a compass marker around its anchor leg, and if hands might quave arcs still run true — not that palsy ever troubles Durga, master as she is of the chakra and trishul. And so it is hardly burdensome for her to take up compass after compass, anchor to marker, twirling each at spins and spreads picked through whimsy. Such a flurry of hands and arms might astonish mortals, but for the slayer of the abominable Mahishasura it is just so many twirlings. With each added compass, the culminating marker traces ways ever more sublime, awarding Durga escalating delights. And so it goes — until demons arise. Durga must then put away her compasses and take up her arrows and bow. |
simple | threewheelie | colorwheelie |
1. | set a particular radius: | +r |
2. | pick a starting orientation: | ±θ |
3. | choose a signed, clockwise or counterclockwise, rotational rate: | ± 2πf |
1. | Root the first wheelie at the origin. |
2. | Hinge additional wheelies at successive free end points, perhaps setting their orientation differently or aligning them all. Do that any number of times, or |
3. | attach a marker to the rim of a terminal wheelie and take a picture of the pip it draws. |
4. | Adjust the orientations to account for 2πf(Δt) angular accretion and plot again … and again until the accumulated adjustment equals some preset, perhaps a full circular rotation of 2π, anticipating that that period draws one arabesque from start to finish. What remains is the traversal path of the marker in all of its charm — an arabesque. |
1. Walking a wheelie chain from marker tip to the origin |
3. The locality of one wheelie pair |
4. Localities of the second and third, and first and second wheelie pairs compounded together |
5. Localities of three wheelies of rates one, two and three |
Line | Remark |
21 | $=a A pseudo assignment. G'MIC transforms this into a series of actual assignments. This $-expression generates an assignment sequence with the base name a for however many command line items there may be. $a0 identifies the name of the script, so one could alter script behavior based on the script name. $a1… onward are the arguments. See Adding Custom Commands. There is nothing special about the base name a. It could be anything: $=fruitloops initializes command line variables $fruitloops0, …, $fruitloops<n>. See 42 for usage. |
25 | Argument Count: $# is an $-expression that resolves to the number of comma-separated arguments given to gtutor_wheelie. Thus, with the pseudo assignment shown in 21, the command line interpreter would resolve ${a{$#}} to the last command line argument. See also 42. |
25 | Argument Triplets: Wheelie specifications occur in groups of three, +r, ±θ and ±2πf, so modulo 3 inverted, !(($#)%3), resolves to True in such cases, enforcing arguments given three-at-a-time. Thus, the user employs triplets of successive arguments to specify wheelies, one triplet for each wheelie. Substitutions: $-expressions specifically appear in custom command definitions, such as the one of which we are in the midsts: gtutor_wheelie. See Adding Custom Commands. See also Substitution Rules for those substitutions that the command line parser recognizes in any setting, not just custom commands. |
32 | Math Expressions: Curly brace pairs here invoke the Mathematical Expression Parser. The present case sets the command line variable wcnt to the number of wheelies specified on the command line. $#, divided by three provides such, as each wheelie consists of three command line arguments. |
33-53 | Persistent Storage: Before Dynamic Arrays, it was not uncommon to harness images as persistent data storage, here image args (34), for mathematical environments as these do not persist. Data storage images also underlie Dynamic Arrays, but wrapped in a nice application programmer's interface (API). In this Old School approach, args is not intended for display but as an argument database, one to be shared across math expression invocations. Wheelie Database I: Each pixel in this "argument image" specifies aspects of one wheelie. Channels 0-2 hold +r, ±θ and ±2πf data, and this first repeat…done loop, 40-53, saves them for future reference; see 114-118. Channels 3-11 are reserved for the wheelie-specific affine transforms. To reduce computations in the animation loop between 92-133, we break the computation of the affine matrix into "stage 1" and "stage 2" phases. "Stage 1" precomputes matrices with only +r and ±θ items, which are time-independent. "Stage 2" computation occurs much more frequently, at every Δt tick, but with stage 1 computations done, there is only a need to composite an additional rotation into "stage 1", reflecting wheelie rotation during Δt. See 115. |
42 | Accessing Argument Lists: A pseudo assignment such as $=a, see 21, gives rise to a a number of actual command line variables $a0, $a1, …, $a<n>. An "array-like" addressing scheme may be carried out, which rad=${a{1+$j}} exemplifies. The curly-brace {1+$j} post-fixed to a is reminiscent of a decorator indexing the "j-plus-one" argument array item. However, suggestive though that notation may be, no actual array access is taking place — it is just syntax sugar. This sweetening relies on the kind of name coordination that the pseudo assignment $=a sets up: a set of variables with common basenames and numeric suffixes in progression. In fact, the command line interpreter undertakes a double substitution. The innermost braces ultimately invoke a mathematical expression; see 32. But before such, the command line interpreter looks up the current value of $j and substitutes it for the look-up value; then the mathematical expression parser is invoked. That tool "sees" just two literal numbers, 1 and the loop count which $j formerly referenced. The mathematical parser has no idea about $j; it exists in another universe. The mathematical parser has just two literal values; it adds these and hands the results back to the command line interpreter. The command line interpreter now regards the transformed construct anew: it is a variable ${a<n>} with <n> the number that $j represents. This engenders a second substitution, a look up for what $a, followed by some number <n>, represents. With foresight and planning, ${a<n>} actually exists, the look up of which rad duly acquires. So for those familiar with this syntax sugar, the process seemingly "accesses the argument array" and all goes well. However, should foresight or planning be lacking — misunderstanding the number of arguments, perhaps — then the look up proceeds anyway, likely returning the empty string of a command line variable used before it is assigned. From there, any number of subtle bugs may arise. Foresight and planning follows from a pseudo assignment such as $=a to set up the pseudo argument array. |
62-68 | Wheelie Database II: fill iterates over the args image, computing the "stage 1" affine transformations for each wheelie. Storage for each wheelie occupies one of the twelve channel pixels of the args image; In each step of the fill, the current arg image pixel, I, represents all that is known about one particular wheelie. |
63 | Pixel Accessors: Assigning I to CPX is not a (redundant) assignment of one vector to another, rather it is the creation of a vector from a pixel accessor function. The right hand side, I, is the pixel accessor; see Specific Functions, bullet points 5 – 10. There is a square-bracket form of the pixel accessor function; its signature, I[#_ind,offset,offset,_boundary_conditions], differs from the square-bracketed form applied to vectors: V[ <starting index>, <subvector length>, <step> ]. The aim here is to remap the pixel to the vector, CPX, so as to harness vector accessor function instead. See 67 following. This particular pixel accessor, I unadorned, lacks an explicit argument decorator, so the default is in play: I(#ind,x,y,z,interpolation,boundary_conditions) where x, y, and z are the predefined math expression variables that, under image iteration, assume the current image column, row and depth values. By default, pixel accessors reference the current image. An optional "hashtag-identifier" initial argument explicitly identifies an access source by an image list index or image name: #2 identifies the third image on the list, counting from the beginning (remember: zero-indexed: 0,1,2!); #-3 identifies the third image counting from the end of the list. #$bluishimag identifies the image named bluishimag, should one exist. If there is no image that is so named, or if any of the access identifiers reach beyond the extent of the image list, you cop yourself an access error. You can access in-between pixels. That is, pixel coordinates x, y and z may be reals with fractional parts — you access the void between pixels! What happens then? An interpolation takes place through one of three flavors: 0:nearest neighbor; the interpolator reports the value of the nearest pixel along each image axis; 1:linear; the interpolator reports the value taken from a linear ramp between the two actual neighboring pixels; 2:cubic; the interpolator reports the value taken from a cubic spline intersecting the two actual neighboring pixels. See Pixel Accessors: Kinds of interpolation. You can access off-image pixels; perhaps you have overidden x with a value far exceeding the image width. What happens then? You apply boundary policy via boundary conditions: 0:dirichlet, the off-pixel is black, 1:neumann, the off-pixel is the same as the nearest edge pixel, 2:periodic, images repeat out to infinity along all axes; the off-pixel assumes the value of wherever it is in this repeating cycle, or 3:mirror, edges are mirror axes where images flip, and flip again out to infinity; the off-pixel assumes the value of wherever it is in this mirror repeating cycle. See Images Have Edges: Now What? for the deep dive. Some pixel accessor functions use a square-bracket decorator, these intended to reference images as if they were one dimensional arrays: I[_#ind,offset,_boundary_conditions]. It is easy to confuse this notation with accessing items of a vector, also using square brackets, and, by extended confusion, taking I to be a vector. Don't confuse this square bracket flavored pixel accessor with vector accessors, described later; see 67. |
64 | Making Rotation Matrices: rot() generates pure rotational matrices: 2 × 2 for two dimensional work and 3 × 3 for three dimensional. In the latter form, one provides the axis of rotation to complete the specification: [0,0,1] identifies the three-space +z unit axis. That we are operating in three dimensions may give one reason to pause. Recall our preference for working with homogeneous points, inhabitants of a three dimensional projective space, so that we can encapsulate both rotation and translation in one transform — as well as disambiguating points from vectors. In this case, the axis of choice is the +z axis, perpendicular to the canvas, so that when we rotate points in the canvas plane, they stay in the canvas plane. The first use of this function gives us orot, origin rotation derived from θ, the second item of the wheelie vector. deg2rad() conveniently converts this item from degrees to radians. So does the ° operator — the degree symbol. Thus 45.739° in a G'MIC math expression quietly transforms itself into ≈ 0.798296 radians. Consult your operating system manual on how to obtain off-keyboard characters if you cannot find the ° symbol on it. |
65,67 | Wheelie Matrix Composition I: We generate the translation matrix, xlat by starting with the 3 × 3 identity matrix, as furnished by eye(). In an affine transform, matrix elements two and five embody spatial displacement. We set only an x displacement, this the length of the wheelie; what of y (xlat[5])? Recall from the main discussion that we view individual wheelies in a relative way, so it suits us to build up the local wheelie transform where the wheelie itself is in a standard orientation: zero degrees, aligned to the +x axis. Thus, in this local space, there is no displacement in y. |
67,68 | As noted before ( Wheelie Database I, 40-53), CPX just holds +r, ±θ and ±2πf; we assemble a new version of CPX, one that additionally contains the time-independent "stage 1" wheelie transform matrix; see Figure 2. We re-compose this new version from a 12 item vector composed of two fields. The first field, items 0 - 2, is a copy of the initial three fields of CPX and contains +r, ±θ and ±2πf, obtained during command line processing; see 40-55. The matrix multiplication mul(orot,xlat,3) populates the second field, items 3 – 11; mul() produces a vector9, the 3 × 3 "stage 1" wheelie transform. Wheelie Matrix Composition II: The second field composing the wheelie vector harnesses mul() to multiply together matrices orot, the rotation component generated from ±θ, and xlat the translation component generated from +r. The right-to-left multiplication, or composition, of the translation and rotation matrices produces the wheelie matrix, which transforms a point at the origin of the wheelie to its tip; it reflects the combined operation of displacing the point along the +x axis by distance +r, then rotating by ±θ. Matrix Multiplication: Order Matters: Care must be taken, for the right-to-left composition of a translation and a rotation is not the same as a left-to-right composition. With the latter, the resulting matrix reflects the combined operation of first rotating the point at the origin, and then translating it. Alas! The rotation of a zero-length position vector — a point — is of no consequence: a point rotating around itself remains unchanged. That leaves a translation without rotation, a less-than-full operation. That is why the composition starts with xlat, generating a positive length displacement vector aligned along +x, to which we compose orot, rotating that displacement vector around the local origin. The non-communicative quality of matrix algebra is a source of many an interesting bug — amusing after the fact, exasperating in the moment. |
67 | Vector Accessor Function: CPX[0,3,1] is an example of a vector accessor function, which extracts subvectors from source vectors. The present example copies +r, ±θ and ±2πf from CPX to the fill return vector. The accessor function's three arguments, [0,3,1], correspond to [ <starting index>, <subvector length>, <step> ]. The first argument marks the index in the source vector where the subvector starts. It is the only required argument and, used in isolation, simply extracts a single item from the source vector. The second argument, if present, sets the subvector's length. Thus V=[0,1,2,3,4,5,6,7,8,9]; SV=V[5,3] sets SV to [5,6,7], the three item subvector beginning at index = 5. The third argument, if present, sets the step, and defaults to increments of one item. Thus V=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];V[0,4,4] retrieves the subvector [0,4,8,12], the four item subvector starting at index 0 in the source vector and consisting of every fourth item thereafter until a length of four items is obtained. A vector accessor function can only read vectors; it cannot write them. This precludes its use on the left hand side of an assignment. For that, consider copy(), which can transcribe a subvector from a source to a destination vector, or sequencing a new vector's content within a pair of square brackets, as done in line 67. |
67 | Implied Assignments: As the last statement in the math expression argument to fill, the re-composed vector12, [CPX[0,3,1],mul(orot,xlat,3)], undergoes an implicit vector-to-pixel conversion, updating the current "wheelie entry" in the database image args. |
71-72 | Passing Data to Math Expressions: Having filled the image-based "wheelie database", we harness store to make the wheelie data accessible to any ensuing math environments. These may do so through the circles image storage variable. The permutation prior to store reorganizes the args image; the new permutation leaves wheelie components arrayed along the arg image x axis instead of along its spectral axis and make for more straightforward access in the arabesque drawing loop, 92-133. |
74-88 | Screen space: We form another transformation matrix, xxsfrm, based on specw, a resolution metric. This matrix relates one unit of distance in the wheelie computation space to half of the smaller image dimension. This heuristic anchors computational measure to a definite number. The resolution metrix specw figures in a number of sizing tasks, such as scaling the number of plots necessary to draw an arabesque (92). Matrix xxsfrm flips the direction of the y axis and locates the plotting origin in the upper left hand corner, in keeping with pixel images. When all other transforms have been carried out on arabesque plots, xxsfrm situates the plot in a pixel image. See 123-124. |
92-133 | Animation Loop: The arabesque animation loop plots one mark of the arabesque per iteration. 92: -repeat 3*$sw k=$> … -done establishes the number of plots; k grows with larger images. The multiplier, 3, is another heuristic; lower this multiplier for faster, sparser plotting. |
110-122 | Integrated Matrix: The repeat() mathematical function carries out the pairwise aggregation of local matrices into an all-embracing translation from the root of the wheelie chain to its tip. On repeat()'s denouement, imat ( intergrated matrix ) embodies this across-the-board translation. This is the "…compounding (of) individual circular motions into wider localities" described in the text. The loop walks from tip to root, cc-k-1 ⇒ idx. For each wheelie, idx, in a chain with cc links, the loop -repeat cc,k, … first retrieves wheelie idx rotation rate, 2πf, via a vector access: circs[12*idx+2] (67). To ascertain wheelie idx rotation to the present, we multiply this rotation rate with accumulated time: omega, obtaining aggregate rotation 2πft. We need to embody this rotation in an affine space transform to compound it with the kindred transforms of antecedent wheelies. To this end, we harness rot() to generate a rotation matrix around the affine space +z axis. This pure rotation matrix embodies the time-dependent rotation of wheelie idx. The inner mul() operation, 114-118, composes this time-based rotation with the wheelie's time-independent displacement and orientation matrix, (+r, ±θ), pre-computed back in "stage 1" (67-68 Wheelie Matrix Composition II:), retrieved through circs[12*idx+3,9,1]. The composition of these time-dependent and -independent matrices turns out the dynamic local motion of wheelie idx. The outer mul() expression, spanning 113-121, rolls this local motion into imat. Dropping out of this repeat loop completes imat's composition, a grand finale matrix that embodies the entire transform of a point from the origin of the wheelie chain up to the tip of the final wheelie. |
123-134 | The bottom of the arabesque drawing loop concerns itself with plotting arabesque marks: line segments between adjacent Δt plots and drawn by polygon(). The drawing loop maintains a two-plot window, realized by storage variables firstpt and lastpt. These points are in display space, so the matrix responsible for that transform is summoned from storage variable ssxform. Recall 79-88. imat*pp carries the point pp from the origin to the wheelie tip. It is subsequently transformed by ssx into a display space plot. To create a two point plotting window, the drawing loop retains the plot of the previous iteration in lastpt and draws from that to the current plot; before the iteration finishes, the current plot becomes the next inhabitant of lastpt, to serve in a similar capacity in the next iteration. That accounts for most drawing activity, excepting the initial and final boundary cases. Nothing is plotted in the first iteration. Instead, the initial plot is stashed to both firstpt and lastpt. Through all remaining iterations, up to the last, firstpt is undisturbed. When the last iteration is realized and plotting has taken place, there remains a gap between firstpt and lastpt. These become plot points for an extra, final call to polygon(), which closes the gap between the initial and final plots, finishing the arabesque. |
Nearest Neighbor | |
gmic \ -input (255,18^236,134^170,215) \ -name. yeloblu \ -input 16,1,1,3,I(#$yeloblu,x/(w-1),0,0,0) \ -name. yeloblunearneigh | |
Linear | |
gmic \ -input (255,18^236,134^170,215) \ -name. yeloblu \ -input 16,1,1,3,I(#$yeloblu,x/(w-1),0,0,1) \ -name. yeloblulinear | |
Cubic | |
gmic \ -input (255,18^236,134^170,215) \ -name. yeloblu \ -input 16,1,1,3,I(#$yeloblu,x/(w-1),0,0,2) \ -name. yeloblucubic |
Next: | Deep Dive |
G'MIC is an open-source software distributed under the
CeCILL free software licenses (LGPL-like and/or
GPL-compatible).
Copyrights (C) Since July 2008,
David Tschumperlé - GREYC UMR CNRS 6072, Image Team.