Still looking…
Ah! September! Once again, the days are brisk, as Western Atlantic high pressure anticyclones sputter, shrink and shift eastward, lessening Gulf stream moisture and letting the dew points drop. Still, those ill-tempered anticyclones may yet lob a hurricane or two at the Eastern North American Seaboard, my home.
Hesitantly, I opened the documentation gates on a new sort of tutorial: a cheat sheet. As I noted in Post 55, I have been given much thought to how rank beginners get through the very basic learning curve of the G’MIC command tool. Trusting that they are inquisitive, they cut-and-paste something from somewhere, often a fx_
<something_or_other> command that underlies a gmic_qt
filter and try and make it go. They are Mickey Mouse in Fantasia’s “The Sorcerer’s Apprentice”. They got a command. Maybe they don’t entirely know how to read it, but — by golly! — if the spell makes the broom carry the water, go for it! You know the rest.
In time, I decided that G’MIC is very sensitive to typing errors and beginners are insensitive to G’MIC’s sensitivities. There are really, really simple maladies which plague the beginner. Spaces after commas in argument lists, An unquestioning acceptance of whatever -display
shows, the troublesome discovery that -output
is on their watch; one has to know how to feed it. So cheat sheets are problem-oriented – perhaps catastrophe-oriented – shorts with a TL;DR for the “Too long; didn’t read.” clientele and a more lengthly précis for those not minding a held hand. I try to be brief; from my point of view, a cheat sheet is a dispatch tool to more lengthly, more developed material. So there are a lot of links. TL;DR is the quick fix; the précis for a little bit of theory; throughout, links to more developed material. I’ve still not quite got the form in hand. The cheat sheet on debugging pipelines, finished in this hour, was unsatisfactory in its length, but it ventured into realms where there is little backing material to which one may link.
That gives me three forms now: catastrophe-oriented cheats, the first of which saw the light of day on August 22nd:
- The cheatsheet index
- The custom command cheat
- The display command lies cheat
- The fourth aforementioned debugging cheat is just now rattling down the distribution pipeline.
The second form are command-oriented tutorials, which are rather a bottom-up way to the topic at hand, starting with a tool and ending with what may done with that tool. As always, there is still much to be done in this realm, but not much was done with command tutorials this month, preoccupied as I was with the mechanics of cheat sheets.
The third form are the situation-oriented Cookbooks, which are rather a top-down way to the topic at hand, starting with a visual and ending with an G’MIC implementation which serves the design. They tend to go to length and generally spring from an interest I already have. The motile sprites which infested Post 66 stem from a heart-felt fascination with how elementary, grade-school mathematical relations can give rise to complex behaviors. Part of the behavior arises from the particular origin of the acceleration field, which the sprites sample for a velocity change and direction: a spectral space consisting of a tiny population of low-frequency coefficients is transformed temporally, giving rise to gradual sinusoidial-like fields. That has a signal effect on sprite behavior, which would be significantly different had the acceleration field been generated through -plasma
or -turbulence.
Freighting Fourier Transforms over the transom may be the most difficult pitch, but past that, the sampling behavior of the sprites is easy to explain. To better elucidate the connection between the sprite’s behavior and the point-wise acceleration fields, I color-coded the vectors comprising the point-wise acceleration field in the usual way: red → 0°, cyan → 180° and so forth, so that as a sprite traverses a red region, its orientation shifts from left to right — but not instantaneously, of course. It’s latent velocity, and the orientation of that vector, is still manifest and only dissipates over time from artifically introduced friction. For those who like toys, an updated seekbetter
script, listed following, may be swiped. The corresponding cookbook article is brewing now.
seekbetter.gmic
#@cli seekbetter : _width(512)>64,_height(512)>64,_depth(128)>64,_sprite_count(128)>0,0<=_acceleration_attenuation(0.1)<=100,0<=_velocity_dampening(0.95)<=100,0<_slice_per_ticktok(50)<=100,1<=_spectral_turbulence(50)<=100,0<=_backblend(0)<=100,start_in_recording_mode:0
#@cli : Generate a "Seeking Better Home" animation. All arguments are optional
#@cli : and default to reasonable values. § Sprites sample acceleration from
#@cli : fields of pointwise acceleration vectors – see
#@cli : _acceleration_attenuation. Per frame, acceleration → delta velocity,
#@cli : which is vectorially added to sprite's current velocity after that has
#@cli : been dampened by _velocity_decay, a kind generalized friction. § The
#@cli : 'Better Homes' which sprites seek arise from two circumstances. (1)
#@cli : Sprites find 'positional stability' in wells of low acceleration. If
#@cli : they move into such environs at low velocity and the acceleration
#@cli : vectors available from the environment are nil or nearly so, then
#@cli : sprites settle in, their velocities dampen to zero, and, thus, might
#@cli : never leave. (2) Sprites find a dynamic stability from periodic
#@cli : accelerations just so timed and directed to keep the sprites
#@cli : travelling along specific paths. These may be loops around wells of
#@cli : low acceleration, where the surrounding acceleration vectors happen to
#@cli : array themselves in circular patterns. These may also be paths aligned
#@cli : along torii latitudes and longitudes, where acceleration vectors on
#@cli : both sides of the line point more-or-less in the same direction. §
#@cli : Both flavors of stability are transient. The field of pointwise
#@cli : acceleration vectors changes once per tiktok. Each field slice is
#@cli : drawn from a volume generated from spectral noise; the inverse Fourier
#@cli : transform of that noise generates repeating temporal acceleration
#@cli : vector fields that are periodic in x, y and z. Paging through this
#@cli : volume along z gives rise to gradual, periodically changing
#@cli : acceleration vector fields. Whatever form they may take, the
#@cli : acceleration vector fields underlying both forms of stability change,
#@cli : inducing the sprites to find newer, more congenial configurations.
#@cli :
#@cli : 1 → acceleration vector field width (pixels)
#@cli : 2 → " " " height(pixels)
#@cli : 3 → " " " depth (number of slices in field)
#@cli : 4 → sprite count: [ 1 → ∞ ]
#@cli : 5 → acceler. attenuation: 0.1: little attenuation (frantic animation)
#@cli : → 100: substantial attenuation (languid animation)
#@cli : 6 → Velocity dampening: 0.1: little dampening
#@cli : → 100: completely dampened velocity
#@cli : 7 → frames per tikok: frame paging rate, relative to field depth ($3).
#@cli : 50 → 50% of depth used as frames per tiktok
#@cli : 8 → Spectral turbulence: 1: Low frequency background gradient.
#@cli : ∞: High frequency background gradient.
#@cli : 9 → Blend background: 0: Spectral field;
#@cli : 100: Solid blue-gray;
#@cli : 50: 50-50 blend
#@cli : 10 → Recording mode: False(0). If true, animation starts
#@cli : in recording mode. 's' on keyboard
#@cli : stops recording.
#@cli : Otherwise 'r' starts recording.
#@cli : Keyboard: ESC: stop animation.
#@cli : c: Toggle color mode.
#@cli : r: start recording.
#@cli : s: stop recording
#@cli : $1 seekbetter 256,256,512,2048 - 256×256×512 pixel field populated with 2,048 sprites.
seekbetter : -check "isnum(${1=512}) && ${1}>=64 && "\
"isnum(${2=512}) && ${2}>=64 && "\
"isnum(${3=128}) && ${3}>=64 && "\
"isint(${4=128}) && ${4}>0 && "\
"isnum(${5=0.10}) && ${5}>=0 && ${5}<=100 && "\
"isnum(${6=0.95}) && ${6}>=0 && ${6}<=100 && "\
"isnum(${7=50}) && ${7}>0 && ${7}<=100 && "\
"isnum(${8=50}) && ${8}>=1 && "\
"isnum(${9=0}) && ${9}>=0 && ${9}<=100 && "\
"isbool(${10=0})"
# Set up parameters
wid=$1 # Field width: pixels 128 minimum
hgh=$2 # Field height: pixels 128 minimum
dph=$3 # field depth: pixels: 64 minimum
cnt=$4 # Sprite count: count of swimming thingies. Positive integral count
mul={exp(-$5)} # Acceleration attenuation: 1 → 100. 1: fully attenuated. → 100: Unattenuated
dcy={exp(-$6)} # Velocity decay: 0 → 100 0: Fully decayed. → 100 No decay
tsz={round($dph*$7/100,1,0)} # Relative frame count per slice: 32 → Page through frames 32% of field depth for each tiktok.
tub={$8/100} # Turbulence: 1 → ∞. Higher: more turbulent landscape. Peaks and crannies galore!
bkb={$9/100} # Background blend: 0 shows acceleration dynamics in background. 100: dark blue-gray
rec=$10 # If set (1), start in recording mode (Pre-presses 'r'). 's' stops recording.
# Otherwise a linear interpolation, with 50 representing a 50-50 blend.
# Announcement
-echo[^-1] "Starting a Seeking Better Home animation.\nField dimensions: "$wid"×"$hgh"×"$dph";\n"\
"Sprite count (>0): "$cnt";\nRelative Acceleration Attenuation (0→10): "$mul";\n"\
"Velocity Dampening (0→10): "$dcy";\nFrames per Ticktok (rel. to depth): "$tsz";\n"\
"Background Turbulence (>=0.01): "$tub";\nBackground Fade (0→1): "$bkb"\n"
# Generate data structures and viewport
tiktok=0
oc=0
clr=0
-input $cnt,1,1,4
-name. sprites
-mksprites[sprites] $wid,$hgh
-mktorii $wid,$hgh,$dph,10,$tub
-name. torii
-input $wid,$hgh,1,3
-name. viewport
# Outer tiktok loop. Page through the torus slices
-do
# Set the viewport background
-if {$clr==0}
-fill[viewport] "[rad2deg(atan2(i(#-2,x,y,$tiktok,1),i(#-2,x,y,$tiktok,0)))+180,0.875,norm2(I(#-2,x,y,$tiktok))]"
-shared[viewport] 100%
-normalize. 0,1
-remove.
-hsv2rgb[viewport]
-else
-fill[viewport] "[i(#-2,x,y,$tiktok,0),i(#-2,x,y,$tiktok,1),0]"
-norm[viewport]
-normalize[viewport] 0,255
-map[viewport] tarn
-fi
-if $bkb
-fill[viewport] "*lerp(I(#-1),[5,30,50],"$bkb")"
-fi
+store[viewport] topography
# Intraslice loop. tsz → tick size → number of frames before
# advancing one torroidal slice.
-repeat $tsz
# Events:
# Quit request?
-if {*}" && "{*,ESC}
-echo[] "Quitting..."
-quit
# Start recording frames?
-elif {*}" && "{*,R}" && "$rec==0
-echo[] "Starting Recording in seek directory..."
rec=1
# Cease recording frames?
-elif {*}" && "{*,S}" && "$rec==1
-echo[] "Stopping recording at "$oc" frames."
rec=0
# Color toggle?
-elif {*}" && "{*,C}
-echo[] "Viewport color toggled."
clr={!$clr}
-fi
# Paint the sprites
-drawsprites. [-3],9,4,0.375
# Step the dynamics
-step[sprites] [-2],$tiktok,$mul,$dcy
-toruswrap[sprites] 0,0,$wid,$hgh
# Paint ticktok
-text_outline. $tiktok,0.01~,0.01~,5%,1,1
# Pause animation
-window[viewport] $wid,$hgh,0,0,0,0,"Seeking a Better Home"
-wait -10
-if $rec
-if !isdir('seek')
-exec "mkdir seek"
-if ${}
-error "Could not create output directory 'seek'; disabling recording."
rec=0
oc=0
-fi
-fi
-outputn[viewport] "seek/seekbetter.png",$oc
oc+=1
-fi
# Refresh background graphic
-input $topography
-name. topography
-fill[viewport] I(#-1)
-remove.
-done
-if $tiktok<$dph-1
tiktok+=1
-else
tiktok=0
-fi
-while {*}" && "!{*,ESC}
mksprites : -check "isnum(${1=128}) && ${1}>=128 && isnum(${2=128}) && ${2}>=128 && isnum(${3=0}) && ${3}>=0"
-if s==4
sw=$1 # Range Width
sh=$2 # Range Height
sm=$3 # Acceleration, pixels/sec²
-fill. u([$sw,$sh,$sm,$sm])-[0,0,$sm/2,$sm/2]
-round. 1
-else
-error "Channel count="{s}" is unexpected. 4 channels needed for sprites."
-fi
mktorii : -check "isnum(${1=128}) && ${1}>=64 && "\
"isnum(${2=128}) && ${2}>=64 && "\
"isnum(${3=128}) && ${3}>=64 && "\
"isnum(${4=1}) && ${4}>0 && "\
"isnum(${5=0.5}) && ${5}>0 && "\
"isint(${6=0}) && ${6}>=0"
# Parameter filtering
wid=$1 # Field width in pixels (x)
hgh=$2 # Field height in pixels (y)
dph=$3 # Field depth in slices/pixels (z)
acc=$4 # Peak to average relative distance. Higher → more active sprites
tub=$5 # Turbulence (literally, spread of Gaussian noise in spectral space.
# Higher → more spread → more high frequency components → more temporal turbulence
# Spectral noise
-input $wid,$hgh,$dph,2,1
-noise. 100,0
-name. snoise
# Shape snoise into an approximate Gaussian distribution
-fill[snoise] whd*i*exp(-norm([x-(w/2)+1,y-(h/2)+1,z-(d/2)+1])/$tub)
-name. specspace
# Locate noise kernel in the center of spectral space
# Set frequency 0 ('DC') spectral point to zero
-shift[specspace] {-(($wid/2)-1)},{-(($hgh/2)-1)},{-(($dph/2)-1)},0,2,0
-set. 0,0,0,0,0
-set. 0,0,0,0,1
-split. c
# Inverse Fourier Transform into temporal space
-ifft[-2,-1]
-append[-2,-1] c
-mul. {abs(im)>iM?$acc/im:$acc/iM}
step : -check ${"is_image_arg $1"}" && isint(${2=0}) && ${2}>=0 && isnum(${3=1}) && ${3}>=0 && isnum(${4=1}) && ${4}>=0"
stp=$2
mul=$3
dcy=$4
-pass$1 1
-fill.. ">
dyn=I;
acc=I(#1,dyn[0],dyn[1],"$stp");
dyn[2]*="$dcy";
dyn[3]*="$dcy";
dyn[2]-="$mul"*acc[0];
dyn[3]-="$mul"*acc[1];
dyn[0]+=dyn[2];
dyn[1]+=dyn[3];
dyn
"
-remove.
toruswrap : -check "isnum(${1}) && isnum(${2}) && isnum(${3}) && isnum(${4})"
lx=$1 # Lower bounds, x
ly=$2 # Lower bounds, y
dx=$3 # Width, x
dy=$4 # Height, y
fill. ">
dI=[
i(x,0,0,0)<"$lx"?"$dx":i(x,0,0,0)>=("$lx"+"$dx")?-"$dx":0,
i(x,0,0,1)<"$ly"?"$dy":i(x,0,0,1)>=("$ly"+"$dy")?-"$dy":0,
0,
0
];
I+dI
"
drawsprites : -check ${"is_image_arg $1"}" && ${2=4} && ${2}>=4 && ${3=6} && ${3}>=4 && isnum(${4=0.5}) && ${4}>=0 && ${4}<=1"
-pass$1 1 # 1D image, vector of sprites. Selected image is the canvas
ra=$2 # a radius of ellipse
rb=$3 # b radius of ellipse
op=$4 # opacity of gray fill. Outline fully opaque
-eval. ">
ang=atan2(i(x,0,0,3),i(x,0,0,2));
ellipse(#0,i(x,0,0,0),i(x,0,0,1),"$ra","$rb",ang,"$op",[80,180,255]);
ellipse(#0,i(x,0,0,0),i(x,0,0,1),-"$ra",-"$rb",ang,1,0xffffffff,[255,255,255]);I"
rm.