Ben Traje
← Back to modelling

Houdini VEX: How to Flatten or Align Points to a Master Reference Group

04 Sep 26 (6d ago)

When building procedural modeling setups, ground alignment tools, or mechanical rig offsets in SideFX Houdini, you frequently need to snap or level geometry against a specific anchor point selected by an artist or upstream procedural rule.

Using an explicit point group (like master_sel) lets you decouple your VEX logic from hardcoded point numbers (@ptnum == 0). Even if upstream topology changes and point counts shuffle, the reference logic remains completely intact.

Here is an analysis of how expandpointgroup() works in a Point Wrangle, a major performance optimization to keep in mind, and how to expand the snippet into full 3D alignment.

The Core VEX Pattern

This snippet queries all points inside the master_sel group, takes the first available index as the master anchor, and blends each evaluated point’s vertical position ($Y$) toward that master point's height.

// Run over: Points
// Retrieve an array of all point numbers belonging to 'master_sel'
int master_pts[] = expandpointgroup(0, "master_sel");

// Guard against empty groups to prevent out-of-bounds indexing
if (len(master_pts) > 0) {
    // Select the first point in the group as our master anchor
    int master_pt = master_pts[0];
    
    // Extract the spatial coordinates of the master point
    vector master_pos = point(0, "P", master_pt);
    
    // User parameter to control blend strength (0.0 = untouched, 1.0 = fully flattened)
    float blend = chf("blend_amount");
    
    // Smoothly interpolate current Y elevation toward the master's elevation
    @P.y = lerp(@P.y, master_pos.y, blend);
}

⚠️ Performance Check: The Per-Point Evaluation Cost

While this script works cleanly on small models, there is an important performance consideration for large meshes:

A Point Wrangle runs its entire code block in parallel for every single point on your geometry. If your mesh has 500,000 points:

  1. expandpointgroup(0, "master_sel") allocates an array and evaluates the group membership table 500,000 times.
  2. point(0, "P", master_pt) queries the attribute dictionary 500,000 times for the exact same coordinate.

The High-Performance Alternative: Detail Pre-Pass

If you are working on dense meshes, extract the master coordinate once in a Detail Wrangle before your Point Wrangle, or store it as a detail attribute:

// STEP 1: Detail Wrangle (Run Over: Detail)
// Reads the master point once for the entire geometry
int master_pts[] = expandpointgroup(0, "master_sel");
if (len(master_pts) > 0) {
    setdetailattrib(0, "master_P", point(0, "P", master_pts[0]), "set");
}

// STEP 2: Downstream Point Wrangle (Run Over: Points)
// Pure parallel position update with zero array overhead
vector master_pos = detail(0, "master_P", 0);
float blend = chf("blend_amount");

@P.y = lerp(@P.y, master_pos.y, blend);

Expanding Beyond Height: Full 3D Snapping & Axis Masking

If you want to use this master reference point to align more than just elevation ($Y$), you can parameterize which axes get affected using a toggle vector in VEX:

// Run over: Points
int master_pts[] = expandpointgroup(0, "master_sel");

if (len(master_pts) > 0) {
    vector master_pos = point(0, "P", master_pts[0]);
    float blend = chf("blend_amount");
    
    // Vector toggles to independently choose axes to lock (e.g., {0, 1, 0} for Y only)
    vector axis_mask = chv("axis_mask"); 

    vector target_pos = @P;
    
    if (axis_mask.x > 0.5) target_pos.x = master_pos.x;
    if (axis_mask.y > 0.5) target_pos.y = master_pos.y;
    if (axis_mask.z > 0.5) target_pos.z = master_pos.z;

    @P = lerp(@P, target_pos, blend);
}

This setup provides an intuitive leveling and projection workflow: pick any vertex on your model, assign it to master_sel via a Group SOP, and instantly level, center, or align arbitrary mesh regions relative to that single reference anchor.