Weighted Selection in VEX

For a project that I’ve been working on, I needed to assign arbitrary string attribute values to a bunch of scattered points. Let’s say these points would have an attribute called s@variant_types. Each point would get a value based on a list of custom names.

One way to do it would be to use an Attribute Randomize SOP. What’s cool about this is that it gives the user the ability to assign weights to distribute the values unevenly. 

The problem with this is that I want to use colors to identify the values assigned to variant types. Sure, it is possible to assign colors using a color SOP after the Attribute Randomize SOP, setting the Color Type to Random by Attribute. However, my limitation here is that I want to have specific colors for my variant types as well as control over their weights.

My solution to unify color, weight, and the variant types is, as expected, to have a Wrangle in detail mode where I create custom arrays with arbitrary values through the following code:

				
					int variant_types = chi("variant_types");
s[]@variant_types;
v[]@colors;
f[]@weights;
for(int i = 0; i<variant_types; i++){
    string variant_type = chs("variant_name_"+itoa(i));
    vector color = chv("color_"+itoa(i));
    float weight = chf("weight_"+itoa(i));
    append(@variant_types, variant_type);
    append(@colors, color);
    append(f@weights, weight);
    }
				
			

If you look closely, you can see that this is intended to be a multiparm. This way, I get to have a custom interface with variant name, color, and weight parameters, and I can add as many entries as I need. But this Wrangle is not connected to the main geometry. It simply acts as a master list of data that will later be applied to the scattered points as part of a two-stage procedure. This Wrangle only contains the first step.

To be able to read from the master list, we need a second Wrangle set to points. The first input should be wired to the scattered points and the second input to the initial Wrangle in Detail mode. I like to use IDs on my points for these operations, so right before the Point Wrangle, I gave IDs to my points using an Enumerate SOP.

As for the code itself, I read the arrays from the initial Wrangle, and then I use the @weight attribute to create an integer selector to assign the values from the initial arrays. Once more, here’s the VEX code:

				
					int seed = chi("seed");
string variant_types[] = detail(1, "variant_types", 0);
vector colors[] = detail(1, "colors", 0);
float weights[] = detail(1, "weights", 0);
//weight calc
float total = 0;
foreach (float w; weights){
    total += w;
    }
float rand = random(@id+seed);
float acc = 0;
int selector = 0;
for(int i = 0; i<len(variant_types); i++){
    acc += weights[i]/total;
    if(rand<=acc){
        selector = i;
        break;
        }
    }
s@variant_type = variant_types[selector];
@Cd = colors[selector];
				
			

As you can see, I can now have the desired variants, in this case, birds, with their corresponding colors on my points. 

And now, thanks to the multiparm and the seed in the second Wrangle, we can easily alter the distribution and weights of each variant.

Evidently, this is just a foundation that can be expanded and made more sophisticated, but it’s a good base to build upon more complex systems to distribute values onto arbitrary geometry. I hope this helps you if you ever need to do something like this.

Thank you for reading, and until next time!

Leave a Reply

Your email address will not be published. Required fields are marked *