• Matt Norris

Procedural Cleanup with Vex

Updated: Feb 25, 2020




So here's my first snippet of hopefully useful VEX!


This is a particular problem I've bumped into on many occasions and I've gradually developed a process for dealing with it that is fairly quick and simple so I thought I'd share what I've learned and hopefully help some other vexers out there.



download the .hip file here



So What's the problem?



Whenever you are procedurally generating geometry you run the risk of getting little bits of left over geometry hanging around that you don't want, for example when I'm doing a sim that requires collision with an animated character such as hair or cloth I often procedurally re-mesh the animation cache to get a more even mesh and remove any unwanted spiky bits that might get in the way. The problem is you often end up with the end of a little finger or some other superfluous chunk of geometry hanging around that isn't actually connected to the main mesh, isn't really needed for the collision and is basically just a massive headache.


So wouldn't it be nice to be able to procedurally remove all small, awkward bits of geo without having to manually select faces and delete stuff based on point numbers. Especially as you want to ideally be creating a flexible setup which will give reliable results with a range of different inputs.


So I've built a very robust setup which is fast and reliable and will remove all those annoying little clumps of geo that mess up your collisions.


Furthermore this setup will also work with basically any geometry setup that contains small bits of disconnected geometry so can be very useful for a range of situations.



The Solution


What I like about this setup is that it uses some of the VEX array functions, pretty basic stuff but powerful if you know what it can do.


So the gist is that you lay down a connectivity SOP, and create a point attribute ("class") for each of the different connected parts, so far so good, nothing too exciting there. What we want to do next is the slightly clever bit, basically count all the points for each different value of "class", pipe that data into an array, then use it to identify the small fragments and delete them.


One thing I'm doing with this setup that might seem a bit odd is splitting the process into 2 distinct sections. The first part is building an array based on the "class" attribute, the second part is where I check point values against the array and delete unwanted geo.

The reason I split it up is because I want to do my array creation in an attribute wrangle set to detail (single iteration), yet do my check and delete in a point wrangle as I will need to loop over every point to do so.




The Build


In my example scene I'm making some random procedural geometry, nothing fancy but it's enough to test the setup. It's also another situation where you can regularly encounter this problem, say you are scattering some procedurally generated debris around an environment you'll probably want to ensure your geometry is fragment free before instancing it.


Anyway, here's the setup:



Very basic setup designed as a proof of concept.


-scatter some points

-assign colour based on 3D noise

-delete some points

-mesh using fluidSurface


This is 100% guaranteed to give you some very ugly geometry, with fragments and slivers and weird spiky bits knocking about that will definitely need cleaning up.





Here's a little gif of the build stages:





The Solution


Here's a quick network view of the solution:




-Connectivity SOP adds a point attrib @class


-attrib wrangle set to detail which builds an array


-point wrangle which checks array and deletes unwanted geo






Here's a screengrab of the detail wrangle:



So nothing too crazy going on here, the only thing that might need clarifying is what I'm doing with the array, I'm basically looping over every point, storing the @class value for that point, then adding 1 into the value of the array at the index of @class.

What this does is give me an array where every index of the array represents a value of @class, and every value of the array represents number of points which have that value for @class.


Here's a screen grab of what that array looks like (you might need to squint a bit):


so what we're seeing (if you can read my tiny screen grab) is an array where each index is a value of @class and the value of each index is the number of points. So, there are:

8 points with @class==0;

16 points with @class==1;

8 points with @class==2;

And so on.


Here's the code:


  1. int pnts = npoints(0); // get total number of points in input geo

  2. int i; // create uncast integer variable

  3. int class_array[]; // create uncast integer array

  4. for(i = 0; i < pnts; i ++){ // loop over all points

  5. int class = point(0, "class", i); // get @class value for each point

  6. class_array[class] += 1; // increment class_array value at index @class

  7. }

  8. i[]@class_array = class_array; // bind array to detail


Next Step


Cool, so far so good, now all we need to do is run over the points and check to find points who's @class value has a number of points below a user defined threshold.


Here's a screen grab of the point wrangle 'clean_array'


Pretty simple stuff, all I need to do is for each point, go and check the array to see how many points share it's value of @class. If that number of points is below my specified threshold (in this case 50) then I remove those points.


Code:


  1. int class_array[] = detail(0, "class_array"); // read in the int array from the detail

  2. if(class_array[i@class] < chi("min_value")){ // look up the value of the array where the index == the point @class value

  3. removepoint(0, @ptnum); // if the array value is less than user defined minimum, remove point

  4. } // job's a good 'un






An Alternative


So the process I outlined above gives the user the option to state a lower threshold or minimum number of points they want to keep, this can be useful for a lot of situations and with some very minor tweaks you could also include an upper threshold if for whatever reason you wanted isolate some of the 'middling' bits of the geometry. However I wanted to share an alternative method here which will exclusively return the largest piece of geometry and delete all the rest. More often than not this is the result we really want, plus this method is extremely reliable and can be safely implemented into complex procedural scripts with minimal human intervention (which is what we like). It's also quite fun as it introduces one of VEX's useful array functions 'argsort()'.



Here's a screen grab of the alternate version of 'create_detail_array' wrangle:


Here's the code:


  1. int pnts = npoints(0); // get total number of points in input geo

  2. int i; // create uncast integer variable

  3. int class_array[]; // create uncast integer array

  4. for(i = 0; i < pnts; i ++){ // loop over all points

  5. int class = point(0, "class", i); // get @class value for each point

  6. class_array[class] += 1; // increment class_array value at index @class

  7. }

  8. i[]@class_array = class_array; // bind array to detail

  9. int arg_sort_array[] = argsort(i[]@class_array);// create new array of sorted indeces

  10. i@max_class_array = arg_sort_array[-1]; // bind the last/highest value of your arg-sorted array


So what the 'argsort()' function does is to take an existing array as input, then return a brand new array which lists the indices of the input array in a sorted order, based on the values of the input array. In other words, if I have an array:


pseudocode:

  1. my_array = [0,100,30,10];

  2. arg_sort_array = argsort(my_array);

  3. print arg_sort_array;

  4. // [0,3,2,1]

So my new array puts index0=0 because in my first array my first argument is the lowest.

it puts index1=3, because in my first array 10 is the next lowest.

it puts index2=2, because in my first array 30 is the next lowest.

it puts index3=1, because in my first array 100 is the highest.



So the final step will be to create a point wrangle and check our points against the @max_class_array value:


screen grab of the 'clean_array' point wrangle


The code:


  1. int target = detail(0, "max_class_array"); // read in detail attribute

  2. if(i@class != target){ // compare current point's @class value

  3. removepoint(0, @ptnum); // if comparison is false remove the point

  4. }

the end result.....




So Clean, so pure....




Thanks for checking out my blog, I hope you found something usefull here and stay tuned for more overly engineered, overly giffified blogs. And remember......


Don't get mad.


Get Vexed




download the .hip file here


1,106 views0 comments

Recent Posts

See All