Houdini VEX
Remap curve
Edit the point placement along the length of a (parametric) curve using a remap curve
vector uv;
int prim;
xyzdist(0,@P,prim,uv);
float umapped = chramp("remap",uv[0]);
uv[0] = umapped;
@P = primuv(0,"P",prim,uv);
Visualize parameter
Why look at the animation editor when you can have a shittier version in the viewport. Don't ask questions, it helped me.
//second input of detail wrangle should be frozen frame, 'parm' channel is a path to the parm you want to see graphed, which can be an expression
vector curPos;
//---graph--
vector scalept(vector normalizedpt){
vector bs = getbbox_size(1);
float size = max(bs[0],max(bs[1],bs[2]))/3;
vector bb = getbbox_center(1);
return ((normalizedpt-{.5,-.1,0})*size+bb);
}
int start = `$FSTART`;
int end = `$FEND`;
int pts[] = {};
for(int i=0;i<end-start+1;i++){
float val = ch(chs('parm'),float(i+start)*@TimeInc);
vector pos = set(float(i)/(end-start),val,0);
int newpoint = addpoint(0,scalept(pos));
append(pts,newpoint);
if(i+start==@Frame){
curPos = pos;
}
}
addprim(0,"polyline",pts);
//-----------
pts = {};
float y = chf('yMax');
int ptA = addpoint(0,scalept(set(0.0,y,0)));
int ptB = addpoint(0,scalept(set(1.0,y,0)));
append(pts,ptA);
append(pts,ptB);
addprim(0,"polyline",pts);
//---x-------
pts = {};
ptA = addpoint(0,scalept(curPos+set(-.01,-.01,0)));
ptB = addpoint(0,scalept(curPos+set(.01,.01,0)));
append(pts,ptA);
append(pts,ptB);
int line = addprim(0,"polyline",pts);
setprimattrib(0, 'Cd', line, set(1,0,0), 'set');
pts = {};
ptA = addpoint(0,scalept(curPos+set(.01,-.01,0)));
ptB = addpoint(0,scalept(curPos+set(-.01,.01,0)));
append(pts,ptA);
append(pts,ptB);
line = addprim(0,"polyline",pts);
setprimattrib(0, 'Cd', line, set(1,0,0), 'set');
Connect random points by direction
WIP-ish but was enough for what I needed might polish later (I did add some error checking though why the fuck...)
This code takes two points or two group of points (of equal size) and attemps to connect points that are in the direction of a,b pairs given a maximum cone angle. This can be useful if you have randomly sorted points and the Connect Adjacent Pieces + Find Shortest Path SOPSs arent doing what you want. Which was my case, or I might have over-engineered something stupid.
TBD:
- Allow for adaptive cone width (if you get increasingly sharper 'turns')
- Alternatively read from point value (like injecting a sharp change of direction)
- Give preference to an axis (imagine a tightly wound spiral) so that it still works and doesn't pick up weird points
- Give priorities to points that have confidence in the previous points, remove them from the available point list (would correct the example in my video)
int a[],b[],bigAssPointsArray[];
vector dirs[];
vector posA,posB;
// channel values values hit the button to create -->
string grp = chs('group');
float dist = ch('max_search_distance'); // 1
float cone = radians(ch('cone_degrees')); // 10
int iterations = chi('iteration'); // 4
// V use direction if no second set of point(s) found:
vector dir = normalize(chv('start_dir')); // 1 0 0
// grp cleaning
string error = '';
if(grp != ''){
//if we found groups:
if(find(grp,' ')>0){
//if there is more than one it means we have two separate lists (or two points)
string grps[] = split(grp,' ',1);
if(len(grps)>2){
error = 'more than two groups found!';
}else{
a = expandpointgroup(0,grps[0],"ordered");
b = expandpointgroup(0,grps[1],"ordered");
if(len(a)!=len(b)){
error = 'the two groups have different point counts';
}
}
}else{
b = expandpointgroup(0,grp,"ordered");
}
}else{
error = 'at least group or point needed';
}
if(error!=''){
error(error);
return;
}
// do the work
if(len(a)==0){
//no second group, fill directions array with initial start dir
for(int i = 0 ; i < len(b) ; i ++){
append(dirs,dir);
}
}else{
//if there are two destinct groups, create a vector array with initial directions
for(int i = 0 ; i < len(b) ; i ++){
posA = point( 0 , 'P' , a[i] );
posB = point( 0 , 'P' , b[i] );
vector direction = normalize(posB-posA);
append(dirs,direction);
}
}
for(int j=0 ; j<len(b) ; j++){
//for every point of the second group start finding out if there are points in the dir direction
posB = point(0,'P',b[j]);
dir = dirs[j];
if(len(a)>0){
append(bigAssPointsArray,a[j]);
}
append(bigAssPointsArray,b[j]);
for(int i=1; i<=iterations;i++){
int points_found[] = pccone_radius(0,"P","none",0.0,posB,dir,cone,dist,2);
if(len(points_found)==1){
//if nothing is found during the point cloud cone search, color the last point and break forloop
setpointattrib(0,"Cd",points_found[0],{1,0,0},"set");
break;
}else{
//we found a point ! use it for the next iteration
int chosenpoint = points_found[1];
append(bigAssPointsArray,chosenpoint);
posA = posB;
posB = point(0,"P",chosenpoint);
dir = normalize(posB - posA);
}
}
/* we finished the max iterations, we could add the poly prim in the
for loop but houdini doesn't like it ? And no way to have multi
dimensional array so let's fill up a big ass one and split with
a non-existing point number -1 */
append(bigAssPointsArray,-1);
}
//build the poly lines
int pointsArray[] = {};
int pr = addprim(0,'polyline');
for(int i=0;i<len(bigAssPointsArray)-1;i++){
int pt = bigAssPointsArray[i];
if(pt == -1){
pr = addprim(0,'polyline');
}else{
int newpt = addpoint(0,pt);
addvertex(0,pr,pt);
}
}
Radial sort
sorting
vector bbcenter = getpointbbox_center(0); float dx = bbcenter.x-@P.x; float dz = bbcenter.z-@P.z; @angle = atan2(dx,dz); //radians
Then sort sop
Galaxy like animation with particles
vector bbcenter = set(0,0,0);
float dx = bbcenter.x-@P.x;
float dz = bbcenter.z-@P.z;
float mag = distance(@P,bbcenter);
@angle = atan2(dx,dz); //radians
float ringmult = snoise(@P);
ringmult = fit(ringmult,-1,1,.8,1.6);
@t=ringmult;
@angle += @Time * fit(rand(@ptnum),0,1,.5,.75) * 1 / pow(mag,ch('exp')) * ringmult;
@P.x = cos(@angle) * mag;
@P.z = sin(@angle) * mag;
Gradient Lattice
Simple XYZ<>splines deformer
vector bb = relbbox(0,@P);
vector m = getbbox_min(0);
vector M = getbbox_max(0);
@P.x = lerp(m.x,M.x,chramp("x", bb.r));
@P.y = lerp(m.y,M.y,chramp("y", bb.g));
@P.z = lerp(m.z,M.z,chramp("z", bb.b));
Move points to sdf surface
The second input is a number of iterations to refine the stickiness
//move points towards the surface of the sdf using gradient
vector p = @P;
int max = chi('max');
for(int i=0;i<max;i++){
vector noise = vector(noise(p)) - .5;
noise *= ch('noise') ;
float reach = volumesample(1,0,p+noise);
p -= normalize(volumegradient(1,0,p+noise))*reach*(1.0*(i+1)/max)*ch('mult');
}
@P = p;
Remove geometry that isn't in the other stream
better edit: keeping the code below if I have an edge case but it's faster (?) or at least clearer to use idtoprim() or idtopoint(), example here: How do I work on two objects that don't have the same number of points/prims?
For each prim check that it exists in the second input, if not, delete.
I use this when I have proxy geometry which I have deleted and need the same deletion on the hirez packed geo (so I can use the transform pieces) as shown in the image below
for(int i=0;i<nprimitives(0);i++){
string name = prim(0,"name",i);
if( findattribvalcount(1,"prim","name",name) < 1 ){
removeprim(0,i,1);
}
}
In case of points, let's say you split the stream, froze one bit at one frame, deleted a few particles, duplicated that setup a few times, here's one way to take the original stream and only remove particles that don't exist in the second input (i have special id called uid)
for(int i=0;i<npoints(0);i++){
int uid = point(0,"uid",i);
if( findattribvalcount(1,"point","uid",uid) < 1 ){
removepoint(0,i,1);
}
}
Remove duplicate prims
Only keep a single instance of a primitive that has specific attribute values on its corresponding points (think constraints):
Walkthrough:
- We first retrieve the prim's points' values and merge them
//in a prim wrangle int pt_ids[] = sort(primpoints(0,@primnum)); string point_A = point(0,"name",pt_ids[0]); string point_B = point(0,"name",pt_ids[1]); s@linkedpoints = join(sort(array(point_A,point_B)),"+");
- We sort prims by that concatenated attribute (like "nameA+nameB")
'Sort' sop by prim attribute "linkedpoints"
- We finally go through the prims and look if the preceding prim already has that attribute value. If so, we deleted the prim
//in a detail wrangle
string previous_linkedpoints = prim(0,"linkedpoints",0);
for(int i=1;i<nprimitives(0);i++){
string current_linkedpoints = prim(0,"linkedpoints",i);
if( current_linkedpoints == previous_linkedpoints ){
removeprim(0,i,1);
}
previous_linkedpoints = current_linkedpoints;
}
Hanging wire (parabola/catanery)
https://en.wikipedia.org/wiki/Catenary
// takes points and creates hanging 'wire' points between said points, using the catenery formula.
// Use in a detail wrangle, make sure to hit the 'Create spare parameter on the right >' and add
// values like curve factor 3 and Rez 10
vector pts[];
int data[];
float curve = ch('curve_factor'); // > 0, gets close to a flat line when you go above 5
int rez = chi('rez'); //number of points to create between given points (assumes the points are at uniform-ish distances)
rez+=1;
vector firstpointpos = point(0,'P',0);
append(pts,firstpointpos);
append(data,1);
removepoint(0,0);
//go through initial points and append catenary coordinates to array with given resolution
for(int i=1;i<npoints(0);i++){
vector a = point(0,'P',i-1);
vector b = point(0,'P',i);
for(int j=1;j<rez;j++){
float lerpV = j*1.0/rez; // ( 0 < lerpV < 1 )
vector mix = lerp(a,b,lerpV);
mix.y = mix.y + curve * cosh((lerpV-.5)/curve);
mix.y -= curve * cosh(-.5/curve);
append(pts,mix);
append(data,0);
}
append(pts,b);
append(data,1);
removepoint(0,i); //delete original point
}
//create points by reading array, adding group attributes as we go
int newpts[] = {};
for (int k = 0; k<len(pts);k++){
int newpoint = addpoint(0,pts[k]);
append(newpts,newpoint);
setpointgroup(0, "original", newpoint, data[k], "set");
if(data[k]){
setpointattrib(0,"Cd",newpoint,{1,0,0});
}else{
setpointattrib(0,"Cd",newpoint,{0,0,1});
}
}
//join with polyline
addprim(0,"polyline",newpts);
Torus / Helix (Toroidal Helical Coil)
float t = 1.0*@elemnum/@numelem ;
float completion = ch('completion') * 2 * $PI;
float coils = ch('coils');
float R = ch('outerRadius');
float r = ch('innerRadius');
float u = t * completion * coils ;
float v = t * completion ;
float x = cos(v)*(R+r*cos(u));
float y = sin(v)*(R+r*cos(u));
float z = r * sin(u);
@P = set(x,y,z);
List of things to Read
* Jake rice primUvs / split / greeble: https://github.com/jakericedesigns/Poly-Splitting-Blog * Un-catmullclarking https://dspace5.zcu.cz/bitstream/11025/6630/1/Laquentin.pdf
Get primitive angles
Calculates the minimum angle (sharp) of each triangle
//if each prim = triangle //get the 3 prim points int pp[] = primpoints(0,@primnum); //grab their positions vector a = point(0,"P",pp[0]); vector b = point(0,"P",pp[1]); vector c = point(0,"P",pp[2]); //get the vectors to calculate angle vector ab = b-a; vector ac = c-a; vector cb = b-c; //get angles from law of cosines: arccos(Ab.Ac) where Ab and Ac are normalized vectors float angleA = degrees(acos(dot(normalize(ab),normalize(ac)))); float angleB = degrees(acos(dot(normalize(-ab),normalize(-cb)))); float angleC = 180-angleA-angleB; //@angleC = degrees(acos(dot(normalize(-ac),normalize(cb)))); //@angletotal = @angleA+@angleB+@angleC; // <-- should always be 180! @minAngle = min(angleA,min(angleB,angleC));
Oriented bounding box matrix transform
(2024 edit: I think this is broken I will look into it, in the meantime there is a labs sop that does the same thing, better) Applying https://vimeo.com/214584753 with wrangle: rotates points with given perpendicular vectors expects oriented BB from 'box' as a second input
vector p0 = point(1,"P",0); vector p3 = point(1,"P",3); vector p4 = point(1,"P",4); vector x = normalize(p3-p0); vector z = normalize(p4-p0); vector y = cross(x,z); matrix m = set(x[0],x[1],x[2],0,y[0],y[1],y[2],0,z[0],z[1],z[2],0,0,0,0,0); @P = invert (m)*@P;
Plexus like effect
Full copypastable code on http://pastebin.com/raw/nB9GLeiZ
Trails can be done with the entagma tutorial: http://www.entagma.com/creating-geometry-with-vex/
//create an attribute on prims that will store the unique hash so we can remove duplicates later on
if(!hasprimattrib(0,'hash'))
{
addprimattrib(0,'hash', 0, 'int');
}
//fetch the neighbours in an array
int neighbours[] = nearpoints(0, @P, ch('radius'), (chi('neighbours')+1) )[1:];
int neighbourCount = len(neighbours);
//only create triangles if there's more than one neighbour
if (neighbourCount > 1)
{
//for each neighbour, create all possible triangles with current point (ie for 'a' and b,c,d => abc abd acd)
for(int i=0;i<neighbourCount;i++)
{
for(int j=i+1;j<neighbourCount;j++)
{
//create an array with the 3 current points to create a triangle
int sortPoints[];
sortPoints[0] = @ptnum;
sortPoints[1] = neighbours[i];
sortPoints[2] = neighbours[j];
sortPoints = sort(sortPoints);
//create triangle
int prim = addprim(0,'poly');
addvertex(0,prim,sortPoints[0]);
addvertex(0,prim,sortPoints[1]);
addvertex(0,prim,sortPoints[2]);
//generate a 'hash' of the triangle prim, so we can remove duplicates later on
//for points 1,3,0 it will be hash(013) --> 67429030
int rhash = random_ihash(atoi(itoa(sortPoints[0])+itoa(sortPoints[1])+itoa(sortPoints[2])));
setprimattrib(0, 'hash', prim, rhash, 'set');
}
}
}