Houdini RBD Packed Geometry and Constraint Networks

Intrinsic Attributes for Packed Geometry

Packed geometry in Houdini exposes several intrinsic attributes that control transform behavior. When creating a Grid and Sphere, then using a Copy node to replicate spheres onto the grid with Pack Geometry Before Copying enabled, moving points via an Edit node stores transform data in the intrinsic:packedfulltransform attribute (16-element matrix).

The intrinsic:packedfulltransform attribute is read-only and cannot be directly modified. However, you can manipulate intrinsic:transform (a 3x3 matrix controlling rotation, scaling, and skew) along with position data (the P attribute). Modifications to these properties automatically update intrinsic:packedfulltransform.

Scale Manipulation Example

Using a Copy node to instance multiple boxes with Pack Geometry Before Copying enabled, connect a Wrangle node with the following VEX:

1 vector scale = fit01(vector(rand(@primnum)), 0.2, 1);
2 matrix3 trn = primintrinsic(0, "transform", @primnum);
3 matrix scalem = maketransform(0, 0, {0,0,0}, {0,0,0}, scale, @P);
4 trn *= matrix3(scalem);
5 setprimintrinsic(0, "transform", @primnum, trn);

Collision Guide Geometry Issue

When using packed objects in DOP with RBD simulation, scaled geometry can cause collision detection problems. The Guide Geometry used for collisions references the initial frame state rather than the current scaled state. Even with "Show Guide Geometry" enabled in the RBD Packed Object, the actual collision bounds remain incorrect.

To resolve this, add the following code to force recalculation of collision geometry:

1 vector scale = chf("scale");
2 matrix3 trn = priminrtinsic(0, "transform", @primnum);
3 matrix scalem = maketransform(0, 0, {0,0,0}, {0,0,0}, scale, @P);
4 trn *= matrix3(scalem);
5 setprimintrinsic(0, "transform", @primnum, trn);
6
7 int pts[] = primpoints(0, i@primnum);
8 setpointattrib(0, "id", pts[0], -1);

Setting the ID attribute to -1 forces the DOP system to recompute collision geometry. This workaround is confirmed by SideFX developers as the current solution.


Constraint Network Node

The Constraint Network node creates connections between objects using polygon edges. Each edge represents a constraint, with its two endpoints serving as anchors.

When a constraint breaks (due to sufficient force), the affected primitives move into a group named broken. The simulation engine ignores all constraints in this group. Currently, only the Bullet solver can break Glue Constraints.

Constraint Properties by Type

Linear Constraints: force, distance

Angular Constraints: torque, angle

Glue Constraints: impact


Anchor Types

Each point in a constraint network represents an anchor, with constraints formed by pairs of anchors. Four anchor types exist, distinguished by name, anchor_type, and anchor_id attributes.

World Space Anchor

A single point-based anchor positioned in world space. If no name attribute exists, or if the specified name doesn't reference a valid RBD packed object, the anchor defaults to World Space type.

Relative Offset Anchor

Positioned relative to an RBD packed object. The anchor maintains its offset from the object's initial rotation. When name references a valid RBD packed object and anchor_id is -1 or undefined, this type applies.

Point Anchor

Binds to a specific point on a simulated object. With name referencing a valid RBD packed object and anchor_id containing a valid point index, the anchor attaches to that point. If anchor_type is set to vertex, the anchor binds to the vertex's corresponding point. For simulations with changing topology, add anchor_pid and anchor_vid attributes to maintain correct binding.

Agent Anchor

Binds to an agent's transform. Refer to Houdini documentation for agent-specific behavior.


Constraint Attributes

condof: Integer point attribute defining constraint degrees of freedom (0-3).

condir: Vector point attribute. With 1 degree of freedom, this defines a normal plane for rotation/translation. With 2 degrees of freedom, this defines an axis for rotation/translation.

These attributes are demonstrated in the Destruction in Houdini tutorial files.


ConnectAdjacentPieces Workflow

Adjacent Pieces from Points Method

connect_nearby_points (Point Wrangle):

void createline(int pt_a; int pt_b)
{
    int prim = addprim(0, "polyline");
    addvertex(0, prim, pt_a);
    addvertex(0, prim, pt_b);
}

int contains(string list[]; string item)
{
    foreach(string str; list)
    {
        if (item == str)
            return 1;
    }
    return 0;
}

int handle = pcopen(0, "P", @P, ch("../searchradius"),
                    chi("../maxsearchpoints"));
int max_connections = chi("../maxconnections");
string other_name;
string known_pieces[];
int num_connections = 0;
string my_name = s@name;

while (pciterate(handle) && num_connections < max_connections)
{    
    pcimport(handle, "name", other_name);

    if ((my_name != other_name) &&
        (num_connections == 0 || !contains(known_pieces, other_name)))
    {
        vector other_P;
        pcimport(handle, "P", other_P);

        if (dot(other_P - @P, @N) >= 0)
        {
            int other_ptnum;
            pcimport(handle, "point:number", other_ptnum);
            
            createline(@ptnum, other_ptnum);
            ++num_connections;
            if (num_connections < max_connections)
                push(known_pieces, other_name);
        }
    }
}

pcclose(handle);

create_ordered_point_pairs (Primitive Wrangle):

int pt0 = @ptnum;
int pt1 = vertexpoint(0, vertexindex(0, @primnum, 1));
s@point_pairs = sprintf("%s-%s", min(pt0, pt1), max(pt0, pt1));

remove_duplicates (Primitive Wrangle):

string my_point_pair = prim(1, "point_pairs", @primnum);
int index = findattribval(1, "primitive", "point_pairs", my_point_pair);
if (index != @primnum)
    removeprim(0, @primnum, 0);

Adjacent Pieces from Surface Points Method

consolidate_points (Point Wrangle):

int index = findattribval(0, "point", "name", s@name);
if (@ptnum != index)
{
    int v = pointvertex(0, @ptnum);
    while (v != -1)
    {
        setprimvertex(0, -1, v, index);
        v = vertexnext(0, v);
    }
    
    removepoint(0, @ptnum);
}
else
{
    @P = v@centroid;
}

Adjacent Points Method

create_explicit_lines (Point Wrangle):

float searchradius = ch("../searchradius");
int haspscale = haspointattrib(0, 'pscale');

int points[];
float @pscale = 1.0;

if (haspscale && !chi("../uniformradius"))
{
    points = pcfind_radius(0, "P", "pscale", searchradius, @P, searchradius*@pscale, chi("../maxsearchpoints"));
}
else
{
    points = pcfind(0, "P", @P, 2*searchradius*@pscale, chi("../maxsearchpoints"));
}

foreach(int ptj; points)
{
    if(@ptnum == ptj)
        continue;
        
    if (ptj < @ptnum)
        continue;
        
    int prim = addprim(geoself(), "polyline");
    
    addvertex(geoself(), prim, @ptnum);
    addvertex(geoself(), prim, ptj);
}

Converting RBD Packed Primitives in DOP with VEX

The following code, originating from SideFX, synchronizes packed geometry transforms between SOP and DOP networks:

#include "math.h"

if(@Frame<15)
{
    float timestep = 1/24.0;
    
    matrix target_xform = primintrinsic(1, "packedfulltransform", @primnum);
    matrix initial_xform = primintrinsic(0, "packedlocaltransform", @primnum);
    matrix cur_xform = primintrinsic(0, "packedfulltransform", @primnum);
    matrix offset = invert(cur_xform) * target_xform;
    matrix total_offset = invert(initial_xform) * target_xform;
    
    @P *= offset;
    setprimintrinsic(0, "transform", @primnum, matrix3(total_offset));
    
    vector4 q = quaternion(matrix3(offset));
    p@orient = qmultiply(q, p@orient);
    v@w = 2 / timestep * vector(q);
    
    vector new_trans = v@trans * offset;
    v@v = (new_trans - v@trans) / timestep;
    v@trans = new_trans;
}
else
{
    i@active = 1;
}

Place this code in an Attribute Wrangle node. The first input connects to DOP Geometry, while the second input references the same packed object transformed via SOP.

hou.PackedPrim Class Methods

The Python hou.PackedPrim class exposes three key methods:

transform() → hou.Matrix3

Returns the local 3x3 transform for the primitive. This excludes local point transforms and internal transforms (such as those in Alembic files).

fullTransform() → hou.Matrix4

Returns the complete 4x4 transform, including translations from points and internal transforms.

setTransform(m4)

Sets the primitive's local transform by updating the 3x3 matrix and point translation. This does not affect internal transforms.

Tags: Houdini RBD Packed Geometry Constraint Network VEX

Posted on Wed, 04 Nov 2026 16:15:29 +0000 by jagat21