Difference between revisions of "JoyOfVex14"

From cgwiki
m (Text replacement - "<source>" to "<source lang="javascript" >")
 
(2 intermediate revisions by the same user not shown)
Line 5: Line 5:
 
Vex can create points with the '''addpoint''' command. Like a lot of functions in vex you have to define the geometry to output to (always 0, ie, the first geo, ie 'this' geometry), and a position. It returns an integer, which represents a ptnum you can refer to later.
 
Vex can create points with the '''addpoint''' command. Like a lot of functions in vex you have to define the geometry to output to (always 0, ie, the first geo, ie 'this' geometry), and a position. It returns an integer, which represents a ptnum you can refer to later.
  
<source>
+
<source lang="javascript" >
 
  int pt = addpoint(0, {0,3,0});
 
  int pt = addpoint(0, {0,3,0});
 
</source>
 
</source>
Line 13: Line 13:
 
As a quick aside, you could collapse this down to a single point afterwards with a fuse sop, or try and control when the addpoint function runs, so it only fires on a single point. Eg:
 
As a quick aside, you could collapse this down to a single point afterwards with a fuse sop, or try and control when the addpoint function runs, so it only fires on a single point. Eg:
  
<source>
+
<source lang="javascript" >
 
  if (@ptnum==0) {
 
  if (@ptnum==0) {
 
   addpoint(0, {0,3,0});
 
   addpoint(0, {0,3,0});
Line 25: Line 25:
 
But back to exploiting the parallelism of vex. Rather than setting the location of the new point manually, we could do it relative to the current point being processed. Eg, to make a new point that sits 5 units above every point (make sure you remove the 0 from the group field if you haven't already):  
 
But back to exploiting the parallelism of vex. Rather than setting the location of the new point manually, we could do it relative to the current point being processed. Eg, to make a new point that sits 5 units above every point (make sure you remove the 0 from the group field if you haven't already):  
  
<source>
+
<source lang="javascript" >
 
  addpoint(0, @P + {0,5,0});
 
  addpoint(0, @P + {0,5,0});
 
</source>
 
</source>
Line 33: Line 33:
 
We could create a point that sits 4 units away in the direction of the normal:
 
We could create a point that sits 4 units away in the direction of the normal:
  
<source>
+
<source lang="javascript" >
 
  addpoint(0, @P+ @N * 4);
 
  addpoint(0, @P+ @N * 4);
 
</source>
 
</source>
Line 39: Line 39:
 
We can combine what we learned about for loops, and run over each point 10 times, generating a new point each time that sits 0.1 units further away from the original position:
 
We can combine what we learned about for loops, and run over each point 10 times, generating a new point each time that sits 0.1 units further away from the original position:
  
<source>
+
<source lang="javascript" >
 
  for (int i = 0; i<10; i++) {
 
  for (int i = 0; i<10; i++) {
 
   addpoint(0, @P+ @N *(i*0.1));
 
   addpoint(0, @P+ @N *(i*0.1));
Line 45: Line 45:
 
</source>
 
</source>
  
So to explain that a little, the loop starts a counter, i, that is initialised at 0. It checks if its less than 10, if it is, it runs the loop, and after the loop is run, adds 1 to i. Within the loop, it multiplies @N times 0 * 0.1. So the first iteration its @N times 0, so its a new point that sits directly on the original point. The next time its @N times 1 * 0.1, so it moves 0.1 units along the normal. Next loop its @N times 2 * 0.1, so it moves 0.2 units along the normal, and so on.
+
So to explain that a little, the loop starts a counter, i, that is initialised at 0. It checks if its less than 10, if it is, it runs the loop, and after the loop is run, adds 1 to i. Within the loop, it multiplies @N times i * 0.1. So the first iteration its @N times 0, so its a new point that sits directly on the original point. The next time its @N times 1 * 0.1, so it moves 0.1 units along the normal. Next loop its @N times 2 * 0.1, so it moves 0.2 units along the normal, and so on.
  
 
[[File:joyofvex14_addpoint_dotlines.gif]]
 
[[File:joyofvex14_addpoint_dotlines.gif]]
Line 59: Line 59:
 
Taking the first example, if we wanted to join every point on the grid to a single point at {0,3,0}:
 
Taking the first example, if we wanted to join every point on the grid to a single point at {0,3,0}:
  
<source>
+
<source lang="javascript" >
 
  int pt = addpoint(0,{0,3,0});
 
  int pt = addpoint(0,{0,3,0});
 
  addprim(0,'polyline', @ptnum, pt);
 
  addprim(0,'polyline', @ptnum, pt);
Line 70: Line 70:
 
You could make a polyline along the normal:
 
You could make a polyline along the normal:
  
<source>
+
<source lang="javascript" >
 
  int pt = addpoint(0,@P+@N);
 
  int pt = addpoint(0,@P+@N);
 
  addprim(0,'polyline',@ptnum,pt);
 
  addprim(0,'polyline',@ptnum,pt);
Line 77: Line 77:
 
And move that top point with some noise to simulate a field of wheat. I limit the vertical movement so the wheat doesn't look like its scaling too much:
 
And move that top point with some noise to simulate a field of wheat. I limit the vertical movement so the wheat doesn't look like its scaling too much:
  
<source>
+
<source lang="javascript" >
 
  vector pos = @N+noise(@P+@Time)*{1,0.1,1};
 
  vector pos = @N+noise(@P+@Time)*{1,0.1,1};
 
  int pt = addpoint(0,@P+pos);
 
  int pt = addpoint(0,@P+pos);
Line 89: Line 89:
 
Lets go all the way, and generate multiple points along the normal, each are moved by slightly different noise, and joined into a line for more of a seaweed feel:
 
Lets go all the way, and generate multiple points along the normal, each are moved by slightly different noise, and joined into a line for more of a seaweed feel:
  
<source>
+
<source lang="javascript" >
 
  vector offset, pos;
 
  vector offset, pos;
 
  int pr, pt;
 
  int pr, pt;
Line 111: Line 111:
 
Here I've done something thats fairly common, which is to define all my variables up front. You can see that if you have multiple variables of a single type, you can define the type and then specify the names as a comma separated list, eg 'vector offset, pos, otherpos, mycolor;' etc. This also has the benefit of keeping the main code you're doing a little cleaner, as you don't have vector/float/int definitions cluttering things.
 
Here I've done something thats fairly common, which is to define all my variables up front. You can see that if you have multiple variables of a single type, you can define the type and then specify the names as a comma separated list, eg 'vector offset, pos, otherpos, mycolor;' etc. This also has the benefit of keeping the main code you're doing a little cleaner, as you don't have vector/float/int definitions cluttering things.
  
Before the main loop, I initialise the new prim and store its id as 'pr, and create a stepsize.  
+
Before the main loop, I initialise the new prim and store its id as 'pr', and create a stepsize.  
  
In the loop I create an offset based on curlnoise, and a position based on that offset and the normal, stepsize, and current i value. Then I define a point, and use a new thing, addvertex.
+
In the loop I create an offset based on curlnoise, and a position based on that offset and the normal, stepsize, and current i value. Then I define a point, and use a new thing, '''addvertex'''.
  
 
Why this way? Well, this is the older method for creating prims I mentioned earlier. You'd define an empty prim, then manually bind points to your prim with addvertex. Addvertex takes a prim id and a point id, and whatever order you add them in, defines the ordering of the sub-sections within the polyline.
 
Why this way? Well, this is the older method for creating prims I mentioned earlier. You'd define an empty prim, then manually bind points to your prim with addvertex. Addvertex takes a prim id and a point id, and whatever order you add them in, defines the ordering of the sub-sections within the polyline.
Line 129: Line 129:
 
To add a point you use the addpoint function. To remove a point you...
 
To add a point you use the addpoint function. To remove a point you...
  
<source>
+
<source lang="javascript" >
 
  removepoint(0,@ptnum);
 
  removepoint(0,@ptnum);
 
</source>
 
</source>
Line 135: Line 135:
 
To remove a prim is similar, but you also have tell it what to do with the points that make up the prim, so keep them around:
 
To remove a prim is similar, but you also have tell it what to do with the points that make up the prim, so keep them around:
  
<source>
+
<source lang="javascript" >
 
  removeprim(0,@primnum,1)
 
  removeprim(0,@primnum,1)
 
</source>
 
</source>
Line 141: Line 141:
 
or delete the points too:
 
or delete the points too:
  
<source>
+
<source lang="javascript" >
 
  removeprim(0,@primnum,0)
 
  removeprim(0,@primnum,0)
 
</source>
 
</source>
Line 147: Line 147:
 
The most useful application of this is a random delete (do this in a prim wrangle) :
 
The most useful application of this is a random delete (do this in a prim wrangle) :
  
<source>
+
<source lang="javascript" >
 
  if (rand(@ptnum) < ch('cutoff') ){
 
  if (rand(@ptnum) < ch('cutoff') ){
 
   removeprim(0,@primnum,1);
 
   removeprim(0,@primnum,1);
Line 157: Line 157:
 
You can add an extra parameter to rand, which makes it generate a new set of random numbers. Slide the 'seed' slider to see this in action:
 
You can add an extra parameter to rand, which makes it generate a new set of random numbers. Slide the 'seed' slider to see this in action:
  
<source>
+
<source lang="javascript" >
 
  if (rand(@ptnum, ch('seed')) < ch('cutoff') ){
 
  if (rand(@ptnum, ch('seed')) < ch('cutoff') ){
 
   removeprim(0,@primnum,1);
 
   removeprim(0,@primnum,1);
Line 191: Line 191:
 
[[File:joyofvex14_spiral_seaweed.gif]]
 
[[File:joyofvex14_spiral_seaweed.gif]]
  
<source>
+
<source lang="javascript" >
 
  // spirally seaweed using sin and cos
 
  // spirally seaweed using sin and cos
 
  vector offset, pos;
 
  vector offset, pos;

Latest revision as of 08:24, 21 May 2020

Create geometry

For once I promise not to do ramp falloffs or waves. :)

Vex can create points with the addpoint command. Like a lot of functions in vex you have to define the geometry to output to (always 0, ie, the first geo, ie 'this' geometry), and a position. It returns an integer, which represents a ptnum you can refer to later.

 int pt = addpoint(0, {0,3,0});

Running that on the grid will create what looks like a single point above the grid, at 0,3,0. But remember that this a point wrangle runs this code in parallel on all the points, so what you really have is a ton of points all bunched up at the same point. You can verify this by middle-mouse-button hold on the wrangle, and look at the number of points, vs the number of points in the original grid.

As a quick aside, you could collapse this down to a single point afterwards with a fuse sop, or try and control when the addpoint function runs, so it only fires on a single point. Eg:

 if (@ptnum==0) {
   addpoint(0, {0,3,0});
 }

Or better still, use the group field at the top of the wrangle to limit it to run on a single point (just type 0 into that field)

Joyofvex14 addpoint group cheat.gif

But back to exploiting the parallelism of vex. Rather than setting the location of the new point manually, we could do it relative to the current point being processed. Eg, to make a new point that sits 5 units above every point (make sure you remove the 0 from the group field if you haven't already):

 addpoint(0, @P + {0,5,0});

Note that here I'm not storing the returned value; I'm not doing anything with it, so I can ignore it.

We could create a point that sits 4 units away in the direction of the normal:

 addpoint(0, @P+ @N * 4);

We can combine what we learned about for loops, and run over each point 10 times, generating a new point each time that sits 0.1 units further away from the original position:

 for (int i = 0; i<10; i++) {
  addpoint(0, @P+ @N *(i*0.1));
 }

So to explain that a little, the loop starts a counter, i, that is initialised at 0. It checks if its less than 10, if it is, it runs the loop, and after the loop is run, adds 1 to i. Within the loop, it multiplies @N times i * 0.1. So the first iteration its @N times 0, so its a new point that sits directly on the original point. The next time its @N times 1 * 0.1, so it moves 0.1 units along the normal. Next loop its @N times 2 * 0.1, so it moves 0.2 units along the normal, and so on.

Joyofvex14 addpoint dotlines.gif

That's points. What about lines?

Addprim

Assuming you've read the points/verts/prims page by now, you should have an intuition that to create a polygon requires linking points together into a prim via verticies. Doing this in vex used to be exactly that, but in H16 some convenience functions were added, so you can just define a prim with points, vex will take care of the verts for you.

The function is addprim, here you tell it the geo reference to use (always 0), the type of prim as a string (often 'polyline'), and then a comma separated list of point id's.

Taking the first example, if we wanted to join every point on the grid to a single point at {0,3,0}:

 int pt = addpoint(0,{0,3,0});
 addprim(0,'polyline', @ptnum, pt);

That creates a point at {0,3,0}, and stores its ptnum as an integer pt.

Then it creates a polyline that goes from the current point (@ptnum) to the point we just generated (pt). Of course its making a new {0,3,0} point for every point in the grid, so again afterwards you'd want to fuse it to avoid having needless extra points floating around.

You could make a polyline along the normal:

 int pt = addpoint(0,@P+@N);
 addprim(0,'polyline',@ptnum,pt);

And move that top point with some noise to simulate a field of wheat. I limit the vertical movement so the wheat doesn't look like its scaling too much:

 vector pos = @N+noise(@P+@Time)*{1,0.1,1};
 int pt = addpoint(0,@P+pos);
 addprim(0,'polyline',@ptnum,pt);

Because this is based on @N, it should switch to the pig head or any geometry with normals and mostly work.

Joyofvex14 addprim wheat1.gif

Lets go all the way, and generate multiple points along the normal, each are moved by slightly different noise, and joined into a line for more of a seaweed feel:

 vector offset, pos;
 int pr, pt;
 float stepsize;
 
 pr = addprim(0,'polyline');
 stepsize = 0.5;
 
 for (int i = 0; i < 6;i++) {
   offset = curlnoise(@P-@Time+i)*0.2;
   pos = @P + @N * i * stepsize + offset;
   pt = addpoint(0,pos);
   addvertex(0,pr,pt);
 }

Joyofvex14 addprim seaweed.gif

There's a bit to unpack there!

Here I've done something thats fairly common, which is to define all my variables up front. You can see that if you have multiple variables of a single type, you can define the type and then specify the names as a comma separated list, eg 'vector offset, pos, otherpos, mycolor;' etc. This also has the benefit of keeping the main code you're doing a little cleaner, as you don't have vector/float/int definitions cluttering things.

Before the main loop, I initialise the new prim and store its id as 'pr', and create a stepsize.

In the loop I create an offset based on curlnoise, and a position based on that offset and the normal, stepsize, and current i value. Then I define a point, and use a new thing, addvertex.

Why this way? Well, this is the older method for creating prims I mentioned earlier. You'd define an empty prim, then manually bind points to your prim with addvertex. Addvertex takes a prim id and a point id, and whatever order you add them in, defines the ordering of the sub-sections within the polyline.

Other primitive types

Check out the helpfile for addprim:

http://www.sidefx.com/docs/houdini16.0/vex/functions/addprim

It tells you that there's quite a few choices other than 'polyline'. 'poly' will make a closed polygon, or you could use 'circle', and get a circle prim placed on the point you specify (you don't need the addvertex function here, prims like circle or spheres aren't connected to anything else, so just a point id is enough).

Remove Geometry

To add a point you use the addpoint function. To remove a point you...

 removepoint(0,@ptnum);

To remove a prim is similar, but you also have tell it what to do with the points that make up the prim, so keep them around:

 removeprim(0,@primnum,1)

or delete the points too:

 removeprim(0,@primnum,0)

The most useful application of this is a random delete (do this in a prim wrangle) :

 if (rand(@ptnum) < ch('cutoff') ){
   removeprim(0,@primnum,1);
 }

Rand generates a random number between 0 and 1. It's tested against a slider, and if the random value is less than the slider, the prim will be deleted.

You can add an extra parameter to rand, which makes it generate a new set of random numbers. Slide the 'seed' slider to see this in action:

 if (rand(@ptnum, ch('seed')) < ch('cutoff') ){
   removeprim(0,@primnum,1);
 }

Joyofvex14 removeprim.gif

Debugging vex

Something I noticed watching people go through this tutorial series; vex is awesome and makes you feel like a wizard when it works, but when you get errors can be a pain to work out why, especially when first starting out. A core problem is due to one of its key features; because its running in parallel on all points/prims, you can't easily do a 'print foo' debug statement, as you'll get 40,000 copies of that if you have 40,000 points.

Further, do a little research into multithreaded programming, it's known to be a tricky thing to deal with. Vex takes care of a lot of that complexity for you, but that has some caveats you should be aware of.

Here's a quick overview of vex debugging techniques, or things to watch for.

  • Find the real error in a text wall of errors - Sometimes when you get a syntax error and middle-click on the wrangle, you'll be presented with a huge error statement. Over time you get used to skipping all the boilerplate text, and zeroing in on the bit of text in the error that doesn't look like the rest. A key one to watch for is 'undeclared variable', which you start to spot faster and faster
  • missing brackets - vex being a C-derived language is bracket heavy, and being a language largely designed for 3d, means lots of vectors. This means you'll get a syntax error, scan it for 20 minutes, be convinced the code is perfect then notice you missed a closing ), or swapped a { for a (. A common one i miss is with if statements, I'll be testing against a channel and frequently miss the double bracket at the end, eg if ( foo > ch('testme') {
  • Syntax error line number - The error statements will usually give you a line and column reference, note that there's a row/col indicator in the lower right of the wrangle text field. Use it!
  • Syntax error off by 1 - Often the error will be on the line after the real problem, like missing a semicolon on the previous line.
  • Auto creation of attributes by mistake - Coding languages can be categorised as 'strict' or not, by how they let you create variables and functions. Vex in its pure form is mildly strict, but wrangles, for the sake of ease of use, aren't very strict at all. The classic example is to misspell an attribute name, using @dC rather than @Cd for example. Vex won't know that's a syntax error, and instead will just create a @dC attribute. Another common mistake is to use a local variable, eg 'float foo', and in your code start using @foo instead, which is an attribute. Again, no syntax error, just incorrect behaviour that can be hard to track down.
  • Not setting the data type on attributes - Another mistake I make too often; I'll have a wrangle that defines v@mycol, and in another wrangle later on I'll use @mycol. Vex can't detect the data type for attributes (it only knows about the handful of built in ones like P, Cd, v, orient etc), so it will be treated as a float, and just read the x (ie red) component of @mycol. Try to get in the habit of always using the data type prefix on attributes. I'm not in that habit, and frequently suffer for it. :)
  • Problems around equals signs - A single '=' is an assignment. A == in an if statement is a comparison for equality. A += is 'add something to the variable'. When typing at speed, its easy to mentally think 'ok, here I'm adding this variable', but your fingers type a single equals, and you'll go into a rage for 20 minutes trying to understand why your code isn't working. If in doubt, double check anywhere you've put down an equals sign, and make sure you haven't mistyped the wrong thing.

Exercises

  1. The seaweed example above doesn't lock the roots in place, fix that. You want to multiply the offset by 0 at the start of the loop, and gradually ramp it up until it has a large effect at the end. If only there were some counter, starting from 0, that you could multiply by a small fraction, that you could use for this...
  2. Make the seaweed move in very computery circles, offset slightly from each other (gif and answer is below, look at the gif first, try and avoid the code. Hint: You'll be moving each point's x and z position based on sin and cos...)
  3. colour the lines from root to tip somehow
  4. take the stocastic slider based prim delete example from above, change its mode to a point wrangle, and make it remove points instead.
  5. rand is pure white-noise static, sometimes you want more structure for the delete trick. try and make the delete work based on noise. Note that noise() doesn't take a seed variable, but you'll probably want controls to scale the size of the noise, and to offset it, maybe even animate the noise over time.

Joyofvex14 spiral seaweed.gif

 // spirally seaweed using sin and cos
 vector offset, pos;
 int pr, pt;
 float stepsize;
 float x, y, inc;
 pr = addprim(0,'polyline');
 stepsize = 0.5;
 
 for (int i = 0; i < 6;i++) {
  inc = @ptnum*0.3;
  x = cos(i*0.6+@Time+inc);
  y = sin(i*0.6+@Time+inc);
  offset = set(x,0,y)*0.1*i;
  pos = @P + @N * i * stepsize + offset;
  pt = addpoint(0,pos);
  addvertex(0,pr,pt);
 }

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