From cgwiki
Revision as of 19:25, 9 January 2023 by MattEstela (talk | contribs) (→‎Modulo)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search


Everyone understands + - * /, but % usually surprises people who haven't done much coding before.

% is modulo. When you first learn to divide in primary school, and you do remainders, well, modulo is the remainder. That's it.

Ask a 10 year old kid 'What is 5 divided by 2?' , they'll tell you '2, remainder 1'. In the same way, 5 % 2 = 1.

This is useful because it sets up loops; when the first and second number are the same, the remainder is 0. If you're doing modulo by 5 on an input that smoothly increases, like @ptnum or @Frame, you'll get a sawtooth waveform that goes 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4...

 @P.y = @ptnum % 5;

Modulo works on fractional values too, so you can give it a float input, and smoothly varying float input like @Time, you still get a sawtooth pattern:

 @Cd.r = @Time % 0.7;

Lots of effects involve setting up loops, modulo gives you an easy way to setup inputs or outputs that loop.

"Matt, why does that last one look cyan so often?" We asked vex to set the red component of @Cd, but not the green or blue. If they're not specified, vex will initialise them to 1. As the red value cycles around, it will be a low value a lot of the time, meaning we get {0,1,1} frequently, which is how you make cyan.

Making smooth things stepped via quantising

Go back to the usual trick of P.y based on distance to the origin:

 float d = length(@P);
 d *= ch('scale');
 @P.y = d;

The result is a smooth funnel shape. What we can do is make this steppy. Imagine we had slowly increasing fractional values:

1.0, 1.1, 1.2, 1.3, 1.4 ... 1.8, 1.9, 2.0, 2.1, 2.2, 2.3 ...

If we removed everything after the decimal place, you'd get this:

1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3

Ie, the smoothly increasing original values are now stepped. But what if the original values are really small, say ranging from 0.001 to 0.003? If you remove values after the decimal point, it all goes to 0, thats no good. Similarly, if the values were between 1000 and 4000, it won't make much of a difference.

The trick is to multiply all the numbers by a factor to push the interesting values to the left of the decimal point, then remove stuff after the decimal point, then divide by the original number. If you put that steppy factor on a channel slider, you have an easy way to dial in the size of the steps. The vex function to remove stuff after the decimal point is trunc, short for truncate ( )

 float d = length(@P);
 d *= ch('scale');
 float f = ch('factor');
 d /= f;
 d = trunc(d);
 d *= f;
 @P.y = d;


This process of losing precision is called quantising.

Faking trunc with a chramp

An equally valid way to get stepping is to draw in a stepped graph into a channel ramp. Whatever is more useful to you at the time. Just remember that you'll probably need 2 scaling functions, the first to bring your thing into a 0 to 1 range for the channel ramp, and a second to scale it to where you need the y-values to be after quantizing.

So if you were doing this to the default grid for example, and you wanted it to look like the earlier attempt, you'd enter the following code, set prescale to 0.1 (Because the grid is 10 units wide, so this will make 'd' go from 0 at the center to 1 at the edge), and afterwards set post-scale to 10 (because the chramp will have its maximum value output at 1, so you need to multiply it back up to 10 to get to the height from the earlier simple example).

 float d = length(@P);
 d *= ch('pre_scale');
 d = chramp('my_stepped_ramp', d);
 d *= ch('post_scale');
 @P.y = d;

Trunc chramp.gif


  1. try the different interpolation modes on the points of the ramp
  2. using interpolation, see how few points you can use to define a sine wave
  3. think of other inputs you can use to drive the ramp. Components of P? Components of Cd or N? Current Time or current @Frame? If those things aren't in a 0:1 range like chramp expects, how do you fit them to the range you need?

prev: JoyOfVex4 this: JoyOfVex5 next: JoyOfVex6
main menu: JoyOfVex