Tutorial
Wheelies | Interlude | Deep Dive | From Wheelies, Arabesques |
From Arabesques, Wheelies | Wheelie Animations |
17. A 1911 Goudy Bookletter Ampersand via 1,024 delta wheelies | Fourteen years ago, Santiago Ginnobili wondered on YouTube if, somewhere in the Universe, a planetary orbit might exist resembling Homer Simpson. This — being YouTube — induced Dr. Ginnobili to post the prerequisite demo video. It did indeed demonstrate a system of epicycles tracing the elder Simpson. Explanations for finding just the right epicycles — a thousand in number, Dr. Ginnobili attested — were missing. And to good effect. Any explanation would have been fatal to the sheer panache of Dr. Ginnobili's cyclic capriccio. He trusted that, by the sweet by-and-by, YouTube would serve up some sod or two who could sacrifice brio for technical prosaics and thereby make good with actual epicyclic computations. Indeed, over the sweet by-and-by YouTube did not disappoint. Improvements in compression resolutions and a lifting of play time constraints have given rise to a number of longer running epicyclic hits. These, unkinked, lay bare circular paths that are bashed a bit. There are even YouTubers who have taken up in fair dinkum ways the lot of explaining epicyclic computations: |
1. | Grant Sanderson ( 3Blue1Brown ): But what is the Fourier Transform? A visual introduction. and its companion But what is a Fourier series? From heat flow to drawing with circles. |
2. | Burkard Polster ( Mathologer ): Epicycles, complex Fourier series and Homer Simpson's orbit, uses Santiago Ginnobili's epicyclic rendering of Homer Simpson as a jumping off point as to what may be going on with all of those circles. |
3. | Daniel Shiffman ( Coding Train ) runs through a JavaScript implementation. |
1. | Digitize the path: obtain discrete samples at (presumably) equal time steps. |
2. | Through the discrete forward Fourier transform, find the frequency domain image of the path these samples trace. |
3. | Compute the delta wheelies through some pair-wise differencing scheme of spectral coefficients. |
4. | Animate the delta wheelie chain and the plotting of the arabesque. The animations mesh automagically. Take your bow; bask in the applause. People think you've done something magical, but, recalling Heinlein's Lazarus Long, you know it to be just engineering. |
15. Selection order for alternate clockwise and counterclockwise wheelies, large to small magnitude (usually...) |
Line | Remark |
1-11 | Argument management: The image selected by the command sets some of the non-dynamic characteristics of the animation; frame dimensions and number of color channels. One could easily select an image with imagery. Such would then form an unanimated "backplate." In classic cel animation, the backplate is furthest from the camera, occupies the bottom of the cel stack and is retained from shot to shot. The upper cells carry the animation, being mostly transparent and swapped with successor cels from shot to shot. The command's first argument is a spectral representation of a plot path; note that it is expected to be arrayed along the image y axis. This is a convention born of convenience, as a common provider of such plot paths, input_csv stacks the path along the y image dimension. -permute[xoriented] yxzc can appropriately rearrange spectral representations stacked along the x image dimension. The second argument sets the number of frames to be rendered for the animation. How long that may be in seconds depends upon what frame rate may be given to some output renderer. If that output renderer is told to make an animation at 30 frames a second, then 300 frames will provide for a ten second animation. The third passband argument are for those who find joy in sloppy tracing effects. Technically, it deletes all but the given number positive and negative coefficients; a passband of 30 Hertz elides all but thirty counterclockwise and thirty clockwise coefficients, retaining sixty coefficients all told. In practice, this removes the high frequency components from the path. Barring a path initially comprised only of low frequencies, this gives rise to an animation that rounds off sharp corners and the like, as sharp corners contain high frequency components — for such is what makes corners sharp. |
18-19 | Centering the arabesque: We adjust the spectral domain image by eliminating the f=0 spectral coefficient. A strict fellow may rightfully carp that this destroys data. That is true, but we are in the service of Art, where compositional adjustments are common. This adjustment removes any offset phasor of the arabesque from the origin so that the spatial conversion of this spectral data by ifft centers the image on the origin. That is, the deletion of the f=0 coefficient in the spectral domain gives rise to the average position of zero for all plots in the spatial domain. |
24-25 | Dynamic Array Coefficient Cache: We establish a dynamic array container image to hold harvested non-zero cardinal sum coefficients. We start with an empty image. Dynamic array internals will set image dimensions, as needed, when vectors of particular dimensions are pushed onto the stack. See 41-64 commentary. |
41-64 | Finding Cardinal Sum Coefficients: We harness a math expression to set the command line variable wfound to the number of wheelies found; that is, the number of non-zero coefficients of the cardinal sum. A spectral image may be predominantly populated with zero coefficients, so we set out to find and cache — in a dynamic array — only the non-zero ones. Whilst we are busying ourselves with this coefficient filtering, we also order found items in a particular permutation, that of alternating positive and negative coefficients while proceeding from low to high rotational rates, this as described in the text. When we plot the delta wheelie chain in the animation loop, 138-144, 178,187, the slowly rotating, large radii delta wheelies will be toward the root end of the chain, with the more rapidly rotating deltas near the tip. This plotting convention appears in most epicycle (nèe "delta wheelie") renderings. Also, whilst we are at it, we represent coeffcients in polar form, storing the same as dynamic array vectors of the form: [+radius, ±orientation, ±rotational_rate] for each non-zero coefficient. This layout is in the service of our animation loop, to wit: animate the orientation angles of the cardinal sum coefficients by adding "smidgen angles," — delta angles derived from the rotational rate. Incrementing by smidgen angles becomes straightforward addition when the coefficients are in polar form. See 81-91, 138-146 and 209. |
81-91 | Tomb-Raiding the Dynamic Array: With the number of coefficients ascertained (wfound is positive), we deconstruct the image underlying the dynamic array, turning it into a "coefficient vector" and a "rotor vector". We: (1) trim away dynamic array administrative fields by means of crop; see 81. (2) by means of shift and the Dirichlet boundary rule, we introduce a dummy constant coefficient term, r=0, ∠=0°, to the cardinal sum. This doesn't change the sum — it is effectively zero — but in the course of converting cardinal sum coefficients to wheelie chain hinge points, this dummy term becomes the very first plot point of the chain. See Culminate Coefficients: 138-144 As a first step in finding "smidgen angles", we split out channel three, ±rotational_rate, from the coefficient vector, making a proto "rotor vector"; see 87-91. The fill mathematical expression applied to the rotor vector accomplishes two tasks: (1) It implements smidgen angle conversion: (2π × f) ÷ framecount for f, the rotational rate of each vector, scaling the vector elements by 2π, then dividing by the frame count. This leaves the rotor vector holding "smidgen angles," by which the animation loop advances the orientations of cardinal sum terms. It also (2) injects an additional, zeroed-out, channel into the rotor vector, converting its elements into [r=0,∠Δθ] polar values. Such values may be added to the coefficient vector in a straightforward manner without altering the radius component; see 209. The means of fabricating such a zeroed-out channel stems from a side-effect of the pixel accessor function i(#ind,_x,_y,_z,_c,_interpolation,_boundary_conditions). This pixel accessor invocation: i(0,y,0,1) references a channel that does not initially exist in rotor vector. Recall that the split of proto rotors from the coefficient vector produced single channel elements, these just rotational rates. In the absence of an explicitly specified boundary_condition, G'MIC creates an out-of-bounds channel and uses the default Dirichlet boundary rule; off-image pixels are set to constant black. |
97-121 | Plot a Back Plate Image: We create and store a back plate image. It portrays the spatial plot points encoded in the Argument 1 [spectral] image, this so that the user might compare the wheelie-generated trace with the actual image. We recover the plot points of the actual image by means of the inverse, discrete time Fourier transform of [spectral], ifft; this brings about a set of plot points realizing an image plotted on the complex plane. To render these plot points, we harness the mathematical expression function polygon(). Particulars follow closely on that of rendering cardinal sums in From Wheelies, Arabesques; the transform of plot points into pixel image space, suitable for consumption by polygon() takes place through an affine transform. See the anim_simple.gmic annotations for how this transform to screen space comes about. Once polygon() has rendered the plot points on the back plate, we employ the store command to stash the back plate off the image list and into a named storage variable, canvastemplate. In the animation loop, we restore this image via input at the start of each frame, upon which we further render wheelie chains and tip tracery; see the Animation loop... 150. |
124-129 | Tracing the Wheelie Chain Tip: Before entering the animation loop, we set up storage to retain plot points of the wheelie tip's per-frame travels. Accumulating coefficients at the top of the animation loop, 138-144 expediently finds all the wheelie chain hinge points; the last hinge point corresponds to the chain's tip. On each frame, we add that tip plot to [trace] for subsequent rendering; see 176-185. This overlay trace rendering allows a visual comparison between the wheelie chain drawing and the benchmark arabesque scribed on the back plate. See 97-121. |
131-210 | Animation Loop: In the animation loop, we: (1) culminate coefficients, creating wheelie chain hinge points; see 138-144 (2) update the trace buffer with the current tip position for this frame; see 149 (3) Fetch a copy of the back plate from off image list storage; see 150 (4) with sufficient trace points accumulated, render the wheelie chain tip trace from plot points accumulated from previous frames; see 153-171 (5) render "hinges" at the joins of the wheelie chain; see 189-202 (6) Increment coefficient orientations by rotor vector angles; see 209 and 81-91. |
138-144 | Culminate Coefficients: A very expedient way to convert a cardinal sum into a wheelie chain is to just accumulate the coefficients into a progressive sum by means of G'MIC's cumulate command; see 141. Each intermediary sum locates a hinge point between successive delta wheelies; rendering line segments from hinge point to hinge point visualizes the delta wheelie chain itself. We actually animate the cardinal sum by adding pre-computed "smidgen angles" to the constant angular component of each cardinal sum term; these "smidgen angles" are scaled from the rotational rate of each term; see Tomb-Raiding the Dynamic Array, 81-91. For each frame in the animation loop we step the orientations of each cardinal sum term by a "smidgin angle", see 209, culminate the adjusted cardinal sum terms, then render the trace, chain, and hinge markers. The culmination method here follows from the distinction between delta wheelies and cardinal sum terms; the line segment between hinges elucidates a pairwise difference between successive cardinal sum terms and is the delta wheelie "connecting" the paired terms. The [dwchain] image houses the hinge plot points. As noted, these are regenerated at the top of the animation loop, once every frame, after stepping the orientations. |
149-170 | Mirroring Paths for Fun and Profit: The last hinge point corresponds to the wheelie chain's tip. For each frame, we copy this last hinge point from the coefficient array to the end of the trace buffer. If more than one trace point has accumulated, we render the entire point set via polygon(). The two crop commands, 156-157, may mystify. When given k plot points to render the connecting k–1 line segments, polygon() automatically closes the loop with a k th additional segment, this running from point k–1 to 0: the last to the first point. Strictly speaking, that is exactly what polygon() has been designed to do: make closed polygons. That being so, if one should still wish for (insist on?) "open" polygons, sans closing segments, then the textbook recommendation suggests calling polygon() k–1 times for k plots, passing it each time successive pairs of plot points. That works, but is not an optimal approach. It is quite a bit faster to call polygon() — once — with, say, 2×k plots than k–1 times with point pairs, as that doubled data set still bypasses much overhead from polygon() setup-and-teardown. The doubled data set, 2×k, arises from mirroring the path, so as to first walk forward along the unmirrored plots, and then trace steps backward along the mirrored plots. polygon() still closes the polygon, but since the first and last points in the data set coincide, there is no visual effect and we seem to have an open polygon. This doubled data set, with one half the plot points the mirror of the other half, furnishes a means of overcoming a polygon() shortcoming: out of the box, it draws single pixel wide lines. With a mirrored dataset, one can displace the outbound half in one direction along miter lines and the other half in the opposite direction; a supplied width parameter controls centerline displacement. There are details: the size of the bisecting angle between successive line segments affects the amount of displacement; it is necessary to scale the furnished width as a function of the bisecting angle. Look for particulars in an upcoming cheatsheet on variable-width line plotting with polygon(). The second crop at line 157 mirrors the data set; how this is so may not be immediately clear. The command trims images, to be sure, but not just to make smaller images. One may also crop from smaller to larger dimensions, and in that case, boundary policies come into play. In the present case, we crop a column vector image with height h to a double-height image: 2×h. G'MIC needs to know how to fill the new pixels: the mirror boundary policy, selected by the last crop argument of 3, instructs G'MIC to fill the new space with a mirror of the old. We get a walk-back data set in one fell swoop. |
174-204 | Annotating the Frame: From this point forward, we embark upon a succession polyon()or ellipse() -based plottings to annotate the back plate with the dynamic elements of the animation. At 150, the top of the animation loop, we input the named image variable $canvastemplate, introducing a copy of the animation back plate drawn at 97-121; see the article Plot a Back Plate Image and the store command. With a fresh copy of the back plate, we harness polygon()to render the current wheelie chain trace, 162-171, and the wheelie chain proper, 176-185. we employ ellipse() to dot the hinge plot points. These three drawing routines all employ the hinge plot points inhabiting the [dwchain] vector image computed at the top of the animation; see 138-144, Culminate Coefficients. We name the backplate frame to enroll the image into the animation sequence. After we have exited the animation loop, 215, we invoke remove [^frame] to purge the image list of everything but animation frames. |
209 | Stepping the Animation: Adding the [rotors] and [coefficients] column vector images steps the orientation angles of the cardinal sum, adding the celebrated "smidgen angles" 2πfΔt to each term. See the run-up to animation loop, 81-91, Tomb-Raiding the Dynamic Array , for the setup behind this addition. |
Argument # | Notes |
1. | A 1D spectral domain image, 1×2n×1×2 dimensions, encoding the line drawing and from which the animated wheelie chain derives. ½ the height of the spectral image, n, reflects the Nyquist Sampling Rate of the spectral data set. This spectral image typically comes from a discrete time, forward Fourier transform fft of 2D path plots; these are taken to be [real, imaginary] position vector coordinates plotting the line drawing on the complex plane. |
2. | A positive integer, sets the number of constituent frames in the animation and defaults to 300. Such a number accounts for about thirty seconds at ≈29.97 frames per second. |
3. | A positive integer, sets the low frequency passband width in Hertz; its useful range is 1 ⇒ n–1. Lower passband settings supress high frequency content in the line drawing's path, giving rise to low-resolution, "loopy" paths. In this example, the math expression {(h#$ampersand/2)} sets the bandpass argument to one half the height of the spectral image, 1024, the Nyquist Sampling rate. This essentially admits all frequencies. An argument of 16 admits only the lowest 16 positive and negative rotation rates, 32 samples for a low-resolution, "loopy" path. |
Selection | The selected image establishes the format of animation. It serves as a backplate for a series of Porter/Duff "over" compositing operations. Backplate imagery persists, unanimated, throughout the run. Draw on it what you may, but be aware that a preimage of the path goes on the backplate as well. |
Output | The command places on the G'MIC image list a series of animation frames tagged with the name frame. |
Passband | Results | Passband | Results |
128 | 32 | ||
16 | 8 | ||
4 | 2 |
Previous: | From Arabesques, Wheelies |
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.