Ben Traje
← Back to modelling

How to Cull Overlapping Points in Houdini Using VEX and Point Clouds

31 Aug 26 (2mo ago)

When scattering assets such as foliage, rocks, and debris in SideFX Houdini, managing intersections is essential. Whether culling points inside colliders or preventing instances from overlapping one another, point cloud functions in VEX offer an efficient, multithreaded solution.

1. Multi-Input Culling: Removing Points Inside an Exclusion Zone

To delete points from a primary source (e.g., leaves in Input 0) when they fall within the radius of points from a secondary source (e.g., rocks in Input 1), use the pcfind function.

// Run on: Points
// Input 0: Leaf points
// Input 1: Rock points

float radius = chf("rock_radius"); 

// Look for rock points within radius of the current leaf point
int close_rocks[] = pcfind(1, "P", @P, radius, 1);

// If at least one rock is detected inside the threshold, remove the leaf
if (len(close_rocks) > 0) {
    removepoint(0, @ptnum);
}

Supporting Variable Radius (@pscale)

To adjust the exclusion zone based on the secondary point scale:

int close_rocks[] = pcfind(1, "P", @P, chf("max_search_radius"), 5);

foreach (int rock_pt; close_rocks) {
    float r_scale = point(1, "pscale", rock_pt);
    if (distance(@P, point(1, "P", rock_pt)) < r_scale) {
        removepoint(0, @ptnum);
        break;
    }
}

2. Single-Input Self-Culling: Preventing Instance Overlaps

When points within the same stream are scattered too closely together, a naive radius check can cause "mutual destruction," where both overlapping points delete each other. Comparing @ptnum resolves this conflict deterministically.

// Run on: Points
// Input 0: Scattered points

float radius = chf("min_dist");

// Search for up to 2 points (the point itself + the closest neighbor)
int neighbors[] = pcfind(0, "P", @P, radius, 2);

// Delete the point only if the neighbor has a lower point number
foreach (int pt; neighbors) {
    if (pt < @ptnum) {
        removepoint(0, @ptnum);
        break;
    }
}

3. Variable Self-Culling with Non-Uniform @pscale

If scattered points possess varying @pscale values, use the combined collision radii of neighboring points:

// Run on: Points
// Input 0: Scattered points with @pscale

float my_r = @pscale;
int neighbors[] = pcfind(0, "P", @P, my_r * 2.0, 10);

foreach (int pt; neighbors) {
    if (pt < @ptnum) {
        float neighbor_r = point(0, "pscale", pt);
        float dist = distance(@P, point(0, "P", pt));
        
        // Remove if the distance is less than the sum of both radii
        if (dist < (my_r + neighbor_r)) {
            removepoint(0, @ptnum);
            break;
        }
    }
}

Summary Checklist

TaskMethodKey Function / Logic
Collider / Exclusion Zone2-Input Wranglepcfind(1, "P", @P, radius, 1)
Self-Culling (Fixed Radius)1-Input Wranglepcfind(0, ...) + pt < @ptnum
Self-Culling (Dynamic Radius)1-Input Wrangledistance(@P, neighbor_P) < (@pscale + neighbor_pscale)