Difference between revisions of "JoyOfVex14"

From cgwiki
Line 46: Line 46:
  
 
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 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.
 +
 +
[[File:joyofvex14_addpoint_dotlines.gif]]
  
 
That's points. What about lines?
 
That's points. What about lines?

Revision as of 04:27, 13 March 2018

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 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.

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.

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);
 }

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);
 }

Debugging vex

Explain all this more in depth:

  • error statements can be big walls of text, quickly filtering to the interesting bit ( eg 'undeclared variable')
  • missing brackets is a constant pain, common one i miss is testing against a channel, miss the double bracket at the end, eg if ( foo > ch('testme')) {
  • line and column numbers in error statements can give clues, note the line/col marker in the wrangle editor
  • often the error will be on the line after the real problem, like missing a semicolon on the previous line
  • insidious errors like mistyping a @dC, which will then define a new attrib rather than trigger a syntax error that you misspelled @Cd
  • casting mistakes, like using @myvector when you should explicitly tell the wrangle that its a vector with the prefix: v@myvector

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 (answer is below, involves sin and cos and stuff)
  3. colour the lines from root to tip somehow
  4. 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.
 // spirally seaweed
 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