├── .gitignore ├── examples.hipnc ├── img ├── sop_geometry.jpg ├── dop_object_box.jpg ├── geo_vop_inputs.jpg ├── sop_solver_geo.jpg ├── dop_object_volume.jpg ├── geo_wrangle_inputs.jpg ├── scalar_field_vis.jpg ├── sop_scalar_field.jpg ├── sop_solver_volume.jpg ├── gas_field_vop_inputs.jpg ├── geo_vop_inputs_myself.jpg ├── gas_field_wrangle_inputs.jpg └── gas_field_wrangle_dop_sop_data.jpg ├── vex └── include │ └── myLib.h └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /backup/ 2 | *.xfc -------------------------------------------------------------------------------- /examples.hipnc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/examples.hipnc -------------------------------------------------------------------------------- /img/sop_geometry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/sop_geometry.jpg -------------------------------------------------------------------------------- /img/dop_object_box.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/dop_object_box.jpg -------------------------------------------------------------------------------- /img/geo_vop_inputs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/geo_vop_inputs.jpg -------------------------------------------------------------------------------- /img/sop_solver_geo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/sop_solver_geo.jpg -------------------------------------------------------------------------------- /img/dop_object_volume.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/dop_object_volume.jpg -------------------------------------------------------------------------------- /img/geo_wrangle_inputs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/geo_wrangle_inputs.jpg -------------------------------------------------------------------------------- /img/scalar_field_vis.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/scalar_field_vis.jpg -------------------------------------------------------------------------------- /img/sop_scalar_field.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/sop_scalar_field.jpg -------------------------------------------------------------------------------- /img/sop_solver_volume.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/sop_solver_volume.jpg -------------------------------------------------------------------------------- /img/gas_field_vop_inputs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/gas_field_vop_inputs.jpg -------------------------------------------------------------------------------- /img/geo_vop_inputs_myself.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/geo_vop_inputs_myself.jpg -------------------------------------------------------------------------------- /img/gas_field_wrangle_inputs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/gas_field_wrangle_inputs.jpg -------------------------------------------------------------------------------- /img/gas_field_wrangle_dop_sop_data.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/vex_tutorial/HEAD/img/gas_field_wrangle_dop_sop_data.jpg -------------------------------------------------------------------------------- /vex/include/myLib.h: -------------------------------------------------------------------------------- 1 | // include guards: those two lines and #endif at the end of this file will prevent multiple including of this header 2 | #ifndef _myLib_ 3 | #define _myLib_ 4 | 5 | 6 | 7 | // you can use macros to define constants and use them in your code 8 | #define MY_INT 123 9 | #define MY_FLOAT 3.1415926 10 | 11 | // you can also create alias to the function 12 | #define RENAMEDPOWER pow 13 | 14 | // or use macros for defining new functions 15 | #define ADDTEN(val) (val+10) 16 | 17 | 18 | 19 | // void functions do not return anything 20 | // "function" keyword is not required 21 | function void myRemPoints(int ptnum) { 22 | if (ptnum > 30) 23 | removepoint(0, ptnum); 24 | } 25 | 26 | // function parameters are passed by reference automatically, without additional syntax 27 | // (function receive the original variable, not its copy) 28 | void scaleByTen(vector P) { 29 | P *= 10; 30 | } 31 | 32 | // you can prevent changing input variable references 33 | void changeA(int a; const int b; int c) { 34 | a += 10; 35 | //b += 10; // uncommenting this line will result in error 36 | c = a; 37 | c += 4; // even though arguments are passed by reference, they are not true references, "c" is still independent from "a" 38 | } 39 | 40 | // testing escaping of special characters 41 | // here it requires two backslashes :D I am wondering about a legendary place where it needs only one :) 42 | void printTestEscaping() { 43 | string a = "from myLib.h: \\n \\t v\@P, %04.2f"; 44 | printf(a + "\n"); 45 | } 46 | 47 | // a function returning float value 48 | float superRandom(vector4 seeds) { 49 | float out = rand(seeds.x * seeds.y * seeds.z * seeds.w); 50 | return out; 51 | } 52 | 53 | // a function returnig an array 54 | int[] range(int max) { 55 | int out[]; 56 | 57 | for(int i=0; igetFullName() == B->getFullName()) match = 1; 149 | 150 | return match; 151 | } 152 | 153 | // func returning hipFile type 154 | // this function expects comma separated list of filenames and will 155 | // return the first occurance of a hip file 156 | hipFile findFirstHipFile(string text) { 157 | string inFiles[] = split(text, ","); 158 | string hipParts[]; 159 | 160 | foreach(string file; inFiles) { 161 | string parts[] = split(file, "."); 162 | if (parts[-1] == "hip" || parts[-1] == "hipnc") { 163 | hipParts = parts; 164 | break; 165 | } 166 | } 167 | 168 | // we can also return error state, warning() function is also available 169 | if (len(hipParts) == 0) error("No houdini project found in this file list: %+s.", text); 170 | 171 | string prefix[] = split(hipParts[0], "_"); 172 | int ver = atoi( prefix[-1] ); 173 | string base = join( prefix[:-1], "_"); 174 | string ext = hipParts[1]; 175 | 176 | hipFile out = hipFile(base, ext, ver); 177 | return out; 178 | } 179 | 180 | // we can as well return an array of structs 181 | hipFile[] findAllHipFiles(string text) { 182 | string inFiles[] = split(text, ","); 183 | hipFile hips[]; 184 | 185 | foreach(string file; inFiles) { 186 | string parts[] = split(file, "."); 187 | if (parts[-1] == "hip" || parts[-1] == "hipnc") { 188 | string prefix[] = split(parts[0], "_"); 189 | int ver = atoi( prefix[-1] ); 190 | string base = join( prefix[:-1], "_"); 191 | string ext = parts[1]; 192 | 193 | hipFile out = hipFile(base, ext, ver); 194 | push(hips, out); 195 | } 196 | } 197 | 198 | // output a warning when no Houdini projects were found 199 | if (len(hips) == 0) warning("No Houdini projects found."); 200 | 201 | return hips; 202 | } 203 | 204 | 205 | #endif // end of include guards -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VEX tutorial 2 | 3 | *A collection of code snippets and examples showing syntax and capabilities of VEX language inside SideFX Houdini* 4 |

by Juraj Tomori

5 | 6 | # How to use it 7 | You can clone, or [directly download](https://github.com/jtomori/vex_tutorial/archive/master.zip) this repository. 8 | 9 | It contains `examples.hipnc` and `vex/include/myLib.h` files which are full of various examples with explanations in comments. 10 | 11 | It is the best to check all the nodes with open *Geometry Spreadsheet* and *Console Output* windows to see values of attributes and output text. Alternatively you can use this page for quick looking at the topics covered and most of the code that I include here as well. I am not including here all of the code since sometimes it might not make a lot of sense outside of Houdini. Where necessary I include related functions from *myLib.h* or attach screenshots. 12 | 13 | # Topics 14 | * [Reading parameter values](#reading-parameter-values) 15 | * [Reading attributes](#reading-attributes) 16 | * [Exporting attributes](#exporting-attributes) 17 | * [Reading arrays](#reading-arrays) 18 | * [Arrays](#arrays) 19 | * [Arrays and strings example](#arrays-and-strings-example) 20 | * [Reading and writing Matrices](#reading-and-writing-matrices) 21 | * [Checking for attributes](#checking-for-attributes) 22 | * [Automatic attribute creation](#automatic-attribute-creation) 23 | * [Getting transformation from OBJs](#getting-transformation-from-objs) 24 | * [Intrinsics](#intrinsics) 25 | * [VDB intrinsics](#vdb-intrinsics) 26 | * [Volumes](#volumes) 27 | * [VOPs / Using Snippets](#vops--using-snippets) 28 | * [VOPs / Using Inline Code](#vops--using-inline-code) 29 | * [DOPs / Volumes workflow](#dops--volumes-workflow) 30 | * [DOPs / Gas Field Wrangle](#dops--gas-field-wrangle) 31 | * [DOPs / Gas Field Wrangle - accessing DOPs and SOPs data](#dops--gas-field-wrangle---accessing-dops-and-sops-data) 32 | * [DOPs / Geometry workflow](#dops--geometry-workflow) 33 | * [DOPs / Geometry Wrangle](#dops--geometry-wrangle) 34 | * [DOPs / Geometry Wrangle - accessing fields](#dops--geometry-wrangle---accessing-fields) 35 | * [Conditions](#conditions) 36 | * [Loops](#loops) 37 | * [Stopping For-Each SOP from VEX](#stopping-for-each-sop-from-vex) 38 | * [Printing and formatting](#printing-and-formatting) 39 | * [Printing attributes](#printing-attributes) 40 | * [Including external VEX files](#including-external-vex-files) 41 | * [Include math.h](#include-mathh) 42 | * [Using macros](#using-macros) 43 | * [Functions](#functions) 44 | * [Functions overloading](#functions-overloading) 45 | * [Variables casting](#variables-casting) 46 | * [Vectors swizzling](#vectors-swizzling) 47 | * [Functions casting](#functions-casting) 48 | * [Structs](#structs) 49 | * [Structs in Attribute Wrangle](#structs-in-attribute-wrangle) 50 | * [Groups](#groups) 51 | * [Attribute typeinfo](#attribute-typeinfo) 52 | * [Attributes to create](#attributes-to-create) 53 | * [Enforce prototypes](#enforce-prototypes) 54 | * [Attribute default values](#attribute-default-values) 55 | 56 | # Tutorial 57 | 58 | ## Reading parameter values 59 | ~~~ C linenumbers 60 | /* 61 | multi-line comments can be typed 62 | using this syntax 63 | */ 64 | 65 | // in vex you can evaluate values from parameters on this node 66 | // by calling ch*() function, with * representing a signature, check the docs 67 | // for the full list, some of them: chv() - vector, chu() - vector2, chs() - string 68 | // chramp() - ramp, chp() - vector4, chi() - int, chf() - float, ch4() - matrix, ch3() - matrix3 ... 69 | // you can also use optioinal argument for time which will enable you to evaluate 70 | // the channel at different frame 71 | // 72 | // once you type ch*() in your code, you can press a button on the right, to 73 | // generate a UI parameter for it automatically, you can do the same by hand as well 74 | float y = chf("y_position"); 75 | vector col = chv("color"); 76 | matrix3 xform = ch3("xform"); 77 | 78 | // you can also reference parameters from external nodes 79 | // if there is an expression (Python/hscript) in the parameter, 80 | // it will be evaluated 81 | float up = chf("../params_1/move_up"); 82 | 83 | 84 | // apply variables to attributes 85 | v@P.y += y*5; 86 | v@Cd = col; 87 | v@P *= xform; 88 | v@P.y += up; 89 | 90 | v@myVec = 1.456; 91 | v@myVec += v@N.y; 92 | ~~~ 93 | 94 | ## Reading attributes 95 | ~~~ C linenumbers 96 | float blend = chf("blend"); 97 | float blendPig = chf("blend_pig"); 98 | vector P1, P2, P3, P_new; 99 | 100 | // this is one way of reading attributes, this is only valid, when 101 | // point count is exactly the same in both inputs, then attribute from 102 | // point from second input with the same @ptnum is retrieved 103 | // v@P can also be replaced with @P, since its signature can be guessed as it is 104 | // commonly used attribute, however I prefer explicit declaration :) 105 | // v@ - vector, i@ - integer, f@ - float, 3@ - matrix3, p@ - vector4 106 | // 4@ - matrix4, 2@ - matrix2, u@ - vector2, s@ - string, 107 | //P1 = v@P; 108 | //P2 = v@opinput1_P; // inputs numbering starts at 0, therefore 1 refers to the second input 109 | 110 | // this approach is useful for querying attributes from different points (other from the currently processed one) 111 | // node input numbering starts from 0 (first input), 1 (second input) ... 112 | P1 = point(0, "P", @ptnum); 113 | P2 = point(1, "P", @ptnum); 114 | 115 | // note that you can also read attributes from node, which is not connected 116 | // to the current node using the "op:" syntax 117 | // this is valid for any function which is expecting geo handle (sampling from other volumes...) 118 | // note that Houdini network UI will not detect this dependency when Show -> Dependency links display is enabled 119 | P3 = point("op:../pig_shape", "P", @ptnum); 120 | 121 | 122 | // blend positions 123 | P_new = lerp(P1, P2, blend); 124 | P_new = lerp(P_new, P3, blendPig); 125 | 126 | v@P = P_new; 127 | ~~~ 128 | 129 | ## Exporting attributes 130 | ~~~ C linenumbers 131 | // create a new attribute simply by typing *@attrib_name with 132 | // * representing its signature 133 | // v@ - vector, i@ - integer, f@ - float, 3@ - matrix3, p@ - vector4 134 | // 4@ - matrix4, 2@ - matrix2, u@ - vector2, s@ - string 135 | 136 | v@myVector = {1,2,3}; 137 | // vectors with functions/variables in them need to be created with set() 138 | u@myVectorFunc = set(@Frame, v@P.y); 139 | u@myVector2 = {4,5}; 140 | f@myFloat = 400.0; 141 | i@myInteger = 727; 142 | 3@myMatrix3x3 = matrix3( ident() ); // this line contains function casting, which is explained in functions_casting section 143 | 4@myMatrix4x4 = matrix( ident() ); 144 | s@myString = "abc"; 145 | 146 | 147 | // attributes can be set to different point from the currently processed one 148 | // and if they do not exist, they need to be added first 149 | // setpointattrib() is also the only way of setting an attribute on newly 150 | // created points 151 | addpointattrib(0, "Cd", {0,0,0}); 152 | setpointattrib(0, "Cd", 559, {1,0,0}); 153 | 154 | // arrays can be exported as well 155 | v[]@myVectorArray = { {1,2,3}, {4,5,6}, {7,8,9} }; 156 | u[]@myVector2Array = { {4,5}, {6,7} }; 157 | f[]@myFloatArray = { 4.0, 2.7, 1.3}; 158 | i[]@myIntegerArray = {132, 456, 789}; 159 | // arrays containing functions/variables need to be initialized with array() function 160 | 3[]@myMatrix3x3Array = array( matrix3( ident() ), matrix3( ident() ) * 5 ); 161 | 4[]@myMatrix4x4Array = array( matrix( ident() ), matrix( ident() ) * 9 ); 162 | s[]@myStringArray = { "abc", "def", "efg" }; 163 | ~~~ 164 | 165 | ## Reading arrays 166 | ~~~ C linenumbers 167 | // this is how you can create local array variables and load array attributes into them 168 | vector myVectorArray[] = v[]@myVectorArray; 169 | 170 | matrix3 a = ident() * 5; 171 | 172 | v@P.x *= a.yy; // you can access matrix components using this syntax 173 | // x -> 1st element, y -> 2nd, z -> 3rd, w -> 4th 174 | v@P.y = 4[]@myMatrix4x4Array[1].ww; // second array matrix, last element 175 | v@P.z = u[]@myVector2Array[1][0]; // this is how you can access array of vectors - second array, first element 176 | ~~~ 177 | 178 | ## Arrays 179 | ~~~ C linenumbers 180 | int numbers[] = array(1,2,3,4); 181 | 182 | // arrays can be handled in Pythonic way 183 | numbers = numbers[::-1]; // array reverse 184 | 185 | // rading from arrays 186 | i@firstItem = numbers[0]; 187 | // writing into arrays 188 | numbers[0] += 1; 189 | // indexing can also go backwards 190 | i@secondLastItem = numbers[-2]; 191 | 192 | // slicing 193 | i[]@firstHalf = numbers[:2]; 194 | i[]@secondHalf = numbers[2:]; 195 | 196 | // some useful functions 197 | i@returnedPopVal = pop(numbers); // removes the last element and returns it 198 | push(numbers, i@returnedPopVal); // appends element to the array 199 | i@lenghtOfArray = len(numbers); 200 | 201 | // export into integer array attribute 202 | i[]@numbers = numbers; 203 | 204 | // flattening an array of vectors and reverting it 205 | vector vectors[] = { {1,2,3}, {4,5,6}, {7,8,9} }; 206 | f[]@serializedVectors = serialize(vectors); 207 | v[]@unserializedFloats = unserialize(f[]@serializedVectors); 208 | ~~~ 209 | 210 | ## Arrays and strings example 211 | ~~~ C linenumbers 212 | // simple example of manipulating strings and arrays 213 | // it will convert /path/to/the/project/file/project_v3.hipnc 214 | // into /path/to/the/project/file/preview/project_v3_img_0001.jpg 215 | // with 0001 being current frame number 216 | 217 | string path = chs("path"); // get string from path parameter of the current hipfile 218 | s@pathOrig = path; // store into attribute original value 219 | 220 | string pathSplit[] = split(path, "/"); // split path into array of strings based on "/" character 221 | 222 | string fileName = pop(pathSplit); // remove last value of the array and assign it into a variable 223 | string fileNameSplit[] = split(fileName, "."); // split string into an array based on "." character 224 | fileNameSplit[0] = fileNameSplit[0] + sprintf("_img_%04d", @Frame); // append into the string _img_0001 (current frame number) 225 | fileNameSplit[-1] = "jpg"; // change file extension 226 | fileName = join(fileNameSplit, "."); // convert array of strings into a one string with "." between original array elements 227 | push(pathSplit, "preview"); // append "preview" string into the array of strings 228 | push(pathSplit, fileName); // append file name into the array of strings 229 | 230 | path = "/" + join(pathSplit, "/"); // convert array of strings into one string, starting with "/" for root, because it is not added before the first element, only to in-betweens 231 | 232 | s@path = path; // output into the attribute 233 | ~~~ 234 | 235 | ## Reading and writing Matrices 236 | ~~~ C linenumbers 237 | // To intilize a vector or a matrix with a variable/attribute we have to use the set method 238 | float f = 0; 239 | vector v = set(f,f,f); 240 | matrix3 m = set(f,f,f,f,f,f,f,f,f); 241 | m = set(v,v,v); 242 | 243 | // We can then read the value back by 244 | float f = getcomp(m,0,0); 245 | // However we can not read back a vector directly instead we have to use: 246 | f = set(getcomp(m,0,0)); 247 | v = set(getcomp(m,0,0), 248 | getcomp(m,0,1), 249 | getcomp(m,0,2)); 250 | // Likewise the setcomp can be used 251 | setcomp(m,f,0,0); 252 | ~~~ 253 | 254 | ## Checking for attributes 255 | ~~~ C linenumbers 256 | // it is also possible to determine if incoming geometry has an attribute 257 | 258 | i@hasCd = hasattrib(0, "point", "Cd"); 259 | i@hasN = hasattrib(0, "point", "N"); 260 | i@hasOrient = hasattrib(0, "point", "orient"); 261 | i@hasPscale = hasattrib(0, "point", "pscale"); 262 | ~~~ 263 | 264 | ## Automatic attribute creation 265 | ~~~ C linenumbers 266 | // if you use @attribute and it does not exist, 267 | // then it will be automatically created 268 | // this might lead to problems, when you have a typo and a 269 | // new attribute is created 270 | 271 | f@foo = 4; 272 | f@boo = v@Cd.y; 273 | //v@CD = {1,1,0}; // this line does not work here, otherwise it would crete new v@CD attribute 274 | 275 | // if you remove * chracter from "Attributes to Create" parameter 276 | // bellow, then you need to manually specify new attributes 277 | // to be created, if you then have a typo and use v@CD instead 278 | // of v@Cd, node will report an error 279 | ~~~ 280 | 281 | ## Getting transformation from OBJs 282 | ~~~ C linenumbers 283 | // VEX is well integrated into Houdini, it can for example fetch 284 | // world space transformation matrix from an OBJ node, let it be a null OBJ 285 | // or part from a rig, camera or whatever object there which has a transformation 286 | // optransform() will contain also all parent transformations 287 | 288 | string nodePath = chs("node_path"); // parameter is a string, but I went into "Edit Parameter Interface" and specified it to be a Node Path 289 | matrix xform = optransform(nodePath); 290 | 291 | v@P *= invert(xform); 292 | ~~~ 293 | 294 | ## Intrinsics 295 | ~~~ C linenumbers 296 | // a lot of information and functionality is stored in 297 | // "intrinsic" attributes, they might be hidden to many users 298 | // because they do not show up in primitive attributes list 299 | // by default 300 | // they are however very useful and important for manipulating 301 | // and controlling those primitives from VEX 302 | 303 | // you can display intrinsics in Geometry Spreadsheet, in primitive attributes 304 | // and Show All Intrinsics in Intrinsics drop-down menu 305 | // intrinsics that are greyed out are read-only, the rest is also writable 306 | 307 | // in this example I will show you how to access and modify those 308 | // values, I will refer to them based on their point numbers as 309 | // those intrinsics vary based on the primitive we are dealing with 310 | // 0 - packed geo, 1 - VDB, 2 - sphere, 3 - packed disk, 4 - abc 311 | // if you change order of inputs in merge, then those numbers will change 312 | // in our case @ptnum matches @primnum 313 | 314 | // this is the type of our primitive 315 | s@primitiveType = primintrinsic(0, "typename", @ptnum); 316 | 317 | matrix3 xform = ident(); 318 | 319 | // sphere 320 | if (@ptnum == 2) { 321 | // accessing sphere volume and area 322 | f@sphereVolume = primintrinsic(0, "measuredvolume", @ptnum); 323 | f@sphereArea = primintrinsic(0, "measuredarea", @ptnum); 324 | 325 | // changing sphere scale, rotation which is not accessible through standard attributes 326 | // this intrinsic is also present on other primitives 327 | xform = primintrinsic(0, "transform", @ptnum); 328 | scale(xform, {1,2,3}); 329 | rotate(xform, radians(45), normalize({1,2,3}) ); 330 | } 331 | 332 | // packed geo 333 | if (@ptnum == 0) { 334 | // change viewport display to point cloud 335 | setprimintrinsic(0, "viewportlod", @ptnum, "points", "set"); 336 | 337 | // move packed's pivot to the bottom 338 | // bounds are stared in this order: xmin, xmax, ymin, ymax, zmin, zmax 339 | float bounds[] = primintrinsic(0, "bounds", @ptnum); 340 | vector pivot = primintrinsic(0, "pivot", @ptnum); 341 | pivot.y = bounds[2]; 342 | setprimintrinsic(0, "pivot", @ptnum, pivot, "set"); 343 | v@P.y = bounds[2]; 344 | 345 | // and now transform the primitive along its new pivot :) 346 | xform = primintrinsic(0, "transform", @ptnum); 347 | scale(xform, {1,2,3}); 348 | rotate(xform, radians(-45), normalize({1,2,3}) ); 349 | } 350 | 351 | // packed disk 352 | if (@ptnum == 3) { 353 | // changing this intrinsic can point the primitive into different geometry on disk 354 | // so instead of boring cube let's have something way cooler 355 | // this is very powerful - e.g. controling your instances, see my other blog post about using it 356 | // https://jurajtomori.wordpress.com/2016/09/29/rain-and-ripples-rnd/ 357 | setprimintrinsic(0, "unexpandedfilename", @ptnum, "$HH/geo/HoudiniLogo.bgeo", "set"); 358 | 359 | // and our mandatory transformation :) 360 | xform = primintrinsic(0, "transform", @ptnum); 361 | scale(xform, 5); 362 | rotate(xform, radians(-90), {1,0,0} ); 363 | } 364 | 365 | // alembic 366 | if (@ptnum == 4) { 367 | // make him move in the loop, he has 48 frames of animation 368 | float frame = @Frame / 24; 369 | frame = frame % 2; 370 | setprimintrinsic(0, "abcframe", @ptnum, frame, "set"); 371 | 372 | // get some useful info 373 | s@abcObjPath = primintrinsic(0, "abcobjectpath", @ptnum); 374 | s@abcFilePath = primintrinsic(0, "abcfilename", @ptnum); // also useful intrinsic 375 | s@abcType = primintrinsic(0, "abctypename", @ptnum); 376 | s@abcVis = primintrinsic(0, "viewportlod", @ptnum); 377 | 378 | // scale the bear up 379 | xform = primintrinsic(0, "transform", @ptnum); 380 | scale(xform, 20); 381 | } 382 | 383 | // all of the primitives have "transform" intrinsic, so I update it at the end 384 | setprimintrinsic(0, "transform", @ptnum, xform, "set"); 385 | ~~~ 386 | 387 | ## VDB intrinsics 388 | ~~~ C linenumbers 389 | // for some reason updating VDB's transform and other primitives transform 390 | // did not work properly from one wrangle, so I put it here 391 | // VDB's transform is 4x4 matrix, while other prims have 3x3 matrices 392 | 393 | // VDB 394 | if (@ptnum == 1) { 395 | // accessing useful information 396 | i@vdbVoxels = primintrinsic(0, "activevoxelcount", @ptnum); 397 | s@vdbClass = primintrinsic(0, "vdb_class", @ptnum); 398 | s@vdbType = primintrinsic(0, "vdb_value_type", @ptnum); 399 | s@vdbVis = primintrinsic(0, "volumevisualmode", @ptnum); 400 | v@vdbVoxelSize = primintrinsic(0, "voxelsize", @ptnum); 401 | 402 | // changing volume transformation 403 | matrix xform4 = primintrinsic(0, "transform", @ptnum); 404 | scale(xform4, {1,2,3}); 405 | rotate(xform4, radians(-45), {1,0,0}); 406 | setprimintrinsic(0, "transform", 1, xform4, "set"); 407 | 408 | // setting volume export precision to half float, which saves space when written to disk 409 | setprimintrinsic(0, "vdb_is_saved_as_half_float", @ptnum, 1, "set"); 410 | } 411 | ~~~ 412 | 413 | ## Volumes 414 | ~~~ C linenumbers 415 | // float volumes can be accessed with volumesample(), vector volumes need 416 | // volumesamplev() function, those functions expect sampling position, which 417 | // does not need to match voxel's center, then the value will be tri-linearly 418 | // interpolated from neighbouring voxels 419 | float den1 = volumesample(0, "density", v@P); 420 | 421 | // sampling position can be offset and lots of cool tricks and magic can be 422 | // acheived with that 423 | vector offset = set( 2, sin(@Frame * .1)*2 , 0 ); 424 | float den2 = volumesample(1, "density", v@P + offset); 425 | 426 | // writing to volumes has the same syntax as writing to attributes 427 | f@density = lerp(den1, den2, chf("blend") ); 428 | 429 | // volumes can be accessed with the same syntax as geometry attributes 430 | f@density = f@density * chf("scale"); 431 | ~~~ 432 | 433 | ## VOPs / Using Snippets 434 | *Check Houdini project to get the best idea of how it works.* 435 | ~~~ C linenumbers 436 | // in snippets we do not need to use any sign for variables, only 437 | // their name is needed 438 | // it is also possible to rename them for this node in 439 | // Vaiable Name parameters bellow 440 | 441 | // to output a new variable, we need to input a constant 442 | // into the node which will generate output for it (N, color) 443 | // even though in the UI it is called outN and outcolor, we only 444 | // need to write to N and color 445 | 446 | N = normalize(P); 447 | dir = normalize(dir); 448 | 449 | float mix = dot(dir, N); 450 | mix = clamp(mix, 0, 1); 451 | 452 | noise = abs(noise); 453 | noise = clamp(noise, 0, 1); 454 | 455 | color = lerp(frontColor, noise, 1-mix); 456 | ~~~ 457 | 458 | ## VOPs / Using Inline Code 459 | *Check Houdini project to get the best idea of how it works.* 460 | ~~~ C linenumbers 461 | // here we can create additional output variables 462 | // however we cannot write to read_only variables (which 463 | // are not exported), therefore for loading $dir I create 464 | // direction variable and for loading $noise noise_in variable 465 | 466 | $N = normalize(P); 467 | vector direction = normalize(dir); 468 | 469 | float mix = dot(direction, N); 470 | mix = clamp(mix, 0, 1); 471 | 472 | vector noise_in = abs(noise); 473 | noise_in = clamp(noise_in, 0, 1); 474 | 475 | $color = lerp($frontColor, noise_in, 1-mix); 476 | ~~~ 477 | 478 | ## DOPs / Volumes workflow 479 | Here I will show basic steps of creating a simple custom DOP solver operating on volumes. 480 | 481 | 1. At first we need to create a *DOP Object*, which is a container that will contain all our fields (volumes in DOPs), geometry and any other data. Object name is important, because later we will use it to access our data. 482 | ![DOP Object](./img/dop_object_volume.jpg) 483 | 484 | 2. In the second step we want to bring in a volume from SOPs. We can use *SOP Scalar Field* node. This node will create field *pig_in*, which you can see in *Geometry Spreadsheet*. *Use SOP Dimensions* option is handy as we do not need to set resolution, size and other parameters by hand. *Border Type* might be useful to have set to *Constant* as it will not introduce infinite streaks when voxels are touching boundaries. *SOP Path* points to the SOP we want to get volume from and *Primitive Number* will identify which volume primitive to import. *Default Operation* when set to *Set Initial* will import the field only at first simulated frame, if you want to import animated volume, set it to *Set Always*. *Data Name* is important as it will be unique identifier for our volume. 485 | ![SOP Scalar Field](./img/sop_scalar_field.jpg) 486 | 487 | 3. DOPs can contain lots of fields and therefore they are not visible by default. To display them in viewport, we can use *Scalar Field Visualization* node. 488 | ![Scalar Field Visualization](./img/scalar_field_vis.jpg) 489 | 490 | 4. If we want to sample different volume, or sample a volume at different location, we need to set up *Inputs* properly. This is needed for *Gas Field Wrangle* and *Gas Field VOP*. 491 | ![Gas Field Wrangle Inputs](./img/gas_field_wrangle_inputs.jpg) 492 | ![Gas Field VOP Inputs](./img/gas_field_vop_inputs.jpg) 493 | 494 | 5. We can also use arbitrary SOP operators to process our DOP fields. We can do so by using *SOP Solver*. We just need to set *Data Name* to our field which we want to process. 495 | ![Sop Solver volume](./img/sop_solver_volume.jpg) 496 | 497 | ## DOPs / Gas Field Wrangle 498 | *Check Houdini project to get the best idea of how it works.* 499 | ~~~ C linenumbers 500 | // we can access fields as in the SOPs, pig_in name 501 | // corresponds to "Data Name" in sopscalarfield_init_pig_in node 502 | f@pig_in *= .9; 503 | 504 | // by default this line will not work, even though it should 505 | // be the same as the previous line 506 | // to make it work, we need to point "Input 1" in the "Inputs" tab 507 | // to the field we want to fetch, in our case "volume/pig_in" 508 | // this way we can access also fields from another DOP objects 509 | // ("volume" is our DOP object in this case) 510 | //f@pig_in = volumesample(0, "pig_in", v@P) *.9; 511 | 512 | // if we want to sample from another position, we also need 513 | // to set up "Input 1" properly 514 | // note that you can also sample from volumes in SOPs if you 515 | // specify path to it: set "Input 1" to *SOP* and point "SOP Path" 516 | // to the volume you want to access 517 | //f@pig_in = volumesample(0, "pig_in", v@P - {0.01}); 518 | ~~~ 519 | 520 | ## DOPs / Gas Field Wrangle - accessing DOPs and SOPs data 521 | ~~~ C linenumbers 522 | // it is also possible to access DOP fields using this syntax 523 | // we can sample "pig_mask" without setting the field "Inputs" 524 | 525 | float pig_mask = 0; 526 | // the following lines bellow are identical, the second and third ones are more flexible as they use relative path: 527 | // it will work even if we renamed this DOP network, the first line would not work after that 528 | // the second argument is either an int representing primitive number, or string representing primitive name 529 | // the syntax for accessing DOP data is: op:/DOP_node_path:dop_object_name/field_name 530 | 531 | pig_mask = volumesample("op:/obj/examples/dopnet_vex_vops_volume:volume/pig_mask", 0, v@P); 532 | //pig_mask = volumesample("op:" + opfullpath("../") + ":volume/pig_mask", "pig_mask", v@P); 533 | //pig_mask = volumesample("op:../" + ":volume/pig_mask", 0, v@P); // op: syntax also accepts relative paths 534 | 535 | // we can also directly access SOP volumes using this syntax 536 | //pig_mask = volumesample("op:../../IN_VOLUMES", 1, v@P); 537 | 538 | f@pig_in *= 1-pig_mask; 539 | ~~~ 540 | ![Gas Field Wrangle - DOPs and SOPs data](./img/gas_field_wrangle_dop_sop_data.jpg) 541 | 542 | ## DOPs / Geometry workflow 543 | Here I will show basic steps of creating a simple custom DOP solver operating on volumes. 544 | 545 | 1. At first we need to create a *DOP Object*, which is a container that will contain all our fields (volumes in DOPs), geometry and any other data. Object name is important, because later we will use it to access our data. 546 | ![DOP Object](./img/dop_object_box.jpg) 547 | 548 | 2. We can import a geometry from SOPs using *SOP Geometry* node. By enabling *Use External SOP* we can select a SOP we want to import. *Data Name* is usually set to *Geometry*. 549 | ![SOP Geometry](./img/sop_geometry.jpg) 550 | 551 | 3. If we want to use functions which take an input as an argument (looking up points, importing point attributes...) in [*Geometry Wrangle*](#dops--geometry-wrangle) or *Geometry VOP*, we need to set up our *Inputs* properly. 552 | ![Geometry Wrangle](./img/geo_wrangle_inputs.jpg) 553 | ![Geometry VOP](./img/geo_vop_inputs.jpg) 554 | Also note, that *Geometry Wrangle* and *Geometry VOP* have an option to use *Myself* in *Inputs* which is equivalent to previous settings. 555 | ![Geometry VOP Myself](./img/geo_vop_inputs_myself.jpg) 556 | 557 | 4. We can as well use *SOP Solver* to process our geometry in a SOP network. 558 | ![SOP Solver geo](./img/sop_solver_geo.jpg) 559 | 560 | ## DOPs / Geometry Wrangle 561 | *Check Houdini project to get the best idea of how it works.* 562 | ~~~ C linenumbers 563 | // we can access attributes from "Geometry" data in our "box" 564 | // object with this syntax 565 | // however if we want to access other point's attributes, we need 566 | // to properly set "Input 1" in "Inputs" tab of this node 567 | v@P *= 1.1; 568 | ~~~ 569 | 570 | ## DOPs / Geometry Wrangle - accessing fields 571 | ~~~ C linenumbers 572 | // we can also access DOP fields from Geometry Wrangle, it is explained in: 573 | // /obj/examples/dopnet_vex_vops_volume/gasfieldwrangle_accessing_DOPs_and_SOPs_data 574 | 575 | // all following lines will produce the same result 576 | float mask = 0; 577 | mask = volumesample("op:../" + ":box/pig_mask", 0, v@P); 578 | //mask = volumesample("op:" + opfullpath("../") + ":box/pig_mask", "pig_mask", v@P); 579 | //mask = volumesample("op:../../IN_VOLUMES", 1, v@P); 580 | 581 | // visualize what points sampled non-zero density in the volume 582 | if (mask != 0) v@Cd = {1,0,0}; 583 | ~~~ 584 | 585 | ## Conditions 586 | ~~~ C linenumbers 587 | // it there is only one statement after if condition, it 588 | // can be written in the same line 589 | if (v@P.y < 0) v@Cd = {1,0,0}; 590 | // or in any other line, since VEX is not indented language, 591 | // but this works only for one operation, else-if block will end with the first semicolon 592 | else if (v@P.x < 0) 593 | v@Cd = {0,1,0}; 594 | // to execute more operations, we need to use a block of code in {} brackets 595 | else { 596 | v@Cd = {0,0,1}; 597 | v@P += v@N; 598 | } 599 | 600 | // it is also possible to use conditional (ternary) operator with the following syntax 601 | // (condition) ? true : false 602 | v@P.x *= v@P.x > 0 ? 0.5 : 1.5; 603 | 604 | // and use of logical AND: &&, OR: || is also possible 605 | if (v@P.y < 0 && v@P.x > 0) v@P -= v@N * .3; 606 | if (v@Cd == {0,0,1} || v@Cd == {1,0,0}) v@P += v@N * .4; 607 | ~~~ 608 | 609 | ## Loops 610 | *Check Houdini project to get the best idea of how it works.* 611 | ~~~ C linenumbers 612 | // VEX uses C-like syntax for for-loops 613 | int valA = 2; 614 | for (int i=0; i<11; i++) { 615 | valA *= 2; 616 | } 617 | i@valA = valA; 618 | 619 | // for convenient iterating over elements of an array we 620 | // can use foreach loop 621 | int nbs[] = nearpoints(0, v@P, .5); 622 | 623 | vector P_avg = {0}; 624 | foreach(int nb_ptnum; nbs) { 625 | P_avg += point(0, "P", nb_ptnum); 626 | } 627 | P_avg /= len(nbs); 628 | 629 | v@P = P_avg; 630 | 631 | // we can also stop the loop at any point by using "break" keyword 632 | int valB = 5; 633 | for (int i=0; i<13; i++) { 634 | valB *= 5; 635 | if (valB > 10000000) break; 636 | } 637 | 638 | i@valB = valB; 639 | 640 | // we can also use "continue" keyword to jump to the next loop iteration 641 | // in this example we average point position with positions of neighbours 642 | // which are above it in world space (their Y coordinate is larger) 643 | int pts[] = neighbours(0, @ptnum); 644 | 645 | vector P_avg_upper = {0}; 646 | int count = 0; 647 | 648 | foreach(int nb_ptnum; pts) { 649 | vector pos = point(0, "P", nb_ptnum); 650 | if (pos.y <= v@P.y) continue; 651 | P_avg_upper += pos; 652 | count++; 653 | } 654 | 655 | P_avg_upper /= count; 656 | v@P = P_avg_upper; 657 | ~~~ 658 | 659 | ## Stopping For-Each SOP from VEX 660 | ~~~ C linenumbers 661 | // in this example we are offseting our points along X axis in each iteration 662 | // 663 | // it is also possible to control For-Each SOP loop from VEX 664 | // "Iterations" count in this loop is set to 300, however we want to stop the loop 665 | // when our X coordinate is higher then 30 666 | // to do so, we can use "Stop Condition" in the loop, when it equals to 1, the loop will end 667 | // in this parameter we can use hscript expression checking for detail attribute 668 | // and we can set this detail attribute from VEX 669 | // hscript expression: detail("../attribwrangle_move_a_bit", "repeat", "0") == 0 670 | 671 | v@P.x += .3; 672 | 673 | // if we comment out the line below, the loop will execute 300 times, otherwise it will execute 674 | // only until our condition is met 675 | if (v@P.x > 30) setdetailattrib(0, "repeat", 0, "set"); 676 | ~~~ 677 | 678 | ## Printing and formatting 679 | ~~~ C linenumbers 680 | // Windows - check console window which 681 | // should pop up automatically 682 | // Linux - run Houdini from command line with 683 | // the -foreground flag to see the output 684 | 685 | // manipulating with strings is useful, not only for doing console outputs, 686 | // but also for generating and stylizing strings, use sprintf() to return a string 687 | // type instead of printing to console 688 | 689 | string var_string = "abcdef"; 690 | float var_float = 1.23456789; 691 | int var_int = 256; 692 | 693 | printf("string: %+10s, float: %10.3f, integer: %-6d \n", var_string, var_float, var_int); 694 | // string = abcdef 695 | // %[+-][length]s 696 | 697 | // %10s -> ____abcdef 698 | // %-10s -> abcdef____ 699 | // %+10s -> __"abcdef" 700 | // %+-10s -> "abcdef"__ 701 | 702 | 703 | // float = 1.23456789 704 | // %[+-][0][length][precision]f 705 | 706 | // %8.3f -> ___1.235 707 | // %-8.3f -> 1.235___ 708 | // %08.3f -> 0001.235 709 | // %+8.3f -> __+1.235 (+ shows sign) 710 | 711 | 712 | // integer = 256 713 | // %[+-][0][length]d 714 | 715 | // %6d -> ___256 716 | // %+6d -> __+256 717 | // %-6d -> 256___ 718 | // %06d -> 000256 719 | 720 | printf("\n"); 721 | 722 | // escaping characters in string 723 | // from my testing it requires 4 more backslashes to escape \n 724 | // when using raw strings, they are automatically escaped, but @ symbol still 725 | // needs to be escaped 726 | // following lines will output the same thing 727 | // 4 backslashes are needed probably because hscript is parsing this text field 728 | // and sending to vop's field, see the node bellow 729 | string a = 'abc \\\\\n \\\\\t v\@P, %04.2f'; 730 | string b = "abc \\\\\n \\\\\t v\@P, %04.2f"; 731 | string c = r"abc \n \t v\@P, %04.2f"; 732 | string d = R"(abc \n \t v\@P, %04.2f)"; 733 | 734 | printf(a + "\n"); 735 | printf(b + "\n"); 736 | printf(c + "\n"); 737 | printf(d + "\n"); 738 | 739 | string multiLine = 740 | R"(It is possible to easily create multi 741 | line strings with this syntax. 742 | In some cases it might 743 | be useful to do it this way, 744 | rather then using \n 745 | However as you have noticed it has weird 746 | 4 characters offset starting on the second line, 747 | not sure if it is a bug or feature)"; 748 | 749 | printf(multiLine); 750 | 751 | printf("\n\n"); 752 | ~~~ 753 | 754 | ## Printing attributes 755 | ~~~ C linenumbers 756 | printf("s\@shop_materialpath (string): %+s, v\@P (vector): %+-10.3f, \@ptnum (integer): %5d \n", s@shop_materialpath, v@P, @ptnum); 757 | 758 | printf("\n\n"); 759 | ~~~ 760 | 761 | ## Including external VEX files 762 | ~~~ C linenumbers 763 | #include "myLib.h" 764 | 765 | // files located in $HIP/vex/include, 766 | // $HOME/houdiniXX.X/vex/include, 767 | // $HH/vex/include 768 | // can be included in wrangles 769 | 770 | // to refresh updated header files, 771 | // promote the "Force Compile" button 772 | // from the attribvop1 node inside of this node, 773 | // or do a change (add a space somewhere) 774 | // in the code and press Ctrl+Enter 775 | 776 | myRemPoints(@ptnum); 777 | ~~~ 778 | *From myLib.h:* 779 | ~~~ C linenumbers 780 | // void functions do not return anything 781 | // "function" keyword is not required 782 | function void myRemPoints(int ptnum) { 783 | if (ptnum > 30) 784 | removepoint(0, ptnum); 785 | } 786 | ~~~ 787 | 788 | ## Include math.h 789 | ~~~ C linenumbers 790 | #include "math.h" 791 | 792 | // This include file contains useful math constant macros 793 | // and is available to every Houdini setup :) 794 | // You can use couple of constants like 795 | // e, pi, sqrt(2)... 796 | // 797 | // check the file at $HH/vex/include/math.h or end of this wrangle 798 | 799 | f@pi = M_PI; 800 | f@e = M_E; 801 | f@log2e = M_LOG2E; 802 | 803 | // XFORM_SRT and XFORM_XYZ are also constants set to values that functions 804 | // maketransform() and cracktransform() expect, they define order of transformations 805 | // and axes 806 | 807 | vector tr = {3,4,5}; 808 | vector rot = {0,M_PI,0}; 809 | vector scale = {2,1,2}; 810 | matrix xform = maketransform(XFORM_SRT, XFORM_XYZ, tr, rot, scale); 811 | 812 | // v@tr_check attribute will match original tr variable 813 | v@tr_check = cracktransform(XFORM_SRT, XFORM_XYZ, 0, {0}, xform); 814 | 815 | v@P *= xform; // apply transformation 816 | 817 | /* 818 | part of the $HH/vex/include/math.h file: 819 | 820 | #define M_E 2.7182818 821 | #define M_LN10 2.3025850 822 | #define M_LN2 0.6931471 823 | #define M_LOG10E 0.4342944 824 | #define M_LOG2E 1.4426950 825 | #define M_PI 3.1415926 826 | #define M_TWO_PI 6.2831852 827 | #define M_PI_2 1.5707963 828 | #define M_PI_4 0.7853981 829 | #define M_SQRT1_2 0.7071067 830 | #define M_SQRT2 1.4142135 831 | #define M_TOLERANCE 0.0001 832 | 833 | #define M_2SQRT6_3 1.6329931618554518 // 2 * sqrt(6) / 3 834 | #define M_SQRT3 1.7320508075688772 // sqrt(3) 835 | #define M_1_SQRT3 0.5773502691896257 // 1 / sqrt(3) 836 | #define M_SQRT_2_3 0.816496580927726 // sqrt(2 / 3) 837 | 838 | #define XFORM_SRT 0 // Scale, Rotate, Translate 839 | #define XFORM_STR 1 // Scale, Translate, Rotate 840 | #define XFORM_RST 2 // Rotate, Scale, Translate 841 | #define XFORM_RTS 3 // Rotate, Translate, Scale 842 | #define XFORM_TSR 4 // Translate, Scale, Rotate 843 | #define XFORM_TRS 5 // Translate, Rotate, Scale 844 | 845 | #define XFORM_XYZ 0 // Rotate order X, Y, Z 846 | #define XFORM_XZY 1 // Rotate order X, Z, Y 847 | #define XFORM_YXZ 2 // Rotate order Y, X, Z 848 | #define XFORM_YZX 3 // Rotate order Y, Z, X 849 | #define XFORM_ZXY 4 // Rotate order Z, X, Y 850 | #define XFORM_ZYX 5 // Rotate order Z, Y, X 851 | */ 852 | ~~~ 853 | 854 | ## Using macros 855 | ~~~ C linenumbers 856 | #include "myLib.h" 857 | 858 | // check attributes in Geometry Spreadsheet, 859 | // they will match values from myLib.h 860 | 861 | // use constants 862 | i@my_int = MY_INT; 863 | f@my_float = MY_FLOAT; 864 | 865 | // use function alias 866 | f@renamed_power = RENAMEDPOWER(2,2); 867 | 868 | // use macro function 869 | i@add_ten = ADDTEN(10); 870 | ~~~ 871 | *From myLib.h:* 872 | ~~~ C linenumbers 873 | // you can use macros to define constants and use them in your code 874 | #define MY_INT 123 875 | #define MY_FLOAT 3.1415926 876 | 877 | // you can also create alias to the function 878 | #define RENAMEDPOWER pow 879 | 880 | // or use macros for defining new functions 881 | #define ADDTEN(val) (val+10) 882 | ~~~ 883 | 884 | ## Functions 885 | ~~~ C linenumbers 886 | #include "myLib.h" 887 | 888 | // void does not return anything 889 | myRemPoints(@ptnum); 890 | 891 | // arguments are passed by reference - function can modify their original value 892 | scaleByTen(v@P); 893 | 894 | // you can prevent voids from modifying variable references 895 | // just to be safe :) 896 | int a = 1; 897 | int b = 1; 898 | int c = 1; 899 | changeA(a, b, c); 900 | i@a = a; 901 | i@b = b; 902 | i@c = c; 903 | 904 | // functions can also return different types - float, string, int, custom struct... 905 | // they can also return an array of any of those types 906 | vector4 seeds = {1.23,4,56.489,0.849}; 907 | f@superRandom = superRandom(seeds); 908 | 909 | // function returning array of int(s) 910 | int items = 9; 911 | i[]@items = range(items); 912 | ~~~ 913 | *From myLib.h:* 914 | ~~~ C linenumbers 915 | // void functions do not return anything 916 | // "function" word is not required 917 | function void myRemPoints(int ptnum) { 918 | if (ptnum > 30) 919 | removepoint(0, ptnum); 920 | } 921 | 922 | // function parameters are passed by reference automatically, without additional syntax 923 | // (function receive the original variable, not its copy) 924 | void scaleByTen(vector P) { 925 | P *= 10; 926 | } 927 | 928 | // you can prevent changing input variable references 929 | void changeA(int a; const int b; int c) { 930 | a += 10; 931 | //b += 10; // uncommenting this line will result in error 932 | c = a; 933 | c += 4; // even though arguments are passed by reference, they are not true references, "c" is still independent from "a" 934 | } 935 | 936 | // a function returning float value 937 | float superRandom(vector4 seeds) { 938 | float out = rand(seeds.x * seeds.y * seeds.z * seeds.w); 939 | return out; 940 | } 941 | 942 | // a function returnig an array 943 | int[] range(int max) { 944 | int out[]; 945 | 946 | for(int i=0; i operator 1127 | int versionA = myProjectA->incVersion(); 1128 | versionA = myProjectA->incVersion(); 1129 | versionA = myProjectA->incVersion(); 1130 | 1131 | int versionB = myProjectB->incVersion(); 1132 | 1133 | // printName is our another struct method, 1134 | // check your terminal output 1135 | myProjectA->printName(); // this file has name: project_A_004.hipnc 1136 | myProjectB->printName(); // this file has name: project_B_002.hip 1137 | 1138 | // we can use functions operating on our structs and accessing their data 1139 | i@match1 = compareHipFiles(myProjectA, myProjectB); // 0 1140 | 1141 | // now let's make them identical 1142 | myProjectB.base = "project_A"; 1143 | myProjectB.ext = "hipnc"; 1144 | myProjectB.version = 4; 1145 | 1146 | // and check if they really are :) 1147 | i@match2 = compareHipFiles(myProjectA, myProjectB); // 1 1148 | 1149 | // we can also create functions which return hipFile type 1150 | // this function expects comma separated list of files and will return 1151 | // first occurance of a hip file (with .hip or .hipnc extension) 1152 | string files1 = "image1.jpg,image2.png,text.pdf,awesome_tutorial_jtomori_003.hipnc,tutorial.h"; 1153 | 1154 | hipFile first = findFirstHipFile(files1); 1155 | s@first = first->getFullName(); 1156 | 1157 | // in VEX we can also output error with custom message, uncomment the line bellow and check 1158 | // node's error message 1159 | string files2 = "image1.jpg,image2.png"; 1160 | //hipFile second = findFirstHipFile(files2); // No houdini project found in this file list: "image1.jpg,image2.png". 1161 | 1162 | // we can also output an array of hipFiles 1163 | // this function will find all Houdini project files and will return array of hipFile(s) 1164 | string files3 = "dust_024.hip,img7.tif,odforce_file_001.hipnc,render1.exr,blood_123.hip,notes.txt"; 1165 | hipFile allHips[] = findAllHipFiles(files3); 1166 | 1167 | // let's check it by adding it into a string array attribute 1168 | s[]@allHips; 1169 | foreach(hipFile i;allHips) { 1170 | push(s[]@allHips, i->getFullName()); 1171 | } 1172 | // result: [ dust_024.hip, odforce_file_001.hipnc, blood_123.hip ] 1173 | ~~~ 1174 | *From myLib.h:* 1175 | ~~~ C linenumbers 1176 | // vex also supoorts structs and methods associated with them 1177 | struct myCustomMatrix { 1178 | // uninitialized variables 1179 | vector x, y, z; 1180 | 1181 | // variables with default values 1182 | vector translate = {0,0,0}; 1183 | string comment = 'default comment'; 1184 | float myPi = 3.14159265; 1185 | float uniformScale = 1.0; 1186 | float myArray[] = {1,2,3}; 1187 | } 1188 | 1189 | // struct for carrying information about our project file 1190 | struct hipFile { 1191 | string base, ext; 1192 | int version = 1; 1193 | 1194 | // you can create methods that operate on structs 1195 | // this method increases version by 1 and returns new version number 1196 | int incVersion() { 1197 | this.version++; 1198 | return this.version; 1199 | } 1200 | 1201 | // inside of a struct function, you can refer to struct fields by name as if they 1202 | // were variables (for example, base is a shortcut for this.base). 1203 | // this method writes to console window / terminal 1204 | void printName() { 1205 | printf("this file has name: %s_%03d.%s\n", base, version, ext); 1206 | } 1207 | 1208 | // returns a string with full file name 1209 | string getFullName() { 1210 | return sprintf("%s_%03d.%s", this.base, this.version, this.ext); 1211 | } 1212 | } 1213 | 1214 | // we can create functions that operate on our structs and use their methods 1215 | int compareHipFiles(hipFile A, B) { 1216 | int match = 0; 1217 | if (A->getFullName() == B->getFullName()) match = 1; 1218 | 1219 | return match; 1220 | } 1221 | 1222 | // func returning hipFile type 1223 | // this function expects comma separated list of filenames and will 1224 | // return the first occurance of a hip file 1225 | hipFile findFirstHipFile(string text) { 1226 | string inFiles[] = split(text, ","); 1227 | string hipParts[]; 1228 | 1229 | foreach(string file; inFiles) { 1230 | string parts[] = split(file, "."); 1231 | if (parts[-1] == "hip" || parts[-1] == "hipnc") { 1232 | hipParts = parts; 1233 | break; 1234 | } 1235 | } 1236 | 1237 | // we can also return error state, warning() function is also available 1238 | if (len(hipParts) == 0) error("No houdini project found in this file list: %+s.", text); 1239 | 1240 | string prefix[] = split(hipParts[0], "_"); 1241 | int ver = atoi( prefix[-1] ); 1242 | string base = join( prefix[:-1], "_"); 1243 | string ext = hipParts[1]; 1244 | 1245 | hipFile out = hipFile(base, ext, ver); 1246 | return out; 1247 | } 1248 | 1249 | // we can as well return an array of structs 1250 | hipFile[] findAllHipFiles(string text) { 1251 | string inFiles[] = split(text, ","); 1252 | hipFile hips[]; 1253 | 1254 | foreach(string file; inFiles) { 1255 | string parts[] = split(file, "."); 1256 | if (parts[-1] == "hip" || parts[-1] == "hipnc") { 1257 | string prefix[] = split(parts[0], "_"); 1258 | int ver = atoi( prefix[-1] ); 1259 | string base = join( prefix[:-1], "_"); 1260 | string ext = parts[1]; 1261 | 1262 | hipFile out = hipFile(base, ext, ver); 1263 | push(hips, out); 1264 | } 1265 | } 1266 | 1267 | // output a warning when no Houdini projects were found 1268 | if (len(hips) == 0) warning("No Houdini projects found."); 1269 | 1270 | return hips; 1271 | } 1272 | ~~~ 1273 | 1274 | ## Structs in Attribute Wrangle 1275 | ~~~ C linenumbers 1276 | // VEX does not allow defining structs inside this field, they need to be defined 1277 | // externally, either in a .h file, or in "Outer Code" string parameter of "snippet1" 1278 | // which is inside of every Wrangle node (this > attribvop1 > snippet1) 1279 | // in this case I unlocked the wrangle and promoted "Outer Code" parameter from 1280 | // the inside of snippet1 to this wrangle 1281 | 1282 | hipFile A = hipFile("awesome_vex_examples_file","hipnc",3); 1283 | s@A = A.base; 1284 | 1285 | // uncommenting of the following lines will result in an error 1286 | /* 1287 | struct hipFileB { 1288 | string base, ext; 1289 | int version = 1; 1290 | } 1291 | */ 1292 | 1293 | // Outer Code behaves just like a .h file included 1294 | i@my_int = MY_INT; 1295 | ~~~ 1296 | *Outer Code* 1297 | ~~~ C linenumbers 1298 | struct hipFile { 1299 | string base, ext; 1300 | int version = 1; 1301 | } 1302 | 1303 | #define MY_INT 123456 1304 | ~~~ 1305 | 1306 | ## Groups 1307 | ~~~ C linenumbers 1308 | // it is also possible to manipulate group membership through VEX, group names 1309 | // are bound by default with i@group_name syntax (int value: 0 - not member, 1 - member) 1310 | // you can disable it in "Bindings" tab of Wrangle node with "Autobind Groups by Name" 1311 | 1312 | // it is also possible to create new groups, they are created automatically like attributes 1313 | // in the following line we assign all points that belong to "selected" group to "red" group 1314 | i@group_red = i@group_selected; 1315 | 1316 | // all points, that have positive X coordinate will belong to "green" group 1317 | i@group_green = v@P.x > 0 ? 1 : 0; 1318 | // except those, which are already in "red" group 1319 | i@group_green = i@group_green && !i@group_red ? 1 : 0; 1320 | // i@group_name can have two values and can be treated as a boolean, the following line 1321 | // has the same effect as the previous one 1322 | //i@group_green = i@group_green == 1 && i@group_red == 0 ? 1 : 0; 1323 | ~~~ 1324 | Group mirror 1325 | ~~~ C linenumbers 1326 | // groups in VEX are very helpful as we can use VEX functions to do our own group logic 1327 | // in this example we mirror red group along X axis and will assign it to "blue" group 1328 | 1329 | int pt_reflected = nearpoint(0, set(-v@P.x, v@P.y, v@P.z) ); 1330 | int pt_group_red = inpointgroup(0, "red", pt_reflected); 1331 | 1332 | i@group_blue = pt_group_red; 1333 | ~~~ 1334 | 1335 | ## Attribute typeinfo 1336 | ~~~ C linenumbers 1337 | /* 1338 | It is possible to assign a meaning to attributes. Houdini will understand this meaning and 1339 | will treat attributes in a specific way based on it. For example a Transform SOP operates 1340 | on P attribute, but will also modify N attribute accordingly. For modifying this behavior 1341 | we can use setattribtypeinfo() function which can set typeinfo to attributes. 1342 | You can check which typeinfo an attribute has by middle-clicking on a node and checking value 1343 | in brackets, e.g. P (pos) - this means point typeinfo 1344 | 1345 | You can chceck list of available typeinfos in the docs, or below: 1346 | none - No transformations should be applied. 1347 | point - Scales, rotations and translations should be applied. 1348 | hpoint - A four-vector with scales, rotations and translations applied. 1349 | vector - Scales and rotations should be applied. 1350 | normal - Scales and rotations should be applied. Scales are applied with inverse-transpose. 1351 | color - No transformations. 1352 | matrix - A 4×4 matrix with scale, rotations, and translations applied. 1353 | quaternion - A four-vector with rotations applied. 1354 | indexpair - No transformations. 1355 | integer - Integer values that do not blend when points are averaged. 1356 | integer-blend - Integer values that blend when points are averaged. 1357 | */ 1358 | 1359 | // set color to green 1360 | v@Cd = {0,1,0}; 1361 | 1362 | // initialize N attribute, which will get automatically get values 1363 | v@N; 1364 | 1365 | // change typeinfos of Cd and N to see funky results after modifying geometry with Transform SOP 1366 | setattribtypeinfo(0, "point", "Cd", "point"); 1367 | setattribtypeinfo(0, "point", "N", "color"); 1368 | ~~~ 1369 | 1370 | ## Attributes to create 1371 | ~~~ C linenumbers 1372 | // when dealing with more code you might often run into 1373 | // errors caused by typos, when you mistype an attribute 1374 | // name, VEX will automatically initialize a new one 1375 | // like in the following example 1376 | v@Cd = {1,0,1}; 1377 | 1378 | // to avoid this kind of errors, we can specify which attributes 1379 | // to create, in "Attributes to Create" parameter of a Wrangle 1380 | // e.g. the following line will result in a node error, because 1381 | // we did not specify to create a "n" attribute 1382 | //v@n = {0,1,0}; 1383 | ~~~ 1384 | 1385 | ## Enforce prototypes 1386 | ~~~ C linenumbers 1387 | // If we want to be even more organized, we can use "Enforce Prototypes" option 1388 | // in Wrangles, it is handy with larger code projects as it helps with 1389 | // managing attributes and simplifies syntax for accesing them (especially with arrays) 1390 | 1391 | // initialize attribute "Prototypes" - here we need to specify all attributes 1392 | // that we want to use/create, it also applies to default/global attributes 1393 | // like v@P, @Frame, @ptnum 1394 | vector @P; 1395 | vector @Cd; 1396 | int @ptnum; 1397 | float @Frame; 1398 | float @new_attrib = 4; // we can also set initial value, but without any expressions 1399 | int @new_int_array_attrib[]; 1400 | 1401 | // we can still use local variables in a standard way 1402 | float A = @Frame * .5; 1403 | int B = 4; 1404 | 1405 | // now we can use attributes without their signature before @ sign 1406 | @P += set(0, B, 0); 1407 | @Cd *= rand(@ptnum); 1408 | @new_attrib *= A; 1409 | @new_int_array_attrib = {1,2,3,4}; 1410 | ~~~ 1411 | 1412 | ## Attribute default values 1413 | ~~~ C linenumbers 1414 | // It is also possible to set default values for attributes from VEX. 1415 | // It replicates behavior of "Default" parameter in Attribute Create SOP, 1416 | // it means that when we create a new geometry (without passing current ptnum 1417 | // to addpoint() function to copy all attributes), it will have those default 1418 | // values. Otherwise it will be usually set to 0, or 1 in case of Cd. 1419 | // The same applies for merging in a geometry, which does not have the attribute, 1420 | // the attribute will be added with default values which we specified. 1421 | // Note that defaults do not work with string attributes, it is a known limitation. 1422 | 1423 | // Default values can be set with Attribute Prototypes which were mentioned in 1424 | // the previous node. Any new geometry will have those default values. 1425 | vector @Cd = {1,0,0}; 1426 | int @id = 4; //@Frame or other expressions will not work 1427 | 1428 | string @default = "abc"; // defaults for strings are not supported yet 1429 | 1430 | // Let's create couple of copies of our points 1431 | vector offset = {0,8,0}; 1432 | for (int i = 0; i < 3; i++) 1433 | addpoint(0, v@P + offset * (i + 1)); 1434 | 1435 | // Original sphere will have this value set to 7, while new points will have 1436 | // default value 0 1437 | f@pscale = 7.0; 1438 | 1439 | // This will set id attribute to zero only on input geometry, new geometry will 1440 | // have default value 4 1441 | i@id = 0; 1442 | ~~~ 1443 | 1444 | # Resources & More 1445 | In this tutorial I am focusing on VEX syntax, capabilities and integration in Houdini. 1446 | 1447 | For more practical and visual VEX examples check [Matt Estela's awesome wiki](http://www.tokeru.com/cgwiki/?title=HoudiniVex) 1448 | 1449 | Another good source is *$HH/vex/include* folder which is full of VEX include files with many useful functions. *( $HH expands to /houdini_install_dir/houdini/ )* 1450 | 1451 | Make sure to watch this very cool [VEX Masterclass](https://vimeo.com/173658697) by Jeff Wagner. 1452 | 1453 | VEX is well documented, [language reference](http://www.sidefx.com/docs/houdini/vex/lang) and [functions](http://www.sidefx.com/docs/houdini/vex/functions/index.html) pages are very helpful too. 1454 | 1455 | # Feedback & Suggestions 1456 | Please let [me](https://jtomori.github.io) know if you find any mistakes or have ideas for improvements. 1457 | --------------------------------------------------------------------------------