Geometric transformations
Basic transformations
Punctual have two sets of functions for making geometric transformations. The first set are functions that are meant for a specific transformation, while the second set are functions that allow to specify any formula. In all cases, the result is achieved by remapping fx
and fy
.
In the first set, there are four functions:
tile
tile [x,y] ...
: repeats the patternx
times across the x axis andy
times across the y axis. If only one number is specified, it’s applied to both axis. If a negative number is specified, the corresponding coordinate is flipped.
tile [8,4] $ iline [0.3,0.5] [-0.6,-0.8] 0.02 >> add;
Note how the visible pattern in the screen is rescaled and repeated: lines get thiner and are limited to the length of the original pattern.
move
move [x,y] ...
: translates the pattern by the specified amount in each axis. If only one number is specified, it’s applied to both axis.
move [0.3, -0.4] $ circle 0 0.1 >> add;
spin
spin amount ...
: rotates the pattern by the specified amount. Here, 1 is half a revolution, andspin
amount acts in a clockwise direction (in opposition to degrees or radiants), hence 0.5 is -90º, -0.25 is 45º and so on.
spin (-0.25) $ line 0 [1,0] 0.001 >> add;
To convert from radiants (α
) to the unit used by spin
(s
), use the formula: s=-α/π
. The reverse is α=-s*π
.
zoom
zoom [x,y] ...
: zooms in byx
across the x axis and byy
across the y axis. If only one number is specified, it applied to both axis. Numbers greater than 1 make the pattern bigger (like looking from a shorter distance), and less than 1 make the pattern smaller (like if we were moving away from the screen). If a negative number is specified, the corresponding coordinate is flipped.
zoom 0.2 $ circle 0 0.1 >> add;
Basic transformations examples
Things get interesting when applying some of these ideas to the transformations:
spin (0.8*osc 0.013*osc 0.15) $ tile [3 ~~ 8 $ osc 0.13] $ circle [0,0.5] (unipolar $ tri 2) >> add;
0.9 * fb fxy >> add;
(fit 1 $ mono $ zoom [0.8, 0.8, 1.4] $ move [0.2,0.2,0.1] $ spin (saw [0.03,-0.02,0.04]) $ tile [0,8] $ hline 0 0.03) * [0.8, 0.4, 0] >> add;
- Transformations that are different for each fragment by using some of the fragment coordinates.
fit 1 $ mono $ spin (saw [-0.06,0.034]) $ spin (6*ft/(2*pi)) $ tile [6+15*(unipolar $ osc 0.07),1] $ vline 0 0.03 >> add;
l << line 0 [1,0] 0.01 >> add;
spin ft l >> red;
This example deserves an explanation. How is it possible that the white line turns into 4 lines when applying spin ft
?
When we say that spin
(or any other transformation) rotates the specified graph we are making a simplification. In reality, each fragment computes its own result. Here, for each fragment, the inverse rotation is computed using its own ft
, and the ones that after being rotated get near enough to the line are painted red.
Note that when using a fixed number (like spin 0.3
), both ways of thinking lead to the same result. It’s the same to say a line is rotated by 0.3, than saying that all fragments that when rotated by -0.3 are in the line are white. But the latter one is far more confusing and difficult to say.
So, let’s try to explain the 4 red lines. We need to find for which ft
, when applying a rotation of -ft
the result is 0. Note that ft
is computed in radiants, but spin
uses its own unit, explained above.
The answer is that this will happen for any ft
that’s equal in radiants than in the spin unit. Note that the obvious answer is ft=0
(because 0 is the solution of the ft=-ft/pi
equation). But there are other possibilities, because adding any multiple of 2 to the spin is the same angle, and it’s valid as long the result is the range -π to π.
Take for example ft=-ft/pi+2
. Group the ft
: ft+ft/pi=2
; multiply by π and take the common factor: ft*(pi+y1)=2*pi
; finally, isolate ft
: ft=2*pi/(pi+1)
. Then, ft ~= 1.517
. You can check that spin 1.517 $ line 0 [1,0] 0.01 >> add
is the same as one of the four red lines.
Following the same argument, the other three angles are the solution of ft=-ft/pi-2
(-1.517), ft=-ft/pi+4
(3.034), and ft=-ft/pi-4
(-3.034).
The next examples combine some of the previous ideas:
(fit 1 $ mono $ spin [fr*8, (-1)*fr*8] $ hline (tri $ 0.35 / [5,7,9]) 0.1) * [fr/3,0,unipolar $ osc 0.05] >> add;
In this first example, we start with three horizontal lines that move vertically at different speeds. By using spin
with fr
, we bend the lines, and, as we used a two-channel graph inside spin, the result are 6 curves that moves by groups of three in a symmetrical way. Lastly, we apply color: first we crunch all channels together with mono
and then multiply the result by the color. Note how the red component depends on the fragment’s radius, and the blue component varies through time.
c << move [tri [-0.13, 0.15], tri [-0.15, 0.16]] $ circle 0 1.2 - circle 0 1;
fit 1 $ spin (fr*0.3) $ spin (ft/pi) $ move [osc fxy * 0.05, osc fxy * 0.09] $ c >> add;
Here, we start by building a circumference by subtracting two circles. Then we provide two oscillators to move
, with two coordinates each one, and so we have four circles moving around the screen. On the second move
, we use oscillators which frequency have two numbers (because fxy
is [fx,fy]
). That means that each fragments is duplicated and moved differently depending on its coordinates, and this creates the blurring effect. Then, spin
is applied twice, using ft
and fr
. The first spin
Remaps
The set of functions setfx
, setfy
and setfxy
allows to transform one or both coordinates using any expression. They are a bit more complex to use than the basic transformations, but very flexible.
setfx
setfx [x...] graph
setfx
remaps the x coordinate of a graph by the given formula.
With simple examples, setfx
is equivalent to move
. These two lines are equivalent:
move [0.2,0] $ vline 0 0.01 >> red;
setfx (fx-0.2) $ vline 0 0.01 >> green;
Moving the line by 0.2 to the right is equivalent than taking each fragment and painting it in the color found at 0.2 to the left.
But these remapping functions are a lot more flexible. In the next example, each fragment x coordinate is multiplied by a number between -1 and 1 that depends on its distance to the origin. This operation completely deforms the image creating an interesting pattern:
l << tile [8,1] $ vline 0 0.05;
setfx (fx*sin' (fr*10)) l >> add;
The formula to calculate x don’t even need to include the original x coordinate. This is the same example than before, switching fx
by fy
:
l << tile [8,1] $ vline 0 0.05;
setfx (fy*sin' (fr*10)) l >> add;
If there is more than one element in the list, each one creates different channels, so the resulting graph has as many channels as the product of the number of channels in graph by the number of channels in the list.
In this example, the final graph has a total of 6 channels (3*2) that are mapped to a 4-channel output:
c << tile [8,4] $ circle 0 0.8 * [0.8, 0, 0.5];
setfx [fx+fx%0.1, fx*sin' (fy*8)] c >> blend;
setfy
setfy [y...] graph
setfy
is exactly the same function than setfx
, but it modifies the y coordinate instead of the x one.
Here, setfy
is used to evolve a color pattern, creating a symmetric repetition across the vertical axis:
c << [unipolar fx, fr*0.4, 1-abs fx, 0.8];
setfy [sin' (fy*10)] c >> blend;
c << fit 1 $ between [0.5+2*px, 0.5-2*px] fr;
mono $ setfy (fy+[0.1,0.2]*osc (fx*0.2)*(sin' $ fx*20)) c >> add;
setfxy
setfxy [x,y...] graph
setfxy
remaps both the x and the y coordinates of a graph. move
, spin
, zoom
and tile
can be rewritten using only setfxy
, but setfxy
is more flexible, allowing the use of any other mathematical formula.
In this example, each fragment is slightly displaced, and the displacement varies through time at a speed that depends on the fragment’s coordinates. This results in the blurring of the circle outline:
setfxy (fxy +: (osc fxy * 0.05)) $ circle 0 0.5 >> add;
This code is equivalent to move (osc fxy * (-0.05)) $ circle 0 0.5 >> add;
.
In the following example, a circle c
is defined with coordinates (x, y)
and radius r
. However, x
, y
and r
are determined using distances from moving points, creating a highly distorted and dynamic shape.
To grasp this concept better, let’s start with a simplified code:
x << dist [0.2,0.2];
circle [x,0] 0.2 >> add;
Here, for each fragment (fx, fy)
, the distance x
between the fragment and (0.2, 0.2)
is computed. Then, the fragment is painted white if the distance between the fragment and the point (x, 0)
is less than 0.2.
In the final example, x
is calculated in this manner but with a moving point. y
is similar, but it uses the “proximity” instead of distance. Additionally, r
is also dynamic and computed similarly to x
.
The computed values of x
, y
, or r
for each fragment can be visualized by sending them to the output.
The resulting circle can be visualized with this code:
x << dist $ osc [0.3, 0.2];
y << prox $ osc [0.13, 0.1];
r << dist $ osc [0.11, 0.19];
circle [x, y] r >> add;
The final shape is built by applying the transformation setfxy (abs fxy)
to the previous circle.
To understand the effect of this transformation, let’s study this simplified code:
c << circle [0.5,0.2] 0.02;
setfxy (abs fxy) c >> add;
Here, the absolute value of fx
and fy
is computed. If the result is near enough to (0.5, 0.2)
, the fragment is white; otherwise, it is black. This results in a circle in the first quadrant (both coordinates positive) being duplicated in each of the other quadrants. Note that if the circle has any negative coordinate, the result is a black screen, as there exists no number that results in a negative value when applying abs
.
With this analysis, we can now understand that this setfx
transformation is creating a four-fold symmetry from the content in the first quadrant.
The rest of the example involves details. fit 1
is applied to the resulting shape in order to make fragments square, and mono
is used to put the four copies of the shape in the same channel.
Finally, some color is applied to the shape before sending it to the output, and feedback is used to create the impression that the shape is moving smoothly:
x << dist $ osc [0.3, 0.2];
y << prox $ osc [0.13, 0.1];
r << dist $ osc [0.11, 0.19];
c << circle [x, y] r;
shape << mono $ fit 1 $ setfxy (abs fxy) c;
color << [0, fr/2, 0.5, 0.7];
shape * color >> blend;
gate 0.1 $ 0.98 * fb fxy >> add;
In the following example, setfxy
is employed twice: first to establish a horizontal symmetry and then to transform the pattern into polar coordinates.
The pattern begins by defining x
and y
as two rotating gradients from black to white.
These gradients x
and y
are combined to create a more complex color pattern in co
. This pattern is then transformed into sh1
by applying horizontal symmetry and further converted into polar coordinates, stored in sh2
. sh3
represents a version of sh2
that is moved and rotated.
Finally, the color in sh3
is transformed into the HSV colorspace and sent to the output, with the hue interpreted as red, the saturation as green, and the value as blue, resulting in the final color pattern.
x << unipolar $ fit 1 $ spin (saw (-0.013)) fx;
y << unipolar $ fit 1 $ spin (saw 0.01) fy;
co << 10*([x,y,x+y]%0.08);
sh1 << setfxy [fx, abs fy] co;
sh2 << setfxy frt sh1;
sh3 << move [0,0.45] $ spin (-0.5) sh2;
hsvrgb sh3 >> add;
In the following pattern, setfxy
is utilized to convert to polar coordinates, followed by a simple geometric transformation, and then another application of setfxy
to revert to Cartesian coordinates.
The initial segment of the pattern builds upon one of the examples seen in 10 ways of drawing a circumference of radius 1. The key variation here is the addition of a value to the radius at each point on the circumference. This value corresponds to the intensity of the frequency that aligns with the angle of the point, as defined by the formula in a
.
In a
, the possible angle range is mapped to [0, 0.7]
, representing audible frequencies except the highest. abs ft
is employed to create symmetry between the upper and bottom halves of the circumference. Finally, the ** 3
operation cubes the result, reducing the amount added to the radius.
The subsequent segment of the pattern takes this result, translates it to polar coordinates, duplicates it using tile
, and then reverts the result back into Cartesian coordinates. This technique enhances simple transformations when applied within polar coordinates.
a << (linlin [0,pi] [0,0.7] $ abs ft) ** 3;
b << 1+ifft a;
c << between [b-px,b+px] fr;
d << tile [8,4] $ setfxy frt c;
setfxy (rtxy fxy) d >> add;
0.9*fb fxy >> add;
The last example expands upon the previous one, retaining the initial setup with minor adjustments to the circumference stroke width. Afterward, the circumference is translated into polar coordinates, and color is applied.
The tile
operation undergoes slight modifications, introducing irregularity by incorporating fx
into the calculation determining the number of fragment repetitions. Additionally, a new basic transformation, a constant velocity spin
, is introduced.
Further irregularity is introduced through a move
operation, with one of its values determined by an oscillator driven by the audio frequency. Visualizing f
reveals a series of curved lines that move dynamically across the screen in response to the music.
The subsequent steps involve translating the result back from polar coordinates to Cartesian coordinates and applying a substantial amount of feedback.
a << (linlin [0,pi] [0,0.7] $ abs ft) ** 3;
b << 1+ifft a;
c << between [b-4*px,b+4*px] fr;
d << (setfxy frt c)*hsvrgb [fr,fx,fy];
e << spin (saw 0.1) $ tile [1+3*fx,4] d;
f << move [saw imid, saw 0.035] e;
setfxy (rtxy fxy) f >> add;
0.99*fb fxy >> add;