On Punctual’s coordinate system, and the use of Cartesian and polar coordinates

Punctual let’s you direct access to fragment coordinates, both in Cartesian and polar systems. This feature allows for a lot creative possibilities.

This section is intimately related with Geometric transformations especially the setfx, setfy and setfxy functions. Also, some examples use Oscillators.

Cartesian coordinates

The Cartesian coordinate system is the coordinate system most commonly used, where a point in the plane is identified by its horizontal distance and vertical distance to a reference point called origin. These two values are called coordinates, the horizontal axis is called x-axis, and the vertical one y-axis. See Coordinate system to see the details on how this is implemented in Punctual.

Cartesian coordinate system
K. Bolino, public domain, via Wikimedia Commons

fx, fy, fxy

As explained in fragments, Punctual expressions are evaluated for each fragment. For a given fragment, fx is the x-coordinate of that fragment, fy is the y-coordinate, and fxy is a list with both coordinates.

The next examples are some creative ideas one can explore using only coordinates and basic mathematical functions.

Dividing the screen in 10 vertical gradients:

5 * (fx % 0.2) >> add;

Cartesian coordinates example 1

The modulus fx % 0.2 (see mathematical functions for more information) makes 10 stripes from 0 to 0.2 (10 as fx goes from -1 to 1). Then, we multiply by 5 to rescale the value to a 0 to 1 range.

Color patterns

Here we are using three simple formulas to get each of the three RGB channels directly from the coordinates of each fragment.

-- Try changing abs by unipolar
abs [fx, (-1)*fy, (fx+fy)/2] >> add;

Cartesian coordinates example 2

unipolar $ (-1)*fx++fxy >> add;

Cartesian coordinates example 3

[unipolar $ sin' fx, 1 - abs fy, unipolar $cos fx] >> add;

Cartesian coordinates example 4

There are a lot of formulas that allow you to make cool gradients as a starting point for more complex transformations.

See Colors for some ideas on using other colors spaces aside from RGB.

Using punctual to draw mathematical functions:

A typical mathematical function has the form y=f(x), where f(x) is a formula that specifies how to get the y coordinate from the x coordinate. So, for any x, we draw a point at coordinates (x, f(x)).

Using this method we can draw a lot of mathematical functions (here, the zoom is only used to see a bit more of the resulting graph):

zoom 0.4 $ point [fx, cos fx] >> add;

Cartesian coordinates example 5

zoom 0.4 $ point [fx, log $ abs fx] >> add;

Cartesian coordinates example 6

zoom 0.4 $ point [fx, fx*fx] >> add;

Cartesian coordinates example 7

Another way to do the same is by using the between function (see Mathematical functions).

In essence, between receives two values and a graph and returns 1 if the graph is between the two values, or else 0.

Remember that this is evaluated independently for each fragment, so in the next example, for any fragment (fx,fy) we’ll draw white if and only if fy is almost equal to cos fx. The 2*px is the margin that fy can be away from cos y and still be white, and determines the wideness of the line we’ll draw. As explained a bit later, px is the fragment’s height:

zoom 0.4 $ between [cos fx-2*py, cos fx+2*py] $ fy >> add;

Cartesian coordinates example 8

We can use evolve these ideas to create interesting patterns:

point[fx, osc 0.09*cos (fx*fy*15*(2+osc 0.32))] >> add;
0.98 * fb fxy >> add;

Cartesian coordinates example 9

px, py

Depending on the screen resolution and the windows size, pixels on Punctual will have a specific dimensions expressed in the Punctual coordinate system.

You can access a pixel’s width with px and a pixel’s height with py. This is usually used to draw thin lines, as in the following example, or the ones on the previous section:

zoom 0.4 $ circle [fx, fx*fx] $ 4*px >> add;

Cartesian coordinates example 10

aspect, fit

aspect is the ratio between the width and height of the window, so, if you don’t change the window size (or use fit), it’s a constant.

To “see” it’s value, we can try something like this:

[0.3*aspect,0,0] >> add;

Now, change the window size, and you should see how the background is redder as the width height ratio increases, and darker as it decreases.

fit can be used to rescale the x coordinates in order to change the aspect ratio of the result. When doing this, the visible coordinates on the x-axis no longer will be on the range -1 to 1 (unless fit aspect is specified, which is the same as doing nothing).

The most obvious way of using fit is to correct the aspect of geometric shapes, so a circle draws like a circle, and so on:

fit 1 $ circle 0 0.3 >> add;

Cartesian coordinates example 11

fit 1 $ rect 0 0.5 >> add;

Cartesian coordinates example 12

Another useful application of fit is to scale an external source (an image, a video, or the camera output) to fit the screen, or a particular portion of the screen:

fit (0.5*aspect) $ move [1, 0] $ img "" >> add;
fit (0.5*aspect) $ move [-1,0] $ img "" >> add;

Cartesian coordinates example 13

In this example, we took images of the Communist Manifesto authors from Wikicommons, and made each one of them to occupy the half of the window, independently from its size.

Same idea, but we divide the window in two horizontally:

fit (2*aspect) $ move [0,-1] $ img "" >> add;
fit (2*aspect) $ move [0,1] $ img "" >> add;

Cartesian coordinates example 14

Can we combine both previous examples to divide the window in four equal-sized parts? The answer is yes, as aspect is modified just when fit is executed:

fit (0.5*aspect) $ fit (2*aspect) $ move [-1,1] $ img "" >> add;

Cartesian coordinates example 15

Now, these last examples show the most standard uses of fit, but you can also use it in many creative ways.

For example, we can apply an oscillator to the aspect ratio to deform the image periodically:

fit (osc 0.2) $ circle 0 0.8 >> add;

Cartesian coordinates example 16

Now, evolving this example, we can create beautiful patterns:

spin [saw 0.2, (-1)*saw 0.2] $ fit (8*osc (0.5*cps)) $ tile [4,step [1,4,8,16] $ saw cps] $ circle 0 0.8 * [0.8,0,0.8]>> add;
0.8 * fb fxy >> add;

Cartesian coordinates example 17

Here, we are synchronizing the fit oscillator to the beat, and increasing its range to -8 to 8. We are also drawing a lot more circles using tile. The replication over the y-axis changes over time, due to the use of step and the saw oscillator. Finally, the spin with to graphs as first argument, duplicates the whole set and made each of the copies rotate in opposite directions.

Polar coordinates

In the polar coordinate system, each point in the plane also has two coordinates, but they are the distance to the pole (the center of the window, analogous to the origin in Cartesian coordinates), and the angle from the horizontal ray that goes from the pole to the right (called polar axis), measured in radians.

Polar coordinate system
Monsterman222, CC BY-SA 3.0, via Wikimedia Commons

fr, ft, frt

In the same way that fx, fy and fxy allow to get fragment coordinates in the Cartesian system, fr, ft and frt are the fragment coordinates in the polar system.

fr is the distance from the pole, and is equivalent to dist 0 or dist [0,0].

Note that if we only move through one of the axis, fr goes from 0 to 1, but can be larger. The point at [1,1] has a fr of approximately 1.41, that is the square root of 2 (by the Pythagorean theorem).

ft is the angle from the reference ray, which is the right part of the horizontal axis. In Punctual, this angle goes from -π to π radians. This is important, as many times you’d like to rescale this range to a [-1,1] range or [0,1] range, in order to use it in other parts of your code:

  • To a [-1,1] range: ft/pi.
  • To a [0,1] range: linlin [(-1)*pi, pi] [0,1] ft or unipolar $ ft/pi.

Next examples are similar to the ones of the Cartesian coordinates section, but the result can be quite different when using polar coordinates.

20 radial gradients:

fit 1 $ 10/pi*(ft % (pi*0.1)) >> add;

Polar coordinates example 1

Or concentric gradients:

fit 1 $ 10 * (fr % 0.1) >> add;

Polar coordinates example 2

Color patterns:

r << fr/1.4;
t << ft/pi;
[1-r, abs t, abs $ r/ft] >> add;

Polar coordinates example 3

r << fr/1.4;
t << ft/pi;
[1-r, abs t, abs $ fx*fy] >> add;

Polar coordinates example 4

Note how fr and ft are rescaled to keep the red and green coordinates from 0 to 1.

Mathematical functions:

Not so obvious as with Cartesian coordinates, but it’s possible to build interesting shapes using the same ideas:

fit 1 $ point [fx, sin' ft] >> add;

Polar coordinates example 5

For each fragment, draw only the points where y matches the sine of the angle. This results in a circumference of radius 1 (by definition of the sine function, the sine of an angle is the y coordinate of the corresponding point on the circumference of radius 1), and a line at x=0, as the sine of 0 (or π) is 0.

The missing central part of the line is due to rounding errors. Actually, point is a small circle, so it’s visible if some fragment is near enough the specified coordinates. That explain why the circumference has some wideness. Now, as we get closer to the origin, a little change in coordinates radically changes the angle, and that’s why there isn’t any point there which sine is near enough to the y coordinate.

Similarly: fit 1 $ between [sin' ft - py, sin' ft + py] $ fy >> add;.

Polar coordinates example 6

dist, prox

dist is the distance of a fragment to a given position.

prox is the opposite of dist, the idea of proximity of a fragment to a given position. prox is calculate such as two opposite screen points have a proximity of 0, and one point has a proximity of 1 to itself.

Let’s take for example [1,1] and [-1,-1] as two point that are as far away from each other as it is possible in the initial visible screen. The distance between them, per the Pythagorean theorem, is the square root of 8, that is approximately 2.828427. prox is computed as (2.828427-dist[x,y])/2.828427, clamped to be between 0 and 1.

One very usual way of using prox is to create masks. Visuals can get quite bright easily, and this can make it difficult for you or the people you are jamming with to see the code on the screen.

You can create a mask in order to keep the borders of the screen relatively clear and focus the visuals on the center:

a << 1;
m << prox 0 ** 4;
a*:m >> add;

Polar coordinates example 7

Here, a is the annoying graph, in this case a completely white screen. m is the mask, calculated as the proximity to the origin, and raised to the power of 4.

Note that, as all proximities lies between 0 and 1, as we increase the power, the resulting numbers will be nearer to 0.

Finally, we multiply a and m, point to point, deeming each fragment according to the proximity it has to the center.

Following there are some examples on how can we use dist and prox in different creative ways:

  • Color patterns depending on the distance of each fragment to certain coordinates:
[dist [0.5,0], prox [-0.6,-0.3], dist [-0.3,0.3]] >> add;

Polar coordinates example 8

  • Deform an image:
i1 << img "";
tile [prox [0.5,0], 3*dist [0,0.2]] $ i1 >> add;

Polar coordinates example 9

Here we use tile (see geometric transformations) to deform the image, as the value of tile is different for each fragment.

We can add some audioreactiveness (see audio reactive visuals) to the last pattern to make it a bit more interesting:

i1 << img "";
tile [prox [ihi,0], 3*dist [0,ilo]] $ i1 >> add;

Polar coordinates example 10

From Cartesian to polar and viceversa

There are a bunch of functions that are meant to transform coordinates from the Cartesian system to the polar system or viceversa:

xyrt: from Cartesian to polar.

To understand this function let’s look at the following example:

c << [1,osc 0.1];
fit 1 $ [circle c 0.1, circle (xyrt c) 0.1] >> add;

Cartesian to polar example 1

The red circle stays at x=1 and moves vertically, from y=-1 to y=1 due to the oscillator.

The cyan circle is very similar, but we’ve applied xyrt to its center coordinates. That means that these coordinates are transformed to polar coordinates. So, looking at the first circle and thinking in how this movement affects the angle and radius from the center, we see that the angle moves from -pi/4 to pi/4, that is from approximately -0.79 to 0.79. The radius moves from the square root of two (1.41) at the top, then decreases until it get to 1, just on the horizontal axis, and then increases again to 1.41 at the bottom.

Now we take these two coordinates, but read them as Cartesian coordinates (note that xyrt calculates the polar coordinates, but doesn’t apply any real geometric transformation like setfxy does). So, the result is a circle which x coordinate moves from 1.41 to 1 then again to 1.41, and which y coordinate moves from -0.79 to 0.79.

Let’s add a few reference lines to check our calculations:

c << [1,osc 0.1];
fit 1 $ [circle c 0.1, circle (xyrt c) 0.1] >> add;
mono $ fit 1 $ hline [-0.79, 0.79] px >> add;
mono $ fit 1 $ vline [1,1.41] px >> add;

Cartesian to polar example 2

As a side note, see how the first example can be rewritten shorter using the ++ operator from the combining graphs section:

c << [1,osc 0.1];
fit 1 $ circle (c++xyrt c) 0.1 >> add;

xyr, xyt

These two functions are nearly identical to xyrt, but they only return one of the two coordinates, the radius or the angle respectively.

Following the previous example:

c << [1,osc 0.1];
fit 1 $ [circle c 0.1, circle [xyr c,0] 0.1] >> add;
mono $ fit 1 $ vline [1,1.41] px >> add;

Cartesian to polar example 3

c << [1,osc 0.1];
fit 1 $ [circle c 0.1, circle [0,xyt c] 0.1] >> add;
mono $ fit 1 $ hline [-0.79, 0.79] px >> add;

Cartesian to polar example 4

rtxy: from polar to Cartesian.

This is the opposite from xyrt: it takes coordinates in the polar system and returns the equivalent in the Cartesian system.

c << [1,pi*osc 0.1];
fit 1 $ zoom 0.25 $ circle (c++rtxy c) 0.1 >> add;

Cartesian to polar example 5

The red circle keeps its x coordinate at 1 and oscillates between -π and π on the y coordinate (note the zoom in order to see all its movement).

The cyan circle takes the 1 as its distance from the origin, and changes the angle, so it keeps moving around the origin, always at the same distance.

I find that rtxy is the most usable of the whole family to create cool visual effects.

In the next example, let’s start with this:

c << [[0.2,0.3..2]*:osc 0.12*osc 0.13, pi*osc 0.1*osc 0.11];
zoom 0.25 $ circle c 0.1 >> add;

Cartesian to polar example 6

These are a bunch of circles that move together in a somewhat irregular way due to the multiplications of oscillators at different frequencies.

The first bit ([0.2,0.3..2]) uses Haskell style list expansion to create the x coordinate for each circle, from 0.2 to 2, going in steps of 0.1.

The y coordinate is common to all circles, and oscillates from -π to π.

Next step is just take all the circle’s center coordinates and read them as polar coordinates.

c << [[0.2,0.3..2]*:osc 0.12*osc 0.13, pi*osc 0.1*osc 0.11];
circle (rtxy c) 0.1 >> add;

Cartesian to polar example 7

Now, we apply some basic geometric transformations, using combinations of fx, fy, ft and fr to twist the circles geometry:

c << [[0.2,0.3..2]*:osc 0.12*osc 0.13, pi*osc 0.1*osc 0.11];
fit 1 $ spin (fr*4) $ move [ft*fx*0.3,ft*0.1] $ circle (rtxy c) 0.1 >> add;

Cartesian to polar example 8

Finally, let’s add some repetition in the middle to create more elements:

c << [[0.2,0.3..2]*:osc 0.12*osc 0.13, pi*osc 0.1*osc 0.11];
fit 1 $ spin (fr*4) $ tile [4,4] $ move [ft*fx*0.3,ft*0.1] $ circle (rtxy c) 0.1 >> add;

Cartesian to polar example 9

Playing with circles and polar coordinates is fun! In the last example, all the circles were moving like a whole. Can we make each one to move separately from the others?

Let’s start with our circles forming a bigger circumference around the center:

t << linlin [0,1] [0, 2*pi] [0, 0.02..1];
fit 1 $ circle (rtxy [0.5, t]) 0.1 >> add;

Cartesian to polar example 10

Here we use a list expansion for the circles angles, just like in the last example, and rescale it from a 0 to 1 range to a 0 to 2π using linlin, just because it’s easier to think from 0 to 1.

Then draw all the circles using polar coordinates, at a fixed distance from the origin of 0.5.

Let’s made the circles spin:

t << linlin [0,1] [0, 2*pi] [0, 0.02..1];
o << pi * saw 0.1;
fit 1 $ circle (rtxy [0.5, t+:o]) 0.1 >> add;

Cartesian to polar example 11

Here, o is an oscillator that goes from -π to π, and when it reaches π it starts again from -π. We add this oscillator to each angle, and the result is that all the circles spin together.

Finally, let’s apply an oscillator to the radius too, but this time, each circle will have a distinct frequency, so they will move apart from each other. We make this by using t to modify the oscillator frequency in the bit osc (0.1*t).

Now, we have a list of radius and a list of angles, and we need to link them together: the first radius with the first angle, the second radius with the second angle, and so on. This is just what the zip function from combining lists does.

As a final touch, we use mono to combine all channels into one, which make all circles white:

t << linlin [0,1] [0, 2*pi] [0, 0.02..1];
o << pi * saw 0.1;
fit 1 $ mono $ circle (rtxy $ zip (osc (0.1*t)) (t+:o)) 0.1 >> add;

Cartesian to polar example 12

rtx, rty

These two functions are nearly identical to rtxy, but they only return one of the two coordinates, the x or y coordinate respectively.

r << unipolar $ osc 0.03;
t << pi*osc 0.07;
fit 1 $ circle [r,t] 0.1 >> add;
fit 1 $ circle (rtxy [r,t]) 0.1 >> red;
fit 1 $ circle [rtx [r,t], 0] 0.1 >> green;
fit 1 $ circle [0, rty [r,t]] 0.1 >> blue;

Cartesian to polar example 13

In this example, r oscillates from 0 to 1, and t from -π to π. There are a total of four circles:

  • White: r and t are interpreted as Cartesian coordinates x and y.
  • Red: r and t are read as polar coordinates.
  • Green: r and t are polar coordinates, but only used to calculate the resulting x position.
  • Blue: same as green, but only the y position is calculated.


10 ways of drawing a circumference of radius 1

Just to see the flexibility of Punctual and different ways to use coordinates, let’s think of different ways to draw a circumference of radius 1.

Probably the easiest way: let’s paint white (1) all fragments whose radius is 1, pixel more, pixel less:

between [1-px, 1+px] $ fr >> add;

Coordinates example 1

As the radius is no more than the distance to the origin, this is equivalent:

between [1-px, 1+px] $ dist 0 >> add;

Also, it’s possible to make this calculation explicit, using the Pythagorean theorem:

between [1-px, 1+px] $ sqrt $ fx*fx+fy*fy >> add;

Next is a similar approach, but without using between. Here, on one hand we take all fragments whose radius is a bit lesser than one, and on the other the fragments whose radius is a bit greater than one. What we want is the intersection of those two, so we multiply the graphs (the result will be one only if both graphs have a one on that fragment).

(fr <= 1+px) * (fr >= 1-px) >> add;

Another way of thinking this is by using the mathematical function of a circumference. For any x, we have to paint only the y that follow the formula x^2 + y^2 = 1. Isolating the y we get:

f << [-1,1]*(sqrt $ 1-fx*fx);
mono $ point [fx, f] >> add;

Note how the square root is multiplied by [-1,1] in order to take both roots.

Now we can take both ways of thinking and mix them. We’ll paint white only those fragments whose x-coordinate is near the computed y for the circle at that x:

c << sqrt (1-fy*fy);
between [c-px, c+px] $ abs fx >> add;

In the last example, we are only taking the positive root of the square root, but are taking the absolute value of x to make the graphic symmetrical.

Yet another way of thinking this problem: we already have a function that draws circles. Let’s tweak it to draw a circumference instead.

In this example, we draw a circle of radius 1, and then take off a slightly smaller circle:

(circle 0 2) - (circle 0 $ 2-2*px) >> add;

Next is mathematically equivalent to the last one, but we get here with another way of thinking. We can draw the inner part of the circumference with a circle, and also the outer part with an inverted slightly bigger circle (using 1- to invert the result, as it is all 0 and 1). So, we have obtained just the invert of what we what, so we invert the result again:

1-(1-circle 0 (2+2*px) + circle 0 (2-2*px)) >> add;

Finally, a last way to solve the problem is by transforming coordinates from Cartesian to polar. If you think about it, a circumference has a very easy representation on polar coordinates, as one of them (the radius) is constant.

This is exactly what vertical and horizontal lines are: a graph where one of the two coordinates is constant.

So we can draw a vertical line, and then think of it a circumference in polar coordinates, so when we interpret the x as the radius and the y as the angle, we get a circumference:

setfxy frt $ vline 1 px >> add;

Similarly, we can use a horizontal line, and the we need to interpret the x coordinate as the angle, and the y coordinate as the radius:

setfxy [ft,fr] $ hline 1 px >> add;

Bonus track: set feedback to 100% and use a point like it is a pencil:

spin (saw 0.06) $ point [0,1] >> add;
fb fxy >> add;

Coordinates example 2

Implementing Hydra functions

As explained in the Overview, Hydra is a popular language for live-coding visuals. One key difference between Hydra and Punctual is that Punctual operates at a slightly lower level than Hydra.

This means that while it is possible (though not always straightforward) to reimplement many Hydra functions using Punctual, the reverse is not necessarily true.

Let’s develop a couple of examples to illustrate this.

  • Implementing Hydra’s osc() function:

Hydra’s osc() function draws a sine wave, visualized from above. When the sine wave is far away (low), the visualization appears black, and as it gets closer, it turns white.

In Punctual, this can be expressed using unipolar $ sin' (fx*freq), where freq determines the number of sine wave repetitions. To fully mimic Hydra’s osc(), we need to adjust the coordinates and use unipolar fx instead of fx: unipolar $ sin' (unipolar fx*freq).

Hydra’s osc() also moves horizontally over time. We can achieve this by adjusting the phase of the sine wave based on the elapsed time:

unipolar $ sin' ((unipolar fx+sync*time)*freq);

Hydra uses 60 as the default frequency and 0.1 for the sync parameter. Applying these values, the Punctual expression becomes:

unipolar $ sin' ((unipolar fx+0.1*time)*60) >> add;

This expression recreates the behavior of Hydra’s osc() function in Punctual.

Coordinates example 3

  • Implementing osc().kaleid():

Now that we have our version of Hydra’s osc() function, we can extend it with some transformations, such as kaleid().

In this example, we are reimplementing the Hydra expression osc().kaleid(5).out().

The core idea of kaleid(5) is to apply a geometric transformation (using setfxy) by which we take an arc of a fifth of the whole circle and copy it five times, filling the entire screen. Additionally, we need to rotate our arc so that the different parts align correctly, and apply a translation because Hydra’s kaleid() copies the upper left part of the image but draws it at the center.

This is a possible implementation:

hosc << unipolar $ sin' ((unipolar fx+0.1*time)*60);
k << ft % (2*pi/5);
setfxy (rtxy [fr, k]) $ spin ((-1)/5) $ move 0.5 hosc >> add;
  1. First line: This is our version of Hydra’s oscillator, osc(), which creates a sine wave pattern that moves horizontally over time.
  2. Second line: Here, k is defined. It represents the new angle for each fragment, and the modulus (%) operator is used to map any fragment to the first fifth of the circle. This is the key to the whole kaleidoscope effect.
  3. Third line: This line combines several transformations:
    • move 0.5 hosc: Moves the oscillator 0.5 units in each coordinate, effectively shifting the top-left corner of the screen to the center.
    • spin ((-1)/5): Rotates one-fifth of the whole circle, ensuring that each part of the pattern matches up seamlessly.
    • setfxy (rtxy [fr, k]): Applies the geometric transformation. Note that setfxy (rtxy frt) something is an identity transformation, as it retains the radius and the angle. In our cas, k is assigned as the new angle, effectively creating the kaleidoscope effect by repeating and rotating the pattern.

This implementation effectively mimics Hydra’s osc().kaleid(5).out() by using Punctual’s lower-level functions to achieve the same visual result.

setfxy is so flexible that we could include the move, spin and rtxy operations inside the setfxy transformation making the whole transformation in only one step:

hosc << unipolar $ sin' ((unipolar fx+0.1*time)*60);
k << ft % (2*pi/5)-pi/5;
setfxy (fr*[cos k, sin k]-0.5) hosc >> add;

Coordinates example 4

Of course, this is complicated to deduce, but as Hydra is free software, we can look up the original kaleid implementation and translate the code to Punctual.