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.