Resampling curves using VEX part 1: Constant segment count

There's much to cover on different ways of resampling curves, so I'll create a small series about this topic. This post will walk you through resampling curves by a constant segment count using VEX. The second part will be about resampling curves using a maximum segment length, and the third will be about resampling curves varying the segment length along the length of the curve. I have plans to extend this series further, depending on if I can figure out more useful and elegant ways to resample curves. Anyway, let's build this script.

Shifting the focus to a single curve is usually the best first step for groom tools. Let's think of rebuilding a single curve. A curve consists of a single primitive and more than one point. To construct a curve, we need a couple of simple actions:

  • Create a primitive

  • Go through each point of the original curve, read the point position, and create a new point to this position.

  • Add the newly created point to the primitive as a vertex.

What this will do is create an exact copy of the input curves. To avoid creating a duplicate, we need to remove the original primitive from the stream.

//primitive wrangle
int pts[] = primpoints(0,i@primnum);

int new_prim = addprim(0,"polyline"); 

for ( int i=0; i<len(pts); i++ ){
     
    vector pos = point(0,"P",pts[i]);
    int new_pnt = addpoint(0,pos);
    
    addvertex(0,new_prim,new_pnt);
}

removeprim(0,i@primnum,1);

This script is a good base to build upon. The only thing left to alter is how to read more or fewer positions along the curve. But how can we read, let's say, ten positions on a curve that only has five points? To do this, we can use the primuv() VEX function. This function allows reading attributes on a primitive seamlessly. I will briefly clarify how this function works for reading attributes from curve primitives. For a more detailed breakdown of the primuv() function, I suggest reading this post on Toadstorm Nerdblog: The joy of xyzdist() and primuv()

In Houdini, a primitive has a barycentric UV coordinate. This coordinate is a two-dimensional vector. However, since we're looking at a curve rather than a surface, it only uses the first component (U). At the root of the primitive curve, this vector attribute has values: {0,0}. At the tip of the primitive curve, the values are: {1,0}. The values in the middle of the curve are something in between. We can use this barycentric UV coordinate to sample any attribute on a curve. To demonstrate what this means, I'll run a script in a primitive wrangle that creates a new point to the position of the barycentric U coordinate.

To turn this into a resample script, we can use a for-loop and increment this barycentric UV coordinate on each step. Now the script looks like this:

We can translate this technique into the resample script we started earlier:

//primitive wrangle

int count = chi("count");

int new_prim = addprim(0,"polyline"); 
vector uv = {0,0};

for ( int i=0; i<=count; i++ ){

    uv.x = i/float(count);

    vector pos = primuv(0,"P",i@primnum,uv);
    int new_pnt = addpoint(0,pos);
    
    addvertex(0,new_prim,new_pnt);
}

removeprim(0,i@primnum,1);

This script will resample the input curves wired into the first stream by a constant segment count. We can easily add fancy features like random resample segment count per curve:

//primitive wrangle

int min_segs = chi("min_segment_count");
int max_segs = chi("max_segment_count");
    
float rand = rand(i@primnum + chi("seed"));
rand = fit01(rand,min_segs,max_segs);
rand = rint(rand);

int count = int(rand);

int new_prim = addprim(0,"polyline"); 
vector uv = {0,0};

for ( int i=0; i<=count; i++ ){

    uv.x = i/float(count);
    
    vector pos = primuv(0,"P",i@primnum,uv);
    int new_pnt = addpoint(0,pos);
    
    addvertex(0,new_prim,new_pnt);
}
removeprim(0,i@primnum,1);

Following up in the next post I’ll describe how to turn this into “maximum segment length“ resample script.

Previous
Previous

Resampling curves using VEX part 2: Maximum segment length

Next
Next

Creating custom groom noise functions using VEX