Scale Groom by Clusters

This is a technique I used for a sheep groom asset some time ago. I wanted to create cracks in the fur after the groom was already in the final stage. Since the groom is very scraggly and noisy, creating parting lines before clumping didn’t do the trick. The parting lines would be washed out at the end of the graph.

Back then, I created a hacky setup inside a foreach loop that would scale each cluster based on voronoi fracture. It worked, but it was painfully slow to cook for a heavy groom like the sheep. Adding any variation would create a mess that would be difficult to read to other users.

Now that I’ve learned more about matrices, I started understanding how this could be written in VEX. What caused me issues was figuring out how to scale a matrix based on a given axis. I’m still learning a more elegant way to solve this problem. Here I’ve used three different matrices to create the scaling.

The first step is calculating a difference matrix between the target (identity matrix aligned to the world XYZ) and the current point transform matrix. Multiplying the cluster by this difference matrix translates the cluster to the origin and aligns it to the world XYZ axes.

The second step is to create a scaling matrix which is an identity matrix scaled on the world x and z axes, leaving world y set to 1. Multiplying the cluster by this matrix scales the cluster by the axis values.

The third step is multiplying the cluster with the inverse of the difference matrix to move it back into its original location.

This is the foundation of the setup. It’s wrapped into a primitive wrangle which leaves the root points untouched and blends the result along the length of the curve. This gives sort of a clumping result without modifying each hair’s original shape too much. The only difference to each hair shape is that they are squished from the scaling operation.

Groom scaled towards the center of each cluster.

Groom before cluster scaling.

Create transform matrix for scattered points:

//point wrangle
//input scattered points
//make sure input has N attrib from skin

vector N = v@N;
vector up = {0,1,0};

vector tangent = normalize(cross(N,up));

4@xform = maketransform(tangent,N,v@P);

Scale clusters:

//primitive wrangle
//groom first input
//scattered points second input

int pts[] = primpoints(0,@primnum);
vector rootP = point(0,"P",pts[0]);

vector search_noise = noise(rootP*chf("search_noise_freq")*10);

search_noise = fit01(search_noise,-1,1);

search_noise *= chf("search_noise_amp");
vector searchP = rootP + search_noise;

int npt = nearpoint(1,searchP);

matrix xform_npt = point(1,"xform",npt);
matrix ident = ident();

matrix difference = invert(xform_npt)*ident;

matrix scale = ident();

float scale_x = chf("cluster_scale_x");
float scale_z = chf("cluster_scale_z");
vector scale_vec = set(scale_x,1,scale_z);

scale(scale,scale_vec);

for ( int i=1; i<len(pts); i++ ){

    float curveu = point(0,"curveu",pts[i]);
    float blend = chramp("blend_ramp",curveu);
    
    vector pos = point(0,"P",pts[i]);
    vector old_pos = pos;
    
    pos *= difference;
    pos *= scale;
    pos *= invert(difference);
    
    pos = old_pos*blend + pos*(1-blend);
    
    setpointattrib(0,"P",pts[i],pos);
    
}
Previous
Previous

Simple Clumping Using VEX

Next
Next

Detangle Solver