Skip to content

Joy of Vex Day 11

If statements

Vex works like most C style languages, you can control execution based on testing against a value.

The trickiest thing with if statements is the punctuation, its easy to trip up and miss things. Here's the template:

vex
 if (test in regular brackets)  {
     code to execute in curly brackets;
     end each line in a semi colon;
     and close the if statement with a curly bracket;
 }
 if (test in regular brackets)  {
     code to execute in curly brackets;
     end each line in a semi colon;
     and close the if statement with a curly bracket;
 }

The test is usually a less than, equals to, or greater than:

vex
 if ( @foo > 1) {
 if ( @foo > 1) {

or

vex
 if ( @ptnum < 50 ) {
 if ( @ptnum < 50 ) {

or

vex
 if ( @name == 'piece5') {
 if ( @name == 'piece5') {

Note that last one, testing for equality needs 2 equals signs. Here comes a big fat warning for emphasis:

TESTING FOR EQUALITY NEEDS TWO EQUAL SIGNS.
EVERYONE MAKES THE MISTAKE OF USING A SINGLE EQUALS SIGN.
DON'T DO THIS.

This is a fundamental flaw with most C derived languages, and most other languages too; assignment vs equality testing. Even experienced coders will scream at code they swear is perfect, and find they've miss-typed an if test.

vex
float foo = 5;  //   single equals sign means 'set foo to 5'

// GOOD
if (foo == 5) {    // double equals sign asks 'is foo equal to 5?'
   // do something
}

// BAD
if (foo = 5) {    // single equals sign! boo!
   // do something
}
float foo = 5;  //   single equals sign means 'set foo to 5'

// GOOD
if (foo == 5) {    // double equals sign asks 'is foo equal to 5?'
   // do something
}

// BAD
if (foo = 5) {    // single equals sign! boo!
   // do something
}

Vex will happily assign foo to 5, the test says 'oh I dunno... true I guess?', so it always evaluates as true. You'll get a little green squiggle in the editor to hint 'are you sure you meant that?', but it's not a syntax error, so it's easy to miss.

/END PUBLIC WARNING

Back to examples. We could colour the top half of a mesh red, the bottom half green, by testing against the relpointbbox result:

vex
 vector bbox = relpointbbox(0,@P);
 @Cd = {1,0,0};
 if (bbox.y<0.5) {
   @Cd = {0,1,0};
 }
 vector bbox = relpointbbox(0,@P);
 @Cd = {1,0,0};
 if (bbox.y<0.5) {
   @Cd = {0,1,0};
 }

Or maybe test if the dot product of @N and {0,1,0} is above a threshold, and colour based on that:

vex
 float d = dot(@N,{0,1,0});
 @Cd = {0,0,1};
 if (d>0.3) {
   @Cd = {1,1,1};
 }
 float d = dot(@N,{0,1,0});
 @Cd = {0,0,1};
 if (d>0.3) {
   @Cd = {1,1,1};
 }

Drive that comparison with a channel slider, you have a simple way to define where dust or snow might be collecting on surfaces that face up (easier to see on a more complex model like the pig or tommy):

vex
 float d = dot(@N,{0,1,0});
 @Cd = {0,0,1};
 if (d>ch('cutoff')) {
   @Cd = {1,1,1};
 }
 float d = dot(@N,{0,1,0});
 @Cd = {0,0,1};
 if (d>ch('cutoff')) {
   @Cd = {1,1,1};
 }

You can throw an 'else' in there to define a 2 way switch. Sometimes this makes the logic more clear, depends on the circumstances.

vex
 float d = dot(@N,{0,1,0});
 if (d>ch('cutoff')) {
   @Cd = {1,1,1};
 } else {
   @Cd = {1,0,0};
 }
 float d = dot(@N,{0,1,0});
 if (d>ch('cutoff')) {
   @Cd = {1,1,1};
 } else {
   @Cd = {1,0,0};
 }

You can also test for less than:

vex
 if (d<ch('cutoff')) {
 if (d<ch('cutoff')) {

Testing for equality

Say we had two variables a and b, and want to test if they're equal. Doing this with integers is fine, the test will behave as you expect:

vex
int a = 3;
int b = 3;
if ( a == b) {   // yes this works
int a = 3;
int b = 3;
if ( a == b) {   // yes this works

But look what happens here:

vex
float foo = 0;
float bar = sin($PI);

if ( foo == bar) {  // fails the test
float foo = 0;
float bar = sin($PI);

if ( foo == bar) {  // fails the test

sin($PI) should be 0, 0 equals 0, why does this test fail? If you look at the result you get for sin($PI) in vex, it returns -8.74228e-08, ie -8.7422, with the decimal point shifted 8 places to the left, so it's 0.0000000087. That's very closet to zero, but its not close enough, so the test fails.

This is a common issue for most programming languages; the floating point data format isn't completely accurate in certain cases. To get around this, you don't test for equality, but rather you subtract one variable from the other, and test if it's under a threshold you're happy with:

vex
 if ( foo - bar < 0.00001 )   {   // close enough to say they're equal
 if ( foo - bar < 0.00001 )   {   // close enough to say they're equal

Seems ok right? But wait, what if foo is 1, and bar is -300? The result of -299 is definitely less than 0.0001, so we've broken the test.

The final step is to take the absolute value, ie, if the answer is a negative number, make it positive. Can do this with the abs function:

vex
 if ( abs(foo - bar) < 0.00001 )   {   // close enough to say they're equal, and won't be fooled by negative numbers
 if ( abs(foo - bar) < 0.00001 )   {   // close enough to say they're equal, and won't be fooled by negative numbers

This test among proper coding people is called an epsilon test, more info here: https://www.wikiwand.com/en/Machine_epsilon

Multiple tests, other tests

Say you wanted to do a thing if @ptnum is greater than 50, and @P.x is less than 2. You can nest if statements, so that only if both tests pass, will it do the thing:

vex
 if (@ptnum > 50) {
     if (@P.x < 2) {
          @Cd = {1,0,0};
     }
 }
 if (@ptnum > 50) {
     if (@P.x < 2) {
          @Cd = {1,0,0};
     }
 }

That's a perfectly fine way to do it, but you can also combine tests into a single statement. To make a test have to pass both things, link them together with a double ampersand:

vex
 if (@ptnum > 50 && @P.y < 2 ) {
     @Cd = {1,0,0};
 }
 if (@ptnum > 50 && @P.y < 2 ) {
     @Cd = {1,0,0};
 }

You can also test with an OR type, which is double vertical lines. That means if either test is true, it will execute. So here, any points with @ptnum > 50 as well as any points with @P.x < 2, will be coloured red.

vex
 if (@ptnum > 50 || @P.x < 2 ) {
     @Cd = {1,0,0};
 }
 if (@ptnum > 50 || @P.x < 2 ) {
     @Cd = {1,0,0};
 }

Finally there's a few more tests you can use beyond <, >, ==.

Not equal; all ptnums except 5 will be red:

vex
 if ( @ptnum != 5) {
     @Cd = {1,0,0};
 }
 if ( @ptnum != 5) {
     @Cd = {1,0,0};
 }

Less than or equal, so ptnums 0 1 2 3 4 5 will be red (using just < would mean only 0 1 2 3 4 would be red)

vex
 if ( @ptnum <= 5) {
     @Cd = {1,0,0};
 }
 if ( @ptnum <= 5) {
     @Cd = {1,0,0};
 }

Have a guess what this does. 😃

vex
 if ( @ptnum >= 5) {
     @Cd = {1,0,0};
 }
 if ( @ptnum >= 5) {
     @Cd = {1,0,0};
 }

Don't forget that modulo can be useful for tests, eg, highlight every 5th point red:

vex
 if ( @ptnum % 5 == 0) {
     @Cd = {1,0,0};
 }
 if ( @ptnum % 5 == 0) {
     @Cd = {1,0,0};
 }

Also note that the comparison operator comes last in the order of operations, so all the * / + - % things you setup are calculated first on either side of the comparison, then the results of both sides are compared. So in this made up nonsensical if statement:

vex
 if ( length(@P)*2+@ptnum % 5 > dot(@N,{0,1,0}*@Time) {
     @Cd = {1,0,0};
 }
 if ( length(@P)*2+@ptnum % 5 > dot(@N,{0,1,0}*@Time) {
     @Cd = {1,0,0};
 }

Everything on the left of the greater-than operator is evaluated:

vex
length(@P)*2+@ptnum % 5
length(@P)*2+@ptnum % 5

Then everything on the other side:

vex
dot(@N,{0,1,0}*@Time)
dot(@N,{0,1,0}*@Time)

And those two results are tested to see if the first is greater-than the second. As a matter of coding style, while it works and is perfectly valid, in my opinion that gets very hard to read. It's common to do short equations (like the @ptnum % 5 one from earlier), but if it gets too complex, I'd probably assign both sides to temp variables, and compare those, easier to understand at a glance:

vex
float a = length(@P)*2+@ptnum % 5;
float b = dot(@N,{0,1,0}*@Time);
 if ( a > b) {
     @Cd = {1,0,0};
 }
float a = length(@P)*2+@ptnum % 5;
float b = dot(@N,{0,1,0}*@Time);
 if ( a > b) {
     @Cd = {1,0,0};
 }

How to format your code

Vex, unlike say python, doesn't care where you put space, returns, any of that. So you could do this horrible thing if you hate others reading your code:

vex
  if ( length(@P)*2+@ptnum % 5 == 0) {  @Cd = {1,0,0}; }
  if ( length(@P)*2+@ptnum % 5 == 0) {  @Cd = {1,0,0}; }

But there's two schools of thought on where to put the curly braces. You can either do them the way I've done so far, first curly brace on the same line as the if test:

vex
 if ( @ptnum % 5 == 0) {
     @Cd = {1,0,0};
 }
 if ( @ptnum % 5 == 0) {
     @Cd = {1,0,0};
 }

But some folk feel that gets hard to track where the curly braces start and end, especially if you have multiple nested statements. Those folk say putting the braces on their own line is better:

vex
 if ( @ptnum % 5 == 0)
 {
     @Cd = {1,0,0};
 }
 if ( @ptnum % 5 == 0)
 {
     @Cd = {1,0,0};
 }

Can see why with nested stuff. The number of spaces you indent is also debated hotly among code nerds, I usually do 4 spaces (or hit tab and hope the vex editor does the right thing):

vex
 if ( @ptnum % 5 == 0)
 {
    if (@P.x > 2)
    {
          @Cd = {1,0,0};
    }
 }
 if ( @ptnum % 5 == 0)
 {
    if (@P.x > 2)
    {
          @Cd = {1,0,0};
    }
 }

Exercises

  1. Waves that only start after @Time being greater than 2 seconds
  2. Colour points that have their normals facing towards a specific point/normal
  3. Highlight red all the points where their ptnum can be cleanly divided by 10

Bonus chapter, ternary operator, yet another way to format if statements

There's a super terse shorthand if statement that combines test and assignment and else all in one line called the ternary operator:

vex
@Cd = @Time%1==0 ? 1 : 0;
@Cd = @Time%1==0 ? 1 : 0;

That means 'if Time modulo 1 (ie its exactly on the 1 second, 2 second, 3 second mark etc) is 0, set Cd to 1, otherwise 0'.

The general way to think of it is

(some test) ? value_if_true : value_if_false;

It's used a lot in hscript expressions for dops to make things only emit one on the first frame, or generally when people like to show off how much C and vex idioms they know. I use it every now and then, mostly I prefer the readability of the longform if statement, and its not really that much more to type.


prev: JoyOfVex10 this: JoyOfVex11 next: JoyOfVex12
main menu: JoyOfVex