** Ensure your ‘agent configure joint’ has a sensible and accurate representation of rotation limits.

** A Ragdoll sim is a crowd solver/bullet solver hybrid with the bullet solver controlling the ragdoll behaviour.

** The heading and up vectors are used to ensure the initial orientation of the agents.

** If you have an initial velocity, it overrides the heading.

*Calculate the orient attribute from normals:

matrix3 m = dihedral({0,1,1},@N); @orient = quaternion(m);

*Calculate the heading and up vectors from the orient attribute:

matrix3 rr = qconvert(@orient); v@up = set(rr.yx,rr.yy,rr.yz); v@heading = set(rr.zx,rr.zy,rr.zz);

* Agent attributes can directly be modified during a simulation within a sopsolver.

* Increasing the number of substeps on the bullet solver helps with weird stretching of ragdolls.

**WHY use the Hard Constraint Relationship(usually Pin Constraint) and the Cone Twist Constraint Relationship Hybrid?
** Taken from houdini documentation:
“The rotation limits set up by the Agent Configure Joints node will be used to set up cone twist constraints. For any transforms where the rotation limits were not set (or if there are multiple root collision shapes), pin constraints will be created to prevent the ragdoll from immediately separating.”
** Because constraint attributes set in SOPs act as multipliers to the DOP parameters with the same names, a good workflow is to set the DOP parameters to one and set the desired values with the matching SOPs attributes.
** REDUCING THE “ERROR REDUCTION PARAMETER” ON THE HARD CONSTRAINT RELATIONSHIP seems to get rid of the undesired stretching of ragdolls.

The following VEX code is for initializing basic agent attributes:

vector @v_agent[]; vector @w_agent[]; vector v = chv("vel"); vector w = radians(chv("angvel")); int n = agenttransformcount(0, @primnum); for (int i = 0; i < n; ++i) { append(@v_agent, v); append(@w_agent, w); }//Primitive attributes set on the constraint network

@softness = chf("softness"); @max_up_rotation=chf("max_up_rotation"); @max_out_rotation=chf("max_out_rotation"); @cfm = chf("constraint_force_mixing"); @bias_factor = chf("bias_factor"); @relaxation_factor = chf("relaxation_factor");]]>

* Wire points must be reasonably spaced apart. Not to many points and not too few points

* Some key basic attributes that allow better manipulation of the wire object in SOPS are: width, klinear, damplinear, kangular, dampangular, targetstiffness and targetstiffness

* Think of the wire object as having two distinct behaviours – stretching(klinear) and bending(kangular).

– Depending on the material, the wire object can oscillate while stretching(damplinear) and/or oscillate while bending(dampangular)

* Use a uvtexture node to get a normalized mapping of values (0-1) along the wire object. Set Texture type to ‘Rows & Columns’ and attribute class to point.

* Use this wrangle code to remap the values to an attribute:

f@f_uv = 1-@uv.x; f@f_uv = chramp("uv_ramp",@f_uv);

* If you notices a wiggly behaviour on the wire object, it is most probably because of the oscillating forces. Either the wire is oscillating too much while stretching or bending. Introduce some damping, damplinear or dampangular as the case may be.

* If you want the wire object to be reluctant to any of the forces, play with the targetstiffness and targetdamping attributes. The wire will then quickly go back to rest. targetstiffness will make the wire reluctant to move from its initial position while targetdamping will make it appear reluctant to oscillate.

* This wrangle code lets you create multipliers for the key wire object attributes:

f@klinear = fit(f@f_uv,0,1,chf("min_klinear"),chf("max_klinear")); f@damplinear = fit(f@f_uv,0,1,chf("min_damplinear"),chf("max_damplinear")); f@kangular = fit(f@f_uv,0,1,chf("min_kangular"),chf("max_kangular")); f@dampangular = fit(f@f_uv,0,1,chf("min_dampangular"),chf("max_dampangular")); f@targetstiffness = fit(f@f_uv,0,1,chf("min_targetstiffness"),chf("max_targetstiffness")); f@targetdamping = fit(f@f_uv,0,1,chf("min_targetdamping"),chf("max_targetdamping"));

*Remember to substep when needed (also ‘Max Collision Resolved Passes’ on the wire solver)

]]>**#SPIRAL FROM LINE**

//SPIRAL FROM LINE

//CIRCLE OF REFERENCE AND LINE MUST LIE IN THE SAME PLANE

//(ZX) PLANE IS SUGGESTED

//RADIUS OF CIRCLE SHOULD IDEALLY BE THE SAME AS LENGTH OF LINE

//Use uvtexture node to compute uv attrib on points

//Texture type is set to Row & Columns

//Get velocity vector along line

int total = npoints(0)-1; //last point

int after = total-1;//penultimate point

int ntm = @ptnum+1;

vector npos = point(0,"P",ntm);

v@v = @P-npos;

//get vel of last point

if(@ptnum==total){

v@v = point(0,"P",after)-point(0,"P",total);

}

v@v = normalize(v@v);

v@up = chv("up"); //set to {0,1,0} if on ZX plane, must be perpendicular to plane

v@side = cross(v@v,v@up);

//Matrices

matrix3 xform = ident();

float freq = chf("frequency");//controls number of spirals

float angle = @uv.x * freq;

vector axis = v@up;

rotate(xform,angle,axis);

@P*=xform;

**#LIMIT THE VELOCITY OF CROWD AGENTS**

//Modifying the 'v_agent' array vector attribute //Use inside a SOP Solver vector myvel[] = v[]@v_agent; float mult = chf("mult"); foreach(int i; vector k; myvel) { if(length(k)!=0.0) { if(length(k)>chf("max_speed")) continue; else myvel[i] = myvel[i] * mult; } } v[]@v_agent = myvel;

**#FLOW VECTOR ALONG GEO (CROSS PRODUCT)**

vector up = set(0,1,0);

vector side = normalize(v@N);

vector flew = cross(side,up);

vector flow = cross(side,flew);

v@side = flow; //visualize flow vector

**#FLOW VECTOR ALONG GEO**

i[]@nbrs = neighbours(geoself(),@ptnum); f[]@dot; v[]@dir; vector down = chv("down"); //resize(@dot,3); //resize(@nbrs,3); //resize(@dir,3); int nlen = len(@nbrs); //Calculate direction vector for(int i=0; i<nlen;i++) { @dir[i] = point(0,"P",@nbrs[i]) - point(0,"P",@ptnum); } //Get dot product for(int i=0; i<nlen;i++) { @dot[i] = dot( normalize(@dir[i]) , normalize(down)); } //Get maximum value of dot product i[]@argdot = argsort(@dot); //Index of sorted array in increasing order //Get direction using lowest index //Loop though argdot // if value of argdot is 0, get value of dir foreach(int c;int d;@argdot) { if(@argdot[c] == 0) v@flow = @dir[c]; } v@v = v@flow; //Flip vector if facing up float dd = dot(normalize(v@v),set(0,1,0)); if(dd>chf("dot_threshold"))v@v*=-1; //-0.1

**#ATTRIBUTE TRANSFER COLOR FROM SECOND INPUT**

int handle = pcopen(@OpInput2, "P", @P, chf("rad"), chi("num")); vector lookup_P = pcfilter(handle, "P"); //Average P from second input vector lookup_Cd = pcfilter(handle, "Cd"); //Average Cd from second input i@many = pcnumfound(handle); if(i@many>0){ @Cd = lookup_Cd; v@P = lerp(v@P, lookup_P, chf("mix")); }

**#CALCULATE VOLUME BASED ON BOUNDING BOX**

//calculate_volume_based_on_bbox vector two = point(0,"P",2); vector one = point(0,"P",1); vector three = point(0,"P",3); vector six = point(0,"P",6); float l = length(two-one); float b = length(two-three); float h = length(two-six); f@vol = l * b * h * chf("scale"); // scale if values are too small f@vol = abs(f@vol);

**#REST POSITION FROM (ARRAY) NAME ATTRIBUTE**

Often times when making RBD simulations, we receive notes to prune some pieces. This code makes isolating the pieces to be pruned to be easily selected in the viewport.

You don’t need to select all the polygons in the primitives to be pruned. A single polygon face for each piece(primitive) to be pruned is sufficient for as long as the polygon contains a valid name attribute. This code iterates through every isolated packed primitive (Input2) and adds their corresponding name attribute to an array stored on a single point at origin(Input1).

//Set rest position from array //This two part code uses two attrib wrangle nodes //It generates an array that contains the name attribs //of selected pieces and stores this array attrib on a single point //steps: isolate points to not deform //attrib promote name to prim //pack and transfer name attrib to packed pts //Input 1 is a single pt to store array of blasted geo on //Input two is the packed pts i[]@pts = pcfind(1, "P", @P, chf("rad"), chi("num")); string nnme[]; s[]@nme= nnme; string tok; foreach(int a;@pts){ tok = prim(1,"name",a); push(@nme,tok); } //The following code goes into another attrib wrangle node //That sets the rest position in name attrib is in our array //Input 1 is our complete sim //Input 2 is the single pt that contains array attrib string names[] = point(1,"nme",0); foreach(string nme; names){ if(s@name==nme)@P = v@rest; }

**#ISOLATE MULTIPLE PRIMS**

i[]@prims = {0,1,2}; //primitives to keep foreach(int i;@prims){ if(i==@primnum)@group_del=1; }

**#PRUNE BY STRING MATCH**

string aa[] = split(chs("str")); foreach(string i;aa) { string word = "*" + i + "*"; if(match(word,s@AssemblyPath))removepoint(geoself(),@ptnum); }

**#AVERAGING STUFF**

vector accum_p = set(0,0,0); vector pos; int count=0; vector avg_p; float vlen = length(v@v); int handle = pcopen(@OpInput1, "P", @P, chf("rad"), chi("num")); while(pciterate(handle)){ pcimport(handle,"P",pos); accum_p+=pos; count+=1; }; avg_p = accum_p/count; v@P = lerp(v@P,avg_p,chf("mix"));

**#CULL BASED ON RAY DIRECTION**

int handle = pcopen(@OpInput1, "P", @P, chf("rad"), chi("num")); vector up = {0,1,0}; vector dir,pos; float angle; i@many = pcnumfound(handle); while(pciterate(handle)){ if(i@many>1){ pcimport(handle,"P",pos); dir = pos - @P; angle = dot(normalize(dir),up); if(angle>chf("mix"))removepoint(geoself(),@ptnum); }; }; pcclose(handle);

**#REFLECT DOWN FACING VEL ATTRIBUTE ABOUT AN AXIS**

vector ref = set(chf("refx"),chf("refy"),chf("refz")); float dot = dot(normalize(v@v),ref); vector axis = set(0,1,0); if(dot<chf("thres"))v@v = reflect(v@v,axis);

**#ASSIGN ROTATION MATRIX COMPONENTS TO ATTRIBUTES**

assign(v@N.x, v@N.y, v@N.z, v@up.x, v@up.y, v@up.z, v@side.x, v@side.y, v@side.z,3@xform); //Get Rotation Matrix components

**#EXTRACT MATRIX COMPONENTS FROM PACKED POINT**

v@up=set(0,1,0); v@v=normalize(v@v); v@side = cross(v@up,v@v); 3@xform = set(v@v.x, v@v.y, v@v.z, v@up.x, v@up.y, v@up.z, v@side.x, v@side.y, v@side.z); //3D Rotation Matrix 4@from = set(1 , v@v.x, v@v.y, v@v.z, v@up.x, 1 , v@up.y, v@up.z, v@side.x, v@side.y, 1 , v@side.z,v@P.x, v@P.y, v@P.z, 1 ); //3D Transformation matrix

**#GET CENTROID**

float cex, cey, cez; vector min, max; getbbox(0, min, max); cex = (max.x + min.x) * 0.5; cey = (max.y + min.y) * 0.5; cez = (max.z + min.z) * 0.5; vector centroid = set(cex, cey, cez);

**#APPLY SOME NOISE TO THE POSITION ATTRIBUTE**

int turb = chi("turb"); float rough = chf("rough"); float atten = chf("atten"); vector mynoise = anoise(@P, turb, rough, atten); v@P+=mynoise;

** #BLACK AND WHITE MASK AFTER PAINTING WITH THE PAINT SOP**

if(@Cd.y<1)@Cd = set(1,1,1); else (@Cd = set(0,0,0));

** #GENERAL POINT CLOUD OPEN FUNCTION**

int handle = pcopen(@OpInput2, "P", @P, chf("rad"), chi("num")); vector lookup_P = pcfilter(handle, "P"); i@many = pcnumfound(handle);

** #QUATERNION ROTATE**

v@N = set(0,1,0); matrix3 xform = ident(); float amt = chf("amt"); vector axis = set(chf("axisx"),chf("axisy"),chf("axisz")); rotate(xform, amt, axis); p@roti = normalize(quaternion(xform)); v@up= qrotate(p@roti,v@N); v@N*=xform;

** #A RANDOM ROTATION MATRIX**

matrix3 myrot = ident(); float amt = fit(rand(@id+chf("seed")),0,1,chf("minamt"),chf("maxamt")); vector axis = sample_direction_uniform(rand(@id)); rotate(myrot, amt, axis); @P*=myrot;

** #REDUCE POINTS**

if(rand(@id)<ch("kill")) removepoint(geoself(), @ptnum);

** #SHRINK WRAP**

int handle = pcopen(@OpInput2, "P", @P, chf("rad"), chi("num")); vector lookup_P = pcfilter(handle, "P"); i@many = pcnumfound(handle); if(i@many>=1)v@N = @P-lookup_P; else v@N = set(0,0,0); @P-=v@N;

** #GENERAL THRESHOLD OPERATION**

if(@Cd.x<chf("thres"))removepoint(geoself(),@ptnum);

** #TIGHTEN POINTS**

int handle = pcopen(@OpInput1, "P", @P, chf("rad"), chi("num")); vector lookUp_P = pcfilter(handle, "P"); v@P = lerp(v@P,lookUp_P,chf("mix"));

** #BLEND GEO BETWEEN SUBFRAMES**

v@N = v@N; //set normals vector pp = point(@OpInput2,"P",@ptnum); float dp = dot(normalize(v@v),normalize(v@N)); float mix = fit(dp,chf("mindot"), chf("maxdot"), chf("min"), chf("max")); @P = lerp(@P,pp,mix);

** #CULL BY VOLUME (chi(“inv”)) is a check box.**

float vsample = volumesample(1,0,@P); if(chi("inv")==1){ if(vsample==0) removepoint(geoself(), @ptnum); } else{ if(vsample!=0) removepoint(geoself(), @ptnum); }

** #DELETE LONE POINTS**

int handle = pcopen(@OpInput1, "P", @P, chf("rad"), chi("num")); i@many = pcnumfound(handle); if(i@many<chi("num"))removepoint(geoself(),@ptnum);

** #DOT PRODUCT CULLING**

vector ref = set(chf("refx"),chf("refy"),chf("refz")); float dot = dot(normalize(v@v),ref); if(dot<chf("thres")) @Cd = set(1,0,0); //Visualize if(dot<chf("thres")) removepoint(geoself(),@ptnum); //Delete

** #GET LENGTH, BREADTH AND HEIGHT**

//get length, breadth and height vector sz = getbbox_size(geoself()); assign(f@l,f@b,f@h,sz);

**#GROUP ROOTS AND TIPS OF CURVES**

i@root = (vertexprimindex(0, @vtxnum) == 0); i@tip = (vertexprimindex(0, @vtxnum) == (@numvtx-1)); setpointgroup(0, "roots", @ptnum, @root, "set"); setpointgroup(0, "tips", @ptnum, @tip, "set");

**#MATCH BY ID**

//create a point cloud //add an enumerate SOP //Group type - points //Attribue - id //delete a subset of the points and add to input 2 //Full point cloud goes into input 1 int match = findattribval(1,"point","id",@id,0); if(match!=-1)removepoint(geoself(),@ptnum);

**#CULL BY RELATIVE BOUNDING BOX**

vector bbox = relbbox(0,@P); if(bbox.xchf("xmax"))removepoint(geoself(),@ptnum); if(bbox.ychf("ymax"))removepoint(geoself(),@ptnum); if(bbox.zchf("zmax"))removepoint(geoself(),@ptnum);

** #LINEAR INTERPOLATIONS**

//Interpolate between point positions vector p1 = point(1,"P",@ptnum); v@P = lerp(v@P, p1, chf("mix")); //Interpolate between velocities vector v1 = point(0,"v",@ptnum); vector v2 = point(1,"v",@ptnum); v@v = lerp(v1, v2, chf("mix"));

** #ROTATE BY MATRIX**

float xrotamt = radians(chf("xrotamt")); float yrotamt = radians(chf("yrotamt")); float zrotamt = radians(chf("zrotamt")); float cex, cey, cez; vector min, max,centroid; vector xrotaxis = set(1,0,0); vector yrotaxis = set(0,1,0); vector zrotaxis = set(0,0,1); //Calculate the Centroid here getbbox(0, min, max); cex = (max.x + min.x) * 0.5; cey = (max.y + min.y) * 0.5; cez = (max.z + min.z) * 0.5; centroid = set(cex, cey, cez); //Declare identity matrix here. 3@xform = ident(); //Rotation order is x y z rotate(3@xform , xrotamt, xrotaxis); rotate(3@xform , yrotamt, yrotaxis); rotate(3@xform , zrotamt, zrotaxis); v@P-=centroid; //move points to origin @P = @P * 3@xform; //Apply Rotation v@P+=centroid; //move back to original position

** #TRANSFORM BY PRIMITIVE INTRINSIC**

matrix xform = primintrinsic(@OpInput2, "packedfulltransform", 0); @P*=xform;

#VOLUME FRUSTRUM CULL

vector pndc = toNDC(chs("camera_name"), @P); // padding float pad = .2; if(pndc.x1+pad || pndc.y1+pad || pndc.z>=0 ){ @density=0; }

#VELOCITY ALONG PATH

int total = npoints(0)-1; //last point int after = total-1;//penultimate point int ntm = @ptnum+1; vector npos = point(0,"P",ntm); v@v = @P-npos; //get vel of last point if(@ptnum==total){ v@v = point(0,"P",after)-point(0,"P",total); }

**#MOVE POINTS TO GROUND SURFACE (GEO)**

int handle = pcopen(@OpInput2, "P", @P, chf("rad"), chi("num")); vector lp = pcfilter(handle, "P"); i@npt = nearpoint(1,@P); vector npos = point(1,"P",i@npt); vector off = set(0,chf("yoffset"),0); if(@P.y<npos.y){ vector disp = @P-lp; @P-=disp; @P+=off; }

**#ROTATE LINE ALONG SELF (TWISTER)**

//uvtexture node to compute uv attrib on points //Texture type is set to Row & Columns //Get velocity vector along line int total = npoints(0)-1; //last point int after = total-1;//penultimate point int ntm = @ptnum+1; vector npos = point(0,"P",ntm); v@v = @P-npos; //get vel of last point if(@ptnum==total){ v@v = point(0,"P",after)-point(0,"P",total); } //Make orthogonal frame vectors v, up and side v@up = set(1,0,0); v@side = cross(normalize(v@v),normalize(v@up)); v@up = cross(normalize(v@side),normalize(v@v)); v@v = cross(normalize(v@side),normalize(v@up)); //Matrices /*'offset' is scaled with up vector or side vector so the offset position is usually perpendicular to the angle of rotation, in the event that the line is rotatated before applying the twist */ float offset = chf("offset_multiplier"); float freq = chf("frequency"); float angle = @uv.x * freq; vector offset_pos = (v@side*offset) + @P; vector axis = v@v; matrix3 xform = ident(); rotate(xform,angle,axis); //EXTRACTING TRANSFORMS //Depending on the value of c, //returns the translate (c=0), rotate (c=1), or scale (c=2) //component of the transform (xform) #define XFORM_SRT 0 // Scale Rotate Translate #define XFORM_XYZ 0 // Rx Ry Rz int trs = XFORM_XYZ; //Transform Order int xyz = XFORM_SRT; //Rotation Order vector p = set(0,0,0); //pivot for crack/extracting transforms vector translate = cracktransform(trs, xyz, 0 , p, xform); vector rotate = cracktransform(trs, xyz, 1 , p, xform); vector scale = set(1,1,1); vector pivot = @P; //pivot for rotations //MAKE TRANSFORM matrix newTrans = maketransform(trs, xyz, translate, rotate, scale, pivot); @P=offset_pos*newTrans;

**#MOVE POINTS TO GEOMETRY SURFACE IF INSIDE SDF**

//Input 2 is Geo //Input3 is SDF representation of Geo int handle = pcopen(@OpInput2, "P", @P, chf("rad"), chi("num")); vector lp = pcfilter(handle, "P"); i@npt = nearpoint(1,@P); vector npos = point(1,"P",i@npt); vector off = set(0,chf("yoffset"),0); vector disp = @P-lp; float vsample = volumesample(2,0,@P); if(vsample<0)@P-=disp;

**#APPLY RANDOM ROTATION TO VECTOR ON ALL AXIS (AXES)**

float amt = 0; float randx, randy, randz; vector axis = set(0,0,0); int npts2 = npoints(1); //Number of points in second input int lidx2 = npts2-1; //Index of penultimate point vector stemdir = point(1,"P",lidx2) - point(1,"P",0); //Main stem direction amt = fit(rand(@id+chf("rotation amt_seed")),0,1,chf("min_rotation_amt"),chf("max_rotation_amt")); randx = fit(rand(@id+chf("x_axis_seed")),0,1,chf("x_lower_limit"),chf("x_uppper_limit")); randy = fit(rand(@id+chf("y_axis_seed")),0,1,chf("y_lower_limit"),chf("y_uppper_limit")); randz = fit(rand(@id+chf("z_axis_seed")),0,1,chf("z_lower_limit"),chf("z_uppper_limit")); axis = set(randx,randy,randz); //create identity matrix to use for rotatating stem dir matrix3 idm = ident(); rotate(idm,amt,axis); //Rotate the matrix v@N = stemdir; //visualize through normal @N*=idm; //Apply transformation

**#MOVE POINTS TO SURFACE (SDF)**

float vsample = volumesample(1,0,@P); vector dirtosurf = volumegradient(1, 0, @P); @P -= normalize(dirtosurf) * (vsample - chf("offset"));

**#QUANTIZE POSITION – CUBIFY**

float ps = chf("scale"); @P.x = rint(@P.x/ps) * ps; @P.y = rint(@P.y/ps) * ps; @P.z = rint(@P.z/ps) * ps;

**#SINK PUMP BEHAVIOR – VELOCITY FIELD**

//Plug in a point cloud into input 1 //Visualize the velocity attrib vector pp = getbbox_center(geoself()); //ORIGIN OR SINK/PUMP v@v = normalize(pp-@P); //v@v.y*=0; vector a = normalize(chv("ref")); vector b = normalize(v@v); float dd = dot(a,b); f@theta = degrees(acos(dd)); matrix3 mm = lookat(v@v,a); v@v*=mm;

**#INTERPOLATE LINE INTO CIRCLE**

//Input is a line //Resample line for more points //UV texture SOP on line as point attrib //Set to rows and column //Interpreted from: https://darioseyb.com/post/curvy-lines/ int num = npoints(0); vector lineStart = point(0,"P",0); vector lineEnd = point(0,"P",num-1); vector lineDir = lineStart-lineEnd; //Calculate vector perpendicular to lineDir float l = length(lineDir); vector up = set(0,1,0); vector side = cross(normalize(lineDir),normalize(up)); //Flip up if lineDir is in the same direction as up vector if(dot(up,normalize(lineDir))<0)up=set(1,0,0); float t = @uv.x; float pivot = chf("pivot"); //Normalized pivot float lineLength = length(lineDir); //line length float bendFactor = chf("bend_Factor"); //bend factor float circleRad = lineLength / (2 * PI); //vector circleCenter = lineStart + lineDir * (pivot + up * circleRad); vector circleCenter = lineStart + (-lineDir * pivot); //Make Circle tangle to line circleCenter+=up*circleRad; float angle = PI + bendFactor * (1.0 - (t+pivot)) * 2 * PI; vector posOnCircle = circleCenter + set(cos(angle),sin(angle),0) * circleRad; @P = lerp(@P,posOnCircle,bendFactor);

**#BEND CURVE**

//Input is a line //Resample SOP on line for more points //Activate curveu attrib in resample SOP //Remap curveu to shape of bend @curveu=chramp("ramp",@curveu); float bamt = chf("bend_amt"); //bend amount vector benddir = chv("bend_dir"); //bend direction @P+= benddir * bamt * @curveu;

**#LOOK AT: GENERATE RADIAL VELOCITIES**

//Input 1 is a point cloud v@v = set(1,0,1); vector pp = getbbox_center(geoself());//point to look at matrix3 mm = lookat(pp,@P); @v*=mm;

**#LOOK AT: GENERATE LINEAR VELOCITIES**

//Input 1 is a point cloud v@v = set(0,0,1); vector pp = getbbox_center(geoself());//point to look at matrix3 mm = lookat(pp,@P); @v*=mm;

**#LOOK AT TARGET**

vector pp = point(1,"P",0);//point to look at matrix3 mm = lookat(@P,pp); @P*=mm;

**#MEDIAN POINT NUMBER**

int npt = npoints(0); //Even if(npt%2==0) i@midpt = (npt/2) -1; else i@midpt = (int(ceil(float(npt)/2))) -1;

**#ADD A POINT AT CENTER OF OBJECT**

//Run Over Detail //Object whose centroid is to be computed goes into input 2 vector cent = getbbox_center(1); addpoint(0,cent);

**#CALCULATE/VISUALIZE POINT CLOUD DENSITY**

//POINT DENSITY float rad = chf("rad"); int num = chi("num"); int handle = pcopen(@OpInput1, "P", @P, rad, num); vector lookup_P = pcfilter(handle, "P"); int many = pcnumfound(handle); i@many = many; f@viz = float(many)/float(num); //point cloud density @Cd = vector(f@viz);

**#SCALE BY MATRIX**

vector p = getbbox_center(0); //pivot float sx, sy, sz; sx = sy = sz = chf("scale"); matrix idan = maketransform(0,0,{ 0, 0, 0 },{ 0, 0, 0 },set(sx,sy,sz),{ 0, 0, 0 }); //Move to origin, scale, then move back @P-=p; @P*=idan; @P+=p;

**#RANDOM ATTRIB ON POINTS BASED ON PARENT PRIM NUM**

/*HINT: use the 'rnd' attrib to sort points by attributes in a sort SOP*/ //Run over Primitives int points[] = primpoints(0,@primnum); int ll = len(points); for(int i=0; i<ll;i++) { setpointattrib(geoself(),"rnd",points[i],@primnum, "set"); }

**#CULL POINTS BASED ON SPEED (VELOCITY THRESHOLD)**

float vlen = length(v@v);

if(vlen<chf("speed"))removepoint(geoself(),@ptnum);

**#ORIENT ATTRIBUTE FROM NORMAL ATTRIBUTE**

matrix3 m = dihedral({0,0,1},@N); @orient = quaternion(m);

**#CONNECT POINTS**

//Run over detail int num = npoints(geoself()); int vtx_limit = num-1; //We dont want to add an extra vertex to the last point for(int i=0;i<vtx_limit;i++) { int prim = addprim(0,"polyline"); addvertex(0,prim,i); addvertex(0,prim,i+1); }]]>

I rarely find the time to update this blog, my bad. The point cloud plucker is especially useful for removing points that are too close to one another. An implementation in VEX is a lot more faster than a Python implementation. Below is the VEX code for this:

**VEX CODE:**

int handle = pcopen(@OpInput1, "P", @P, chf("rad"), chi("num")); while(pciterate(handle)){ i@many = pcnumfound(handle); for(int i = 0; i<i@many;i++){ if(i==0)continue;//ignore myself else{ pcimport(handle,"point.number",i@nearptnum);//import ptnum of nearpoints /* If point is already in group exit the loop, else add point to group */ if(inpointgroup(0,"rmve_grp",i@nearptnum)==0){ setpointgroup(geoself(),"rmve_grp",i@nearptnum,1,"set"); }; }; }; }; pcclose(handle);

This python code allows one to remove points that are too close to one another in a point cloud. The result ideally should be a well spaced point cloud. A radius is specified and each point in the point cloud is tested to see if they are in proximity of the specified radius. The ‘plucked’ points are then added to a group: ‘rmve_grp’. You typically want to run this code from a python defined SOP and append a delete SOP (deleting non-selected ‘rmve_grp’) to view the points that have been plucked, or vice – versa as the case may be.

**PYTHON CODE:**

node = hou.pwd() geo = node.geometry() #Define a group that holds all points that have been removed rmv_grp = geo.createPointGroup("rmve_grp") rad = 1 #Iterate through all points for pt in geo.points(): #Continue if pt is already in removed group if pt in rmv_grp.points(): continue else: #otherwise, find the points close to pt and less than specified radius for apt in geo.points(): # Don't perform test on myself if pt == apt: continue else: # find close points to pt but less than distance threshold ptdisp = pt.position() - apt.position() # displacement vector ptdist = ptdisp.length() if ptdist < rad: rmv_grp.add(apt) break

NB: This implementation is a bit buggy. A better implementation would be to wrap this code in a recursive function. This will be addressed in a future post.

]]>This simple RnD demonstrates the use of Rigid Body Dynamics, Computational Fluid Dynamics, Particle Dynamics and their interoperability all within Houdini DOPS.

**RIGID BODY DYNAMICS:
**

My previous RBD simulations suffered from the debris pieces being perfectly flat and polygonal around their surfaces. After generating and caching my initial debris simulation, I then used the for-each SOP and the corresponding ‘name’ attribute to apply a bit of noise (Mountain SOP) and subdivision to the individual debris. This made them look a bit more rocky and less flat/polygonal.

**SMALLER PEBBLES AND THE PEBBLE LIBRARY:
**

In order to add a bit more variation to the system, I decided to add smaller pebbles that get thrown off with the bigger debris pieces. To accomplish this, I again took the original debris simulation into a for-each SOP, scaled them down and then moved their centroids to the origin. With the use of the file SOP, I was able to cache about 20 variations to disk, suffixing them with ‘opdigits’, that is, pebble1, pebble2, pebble3…e.t.c.

These smaller pebbles were then brought back into the RBD simulation with a RBD Point object by adding an instance attribute onto a point cloud that followed a pattern similar to:

/obj/pebble_library/pebble`trunc(fit(rand($PT),0,1,1,21))`

The point cloud was generated by scattering some points in a region where I wanted as the initial position for the pebbles. Note that the total number of points scattered will result in the total number of pebbles in the system.

By bringing the pebbles back into the RBD simulation, the smaller pebbles could then collide properly with the bigger debris. The result of this was then cached to disk.

**FLUID DYNAMICS: DUST SIMULATION AND THE PYRO SOLVER
**

The Pyro Solver in Houdini was used for the dust that get emitted with the debris. Note that this may also be achieved with the smoke solver, however, I always prefer using the pyro solver as it offers a lot more flexibility and controls that aids with the shape and character of smoke and fire simulations.

The source of the dust emitter were the debris pieces that only fell unto the ground surface. To isolate these pieces, I used an expression in the delete SOP that followed this pattern:

$TY<0.1

When the expression evaluates to true, it selects only the pieces that had fallen below 0.1 units. By deleting non-selected pieces, we can get rid of those pieces that aren’t falling to the ground. The desired pieces are then sent into the ‘fluid source’ SOP and the result is then cached to disk for a faster feedback.

To make the pyro solver give me results that resembled that of dust, I had to pump in just a tiny bit of viscosity and bring down the turbulence significantly. I must mention that the desired look is always relative based on an individual and how they want their art to be directed.

I also had to key frame the buoyancy just so the smoke begins to rise only when they have hit the ground surface. I can assure you that there are definitely a lot of controls to play with on the pyro solver in order to achieve different aesthetics. The result of the pyro simulation is then also cached to disk.

**RBD / FLUID COLLISIONS:
**

In order to further aid the realism, it is imperative for the rigid body elements to collide properly with the emitted smoke. I know of two ways of achieving this interaction, I however went for the simplest/faster approach.

A much more precise approach is to generate a collision field from the debris and then plug this into the pyro solver. In my case, I simply used a merge DOP, and I set its collision property to ‘mutual’.

**PARTICLE DYNAMICS:
**

I also wanted to generate some grit particles that may be interpreted as sand. The first approach that came to mind was to scatter some points at the expected initial position of the sand particles and extract the ‘vel’ attribute from my dust simulation and then apply it unto the particles for advection.

While that is a totally valid approach, I decided to rather take advantage of the POPS/DOPS interaction that has been introduced in recent versions of Houdini using the POP object. It was quite straight forward. I emitted some particles off the RBD debris, inherited a part of the debris velocity and I applied some gravity. This made the particles move downward and in the direction of the falling debris pieces.

**PARTICLES/RBD COLLISIONS:**

I required the particles to collide properly with the debris as well as the ground plane but simply merging the particle stream into the rigid body stream alone didn’t cut it. Two problems arose with this approach:

* The particles collided well with the ground plane but kept on sliding

* The particle velocity was affected by the debris velocity and/or vice-versa depending on what the merge relationship was set to.

This caused the partcles/debris to explode.

To fix the first problem, I set the static friction value for the particles to about 2 on the pop object and this put the particles to rest the moment they collided with the ground.

The other problem was fixed by using 3 merge DOPS:

merge 1 : particle -> ground plane : collide relationship = mutual

merge 2 : debris -> ground plane: collide relationship = mutual

merge 3: merge1 -> merge2: collide relationship = No change

This obviously is not the best approach as it might mean that the particles would not collide well with the debris but then, who in the world wants to deal with rebellious particles that are out of control?

]]>The logic behind this technique is quite simple and interesting – while I’m not going to be addressing an actual pyro simulation in this write up, this will serve as an opportunity to document the algorithm for future reference. Other general Pyro tips and tricks learnt while working with the awesome talent in London would be documented in another production note.

The basic idea is to have the clustered containers created only once and at specific points during the simulation and then the pyro solver takes care of their auto-resizing/expansion up until the next creation point/frame.

While working on this in London, I used VOPS to implement the algorithm which was originally pointed to me by Arpita. However, I tried to understand the logic and I have re-implemented it in my own understanding, but this time with VEX using the point wrangle node.

** Below is the pseudocode for having the instance points created only at specific frames:**

compare present cluster value to the cluster value on the next frame if (cluster value at next frame == value at present frame) { set the 'deleteme' flag to 1; } else { set the 'deleteme' flag to 0; } //First frame check: if (current frame == first frame) //we do not want to delete the point at the first frame" set the 'deleteme' flag to 0;

**VEX CODE:**

float sframe = ch("./sframe");// Read value of custom parameter representing the first frame float cframe = @Frame; // current floating frame int thisCluster, nextCluster; addattribute("sframe", sframe); //optional addattribute("cframe", cframe); //optional import("cluster", thisCluster, 0,0);//import cluster value at this frame import("cluster", nextCluster, 1,0);//import cluster value at next frame addattribute("this",thisCluster); //optional, for visualization addattribute("next",nextCluster); //optional, for visualization if (thisCluster==nextCluster) { addattribute("deleteme",1); } else { addattribute("deleteme",0); } //First frame check if(cframe==sframe) addattribute("deleteme",0); addvariablename("deleteme","DELETEME"); //custom variable mapping

In this particular VEX implementation, I have created a custom parameter that represents the first frame and the point wrangle node uses this channel as a means of comparison for the ‘first frame check’.

Outputing some attributes was totally unnecessary but I used these for sanity checks while testing the code.

**UPDATED VEX CODE BASED ON RECENT VERSIONS OF HOUDINI:**

**Monday, April 11 2016**

f@sframe = chf("sframe");// Read value of custom parameter representing the first frame f@cframe = @Frame; // current floating frame i@thisCluster = point(0,"cluster",@ptnum); //import cluster value at this frame i@nextCluster = point(1,"cluster",@ptnum);//import cluster value at next frame if (i@thisCluster==i@nextCluster) { i@deleteme = 1; } else { i@deleteme = 0; } //First frame check if(f@cframe==f@sframe) i@deleteme = 0; addvariablename(0,"deleteme","DELETEME"); //custom variable mapping if(i@deleteme==1)removepoint(geoself(),@ptnum);]]>

A very simple and interesting way to implement a flocking system with Houdini DOPs. As with almost all things in Houdini, the system is purely procedural.

One has full control over the total number of intelligent agents in the system – there is room for collision detection and the ‘leader’ parameter can also be animated. This means you can make the agents follow a leader during a particular timestep and then follow another leader at another timestep.

]]>Using the Output of a RBD sim as an input for a FLIP sim.

]]>Extracting the velocity attribute from particles and using that as a Wind Force

]]>**#CREATE A PRIMITIVE GROUP FOR EACH POLYGON ON A GEOMETRY**

node = hou.pwd() geo = node.geometry() for primm in geo.iterPrims(): grpName = geo.createPrimGroup('Gurupu_' + str(primm.number())) grpName.add(primm)

**#SET KEYFRAME VALUES ON POINTS AS POINT ATTRIBUTES**

node = hou.node('/obj/CRACK_SIM_7/carve1') #Keyframed node geo = hou.pwd().geometry() #Node to write attribs to parm = node.parm("domainu1") #Keyframed parameter #Extract value of keyframes from tuple firstkey = int(parm.keyframes()[0].frame()) secondkey = int(parm.keyframes()[1].frame()) #create point attribs first = geo.addAttrib(hou.attribType.Point, "first",0) second = geo.addAttrib(hou.attribType.Point,"second",0) #setting attribute values for pt in geo.points(): pt.setAttribValue(first,firstkey) pt.setAttribValue(second,secondkey)

**#CHANGE A CERTAIN PARAMETER IN ALL MANTRA NODES**

# change a certain parameter in all Mantra nodes parent = hou.node("/obj/ROPS") for node in parent.children(): node_type_name = node.type().name() if node_type_name == 'mantra': # Get the value of a parameter and store it in a variable param = node.parm("matte_objects") # set the value of the parameter to a different value param.set("endurance render")

**There are times when you want to perform an operation on nodes whose names contain a particular pattern. In this case, all nodes with ‘thrusterA’ in their names are acted upon.
**

#Change the forced matte parameter of node with 'thrusterA' pattern to 'endurance_render' parent = hou.node("/obj/ROPS") sstring = 'thrusterA' temp = 'endurance_render' for child in parent.children(): if sstring in child.name(): param = child.parm("matte_objects") if param is None: pass else: print searchStr print replStr param.set(temp)

**#REMOVE A NODE FROM A PARTICULAR BUNDLE**

node = hou.node('/obj/test') #Node path bundlename = 'testy' #name of bundle for nn in hou.nodeBundles(): if nn.name() ==bundlename: if nn.containsNode(node): nn.removeNode(node)

**#THIS TWO PART CODE ALLOWS YOU TO COPY BUNDLES FROM ONE HOUDINI SCENE TO THE OTHER**

# write bundle names, filter and pattern to file import hou namedumpfile = '/u/toa/Desktop/b_name.txt' patterndumpfile = '/u/toa/Desktop/b_pattern.txt' filterdumpfile = '/u/toa/Desktop/b_filter.txt' a = file(namedumpfile, 'w') b = file(patterndumpfile, 'w') c = file(filterdumpfile, 'w') # write bundle names, filter and pattern for bdl in hou.nodeBundles(): a.write(str(bdl.name())) a.write("\n") a.close b.write(str(bdl.pattern())) b.write("\n") b.close c.write(str(bdl.filter())) c.write("\n") c.close

# Read bundle names, filter and pattern from file namedumpfile = '/u/toa/Desktop/b_name.txt' patterndumpfile = '/u/toa/Desktop/b_pattern.txt' filterdumpfile = '/u/toa/Desktop/b_filter.txt' import hou a = file(namedumpfile, 'r') b = file(patterndumpfile, 'r') c = file(filterdumpfile, 'r') global need for lin in b: for line in a: need = hou.addNodeBundle(line.rstrip()) need.setPattern(lin.rstrip()) break

**#REPLACE A SUBSET OF STRINGS IN A SPECIFIC PARAMETER**

''' matchStr is the value to be replaced and replaceStr is the replacement make sure the nodes you want to change are selected ''' def forceObjects(): for node in hou.selectedNodes(): param = node.parm("forceobject") searchStr = param.evalAsString() matchStr = "thrusterDelayedBB" replaceStr = "thrusterDelayedBC" #Change the values here if matchStr in searchStr: replStr = searchStr.replace(matchStr,replaceStr) #Replace the string param.set(replStr) def forceLights(): for node in hou.selectedNodes(): param = node.parm("forcelights") searchStr = param.evalAsString() #Change the values here matchStr = "thrusterDelayedCA" replaceStr = "thrusterDelayedBA" if matchStr in searchStr: replStr = searchStr.replace(matchStr,replaceStr) #Replace the string param.set(replStr) forceObjects() forceLights()

**When it comes to batch/distributed rendering on the render farm, there are times when I want full control over the individual jobs just so I can easily delete/troubleshoot a job that stalls on the render farm. The following code allows me the control of sending each job as individual render tasks to the render farm rather than sending multiple jobs as a single render task.
**

**#RENAME ALL SELECTED ‘ALFHOU’ NODES TO THE NAME OF THEIR ANCESTORS PREFIXED WITH ALFHOU**

import hou for node in hou.selectedNodes(): ans = node.inputAncestors() suff = ans[0].name() node.setName("alfhou_" + str(suff))

**#APPEND AN ALFOU NODE TO THE SELECTED NODES AND RENAME THEM SUFFIXED WITH ITS ANCESTORS NAME**

import hou parent = hou.node("/obj/ROPS") for node in hou.selectedNodes(): alfu = parent.createNode("alfhou") alfu.setFirstInput(node) alfu.moveToGoodPosition() ans = alfu.inputAncestors() suffix = ans[0].name() alfu.setName("alfhou_" + str(suffix))

**#REPLACE A CHARACTER IN ALL SELECTED NODE NAMES**

import hou for node in hou.selectedNodes(): name = node.name() str = "D" rep = "A" if str in name: newN = name.replace(str,rep) node.setName(newN)

**#WRITE A SET OF NODES TO DISK AS CODE**

import hou dumpfile='/u/toa/Desktop/nodeAsCode.txt' a = file(dumpfile,'w') for node in hou.selectedNodes(): acode = node.asCode() a.write(acode) a.close

**It is quite easy to do channel referencing in Houdini but that does not work when you want to copy and paste ramp parameter values. The following code allows you to do that and saves you a bit more time so you can get your work into dailies faster!**

**#COPY RAMP PARAMETERS**

import hou #Change path to source and destination nodes here sourceNode = hou.node("/obj/Rnd/color1") destNode = hou.node("/obj/Rnd/color2") sourceParm = sourceNode.parm("ramp") destParm = destNode.parm("ramp") destParm.set(sourceParm.eval())

**#COPY ALL PARAMETERS FROM ONE NODE TO ANOTHER**

import hou source = hou.node('/shop/fireball') dest = hou.node('/shop/test1') for pp in source.parms(): sourceParm = pp.name() #source parameter destParm = dest.parm(sourceParm) #Destination parameter destParm.set(pp.eval())]]>