Skip to content

Joy of Vex Day 13

For each loops, for loops

For each loops

So clearly in the previous example there must be something better than copying and pasting huge chunks of code. And there is! If you have an array, you can loop through all the elements of it with a foreach call. It looks like this:

vex
 foreach( element; array) {
     // do things to element
 }
 foreach( element; array) {
     // do things to element
 }

Nice and clean. You give it a temporary name that represents each element of the array, and the array itself. Inside the curly braces, you refer to that temp name. The foreach loop will execute the code for each element in the array. So, here's how you'd rewrite the previous example:

vex
 vector pos, col;
 int pts[];
 int pt;
 float d;

 pts = nearpoints(1,@P,40);  // search within 40 units
 @Cd = 0;  // set colour to black to start with

 foreach(pt; pts) {
    pos = point(1,'P',pt);
    col = point(1,'Cd',pt);
    d = distance(@P, pos);
    d = fit(d, 0, ch('radius'), 1,0);
    d = clamp(d,0,1);
    @Cd += col*d;
 }
 vector pos, col;
 int pts[];
 int pt;
 float d;

 pts = nearpoints(1,@P,40);  // search within 40 units
 @Cd = 0;  // set colour to black to start with

 foreach(pt; pts) {
    pos = point(1,'P',pt);
    col = point(1,'Cd',pt);
    d = distance(@P, pos);
    d = fit(d, 0, ch('radius'), 1,0);
    d = clamp(d,0,1);
    @Cd += col*d;
 }

Neat. Now the radius can scale as much as we want, no weird borders (I assure you its buttery smooth, gif compression doesn't like this much)

And because its late and I'm tired, the usual wave placeholder. Main things to note is that the wave ripples now overlap and mix properly, they fade with distance, they have little timing offsets, and they are driven by normal, so will work on any shape.

vex
 vector pos;
 int pts[];
 int pt;
 float a,d,f,t;

 pts = nearpoints(1,@P,40);  // search within 40 units

 foreach(pt; pts) {
    pos = point(1,'P',pt);
    d = distance(@P, pos);
    d = fit(d, 0, ch('radius'), 1,0);
    d = clamp(d,0,1);
    t = @Time*ch('speed');
    t += rand(pt);
    a = d*ch('amp');
    f = d*ch('freq');
    @P.y += sin(t+f)*a;
 }
 vector pos;
 int pts[];
 int pt;
 float a,d,f,t;

 pts = nearpoints(1,@P,40);  // search within 40 units

 foreach(pt; pts) {
    pos = point(1,'P',pt);
    d = distance(@P, pos);
    d = fit(d, 0, ch('radius'), 1,0);
    d = clamp(d,0,1);
    t = @Time*ch('speed');
    t += rand(pt);
    a = d*ch('amp');
    f = d*ch('freq');
    @P.y += sin(t+f)*a;
 }

It's worth pointing out that the process for making these is never "I'll just magically write this perfectly in one go from top to bottom". I'll usually start with something simple like find the distance, and run it through a sin() function. Then I'll add a channel inside sin to set frequency. The a slider outside in to set the height. Then add time. Then add a channel to time to control its speed. Then add a random offset to time. Then realise I have an unreadable one-liner, that I'll then reformat into something like you see above. I suspect most people write vex wrangles in a similar fashion, it can be (almost) as loose and freeform as putting down nodes.

Also note that you'll have to play with the numbers a bit to work out what fits onto your shape. Eg if you use radius 8 and frequency 10, that will generate quite large waves that'll only be visible on a big grid, say size 50x50 with 100x100 divisions. On a regular 10x10 grid the frequency needs to be higher, and the radius smaller.

For Loop

Foreach loops as shown above are a very convenient way to process arrays, and most of the time this is the better way to go. Most C style languages don't have this, and use a more traditional for loop. Vex supports this too, the syntax is a bit more complex:

vex
 for ( starting value; test; value increment) {

 }
 for ( starting value; test; value increment) {

 }

So if you wanted a really stupid way to set @a=10 using a for loop, you'd do this:

vex
 int i;

 for (i=1; i<11; i+=1) {
   @a = i;
 }
 int i;

 for (i=1; i<11; i+=1) {
   @a = i;
 }

There's no array magic here, so you have to do a lot of the manual lifting yourself. To rewrite the original colored dots with falloff thing in a for loop. You need to know the size of the array (which you get with len), and you have to get the array elements yourself:

vex
 vector pos, col;
 int pts[];
 int i, pt;
 float d;

 pts = nearpoints(1,@P,40);  // search within 40 units
 @Cd =0;

 for(i=0; i<len(pts); i++) {
    pt = pts[i];
    pos = point(1,'P',pt);
    col = point(1,'Cd',pt);
    d = distance(@P, pos);
    d = fit(d, 0, ch('radius'), 1,0);
    d = clamp(d,0,1);
    @Cd += col*d;
  }
 vector pos, col;
 int pts[];
 int i, pt;
 float d;

 pts = nearpoints(1,@P,40);  // search within 40 units
 @Cd =0;

 for(i=0; i<len(pts); i++) {
    pt = pts[i];
    pos = point(1,'P',pt);
    col = point(1,'Cd',pt);
    d = distance(@P, pos);
    d = fit(d, 0, ch('radius'), 1,0);
    d = clamp(d,0,1);
    @Cd += col*d;
  }

I snuck a new thing in there, i++. It's the same as i=i+1, or i+=1, its just a shorthand for incrementing a value.

Exercises

  1. Make ripples that are red at their peaks and green at the lowest points
  2. Make each ripple setup have the wave frequency be determined by data coming from the scatter points. Eg, multiply frequency by the scatter point's @ptnum, or @P.x value.
  3. I initially had an exercise "make the colour example do a colour blend in multiply mode rather than additive", but its not easy to get right! I include the finished result below, your exercise is to pull it apart and understand why it works as it does. 😃
vex
int pts[];
int pt;
vector col,pos;
float d;

pts = nearpoints(1,@P,20);

// treat this as ink on paper, so start with white paper
@Cd = 1;

foreach(pt; pts) {
    pos = point(1,'P',pt);
    col = point(1,'Cd',pt);
    d = distance(@P,pos);
    d = fit(d,0,ch('radius'),0,1);

    // adjust the ramp so its mostly 0,
    // then suddenly 1 at the end
    d = chramp('fade',d);

    // lerp is interpolate. we use d to make the colour
    // mostly the point colour, the quickly fade to white
    // at the radius border
    col = lerp(col,1,d);

    // multiply the colour
    @Cd *= col;
}
int pts[];
int pt;
vector col,pos;
float d;

pts = nearpoints(1,@P,20);

// treat this as ink on paper, so start with white paper
@Cd = 1;

foreach(pt; pts) {
    pos = point(1,'P',pt);
    col = point(1,'Cd',pt);
    d = distance(@P,pos);
    d = fit(d,0,ch('radius'),0,1);

    // adjust the ramp so its mostly 0,
    // then suddenly 1 at the end
    d = chramp('fade',d);

    // lerp is interpolate. we use d to make the colour
    // mostly the point colour, the quickly fade to white
    // at the radius border
    col = lerp(col,1,d);

    // multiply the colour
    @Cd *= col;
}


prev: JoyOfVex12 this: JoyOfVex13 next: JoyOfVex14
main menu: JoyOfVex