Procedural bridge with Houdini and Unreal Engine 4 (UE4)

September 12, 2020 -

Creating a game ready bridge inside Unreal Engine 4 (UE4) with Houdini

"Ey, could you make it a bit longer and wider, oh and bend it more, with more gravity...".


Some months ago I got stunned by watching a mate creating a bridge for a game. It was “Indiana Jones and Temple of Doom” like, with plenty of ropes, tilted planks and similar details.

What shocked me most was the amount of time he was spending by manually adjusting every wooden plank and knot. After a minute I resumed walking and worried about him, imagining his supervisor asking for a change:

“Ey, could you make it a bit longer and wider, oh and bend it more, with more gravity…”.

I couldn´t believe how much time he was about to lose so I did a mental note to develop some kind of procedural bridge generation with Houdini.

Other procedural bridges and my proposal: Procedural generation through HDA

The tool provides level design flexibility and outputs final art at once

First thing I did was some Google research, to find what is currently out there about procedural generation of a bridge. Most results where related to procedural modelling which outputs a ton of geometry, suitable for animation and cinema, but not for gamedev purposes. Game ready solutions I found where pretty limited, based on blueprints, which I found useful for blocking purposes only. 

So I decided to create my own version for Unreal through the Houdini engine plugin, with these design guidelines in mind:

  • Game ready: optimized, instancing-based.
  • Able to fit level design requirements.
  • Able to output final art.

If you are a Houdini nerd like me, you might be interested in technical breakdown. Just keep reading 😉

 

The result: Finished bridges in seconds

Procedural bridge - HDA technical Breakdown

I used to build my tools on top of complex node networks and calculations but I realized this leads to birthing a giant with clay feet.


The goal is designing a tool that allows the user to create finished game ready bridge with minimum effort and maximum flexibility.

 

Design guidelines:

  • Able to output game ready and optimized final art 
  • The assets will be fed dinamically from editor’s content browser. That means, the tool must be unaware of the modules it is gonna be receiving as input.

(For this demo, I used Megascan assets)

There will be two types of inputs:

  • Steps:
    I used wooden boards mainly, but you can feed whatever uasset existing within your project like rocks, metallic panels or the asset of your choice
  • Pillars:
    For both inputs, the user will be able to feed in as many variations as required. That means you are able to feed the tool with one, two, twenty different uassets and they will be scattered randomly.

To decide what to use as input for a tool I try to think of whatever I am creating in the most abstract way possible. An in-editor curve seemed to me a suitable starting point for the procedural bridge. I found a good practice not to trust marshalling data between softwares, so I tend to redo from scratch as much as I can (the curve in this case), whilst keeping this principle in mind: “the simplest option is the more robust one”. I used to build my tools on top of complex node networks and calculations but I realized this leads to birthing a giant with clay feet. It might look silly, but I found comfortable removing everything from Unreal´s curve but the tip points and regenerating the curve: I love doing accurate operations with VEX, specially in cases like this, where a short code outputs exactly what I aim looking for.    

int neighs=neighbourcount(0,@ptnum);
if(neighs>1) removepoint(0,@ptnum);

Simple, robust and  neat. Then I resample at will, to have enough resolution to bend the line and create the belly of the bridge. There might be a zillion ways of creating gravity effect procedurally along a curve and I am pretty sure I change my approach every time.

float perim=primintrinsic(0,"measuredperimeter",0);

float power=chf("Power");
float slide=fit01(chf("Slide"),-perim,perim);
float k=fit01(f@curveu,-1,1)+slide;
float push=chf("Push");

@P.y+=pow(k,power)*push;

  This offsets the curve vertically, so I need to reset its position. I use prim centroid and first point position to achieve that.

vector cen=point(1,"P",0);
v@P-=point(0,"P",0);
v@P+=cen;

If you wondered about the pointwrangle before the resample node, it stores curve main axis and its orthogonal vector, used in further calculations. It comes in handy to be able to query where this curve is pointing any time it is needed.  

vector dir=normalize(point(0,"P",1)-point(0,"P",0));
vector cross=normalize(cross(dir,{0,1,0}));

v@dir=dir;
v@cross=cross;

 

vector dir=normalize(point(0,"P",1)-point(0,"P",0));
vector cross=normalize(cross(dir,{0,1,0}));

v@dir=dir;
v@cross=cross;

Orienting unreal actors through @orient point attribute is key for succesfully build a level through HDAs. It is hard to keep under control and do what you actually need it to do; specially when it comes to instance uassets along a curve. If you add to the mix unconsistent uasset orientation (some assets facing +X, others facing +Y, some sitting on the ground, others centered at origin, etc) then you are in trouble. The project manager might not be happy if you tell him to tweak 500 static meshes to unify orientation. With this in mind, I aimed for the most ambitious approach so that I could be able to properly place the bridge modules no matter their orientation (pointing X or Y). Rotating and orienting stuff is hard to master, because you need to add quaternions and matrices to the usual vector stuff. It takes time to understand what they are, the way they morph into each other and the reasons of using them. I felt terribly lost when I first met them but I kept pushing so I am reasonably comfortable dealing with them at this point. If you feel like I described before, you might benefit from Matt Estela´s Wiki, it covers rotation and orienting  among other amazing stuff, it is one of my pinned tabs within browser. First thing I do is calculating @up attribute based on the point Normal (previously calculated with the “polyframe” node).

v@up=normalize(cross(cross({0,1,0},v@N),v@N));

Then I assemble the @orient attribute:

vector up_rest=v@up;
vector N_rest=v@N;

//-----------ROLL----------------------------------------------------------------------------------
///Pre-rotate mesh by rotating up --> Equals to ROLL effect
float roll=chf("Roll");
float pre_rotate_up=fit01(roll,-$PI,$PI);
matrix3 m1=ident();
vector axis1=normalize(v@N);
rotate(m1,pre_rotate_up,axis1);
v@up*=m1;

//This is for adding ramp control
matrix3 m4=ident();
float turnsRoll=radians(fit01(chramp("Roll_increase",f@curveu),-90,90));
vector axis4=normalize(v@N);
rotate(m4,turnsRoll,axis4);
v@up*=m4;

//-----------YAW----------------------------------------------------------------------------------
///Pre-rotate mesh by rotating N --> Equals to YAW effect
float yaw=chf("Yaw");
float pre_rotate_N=fit01(yaw,-$PI,$PI);
matrix3 m2=ident();
vector axis2=normalize(v@up);
rotate(m2,pre_rotate_N,axis2);
v@N*=m2;

//This is for adding ramp control
float turnsYaw=radians(fit01(chramp("Yaw_increase",f@curveu),-90,90));
matrix3 m3=ident();
vector axis3=normalize(v@up);
rotate(m3,turnsYaw,axis3);
v@N*=m3;

//Compose final orient quaternion
matrix3 m5=maketransform(v@N,v@up);
@orient=quaternion(m5);

After writing all this code, I realized SideFx ships Houdini with a node called “Orient along curve” now, which does exactly this with more options and probably better performance. But..ehmm….you know, I can say I created my own version now. There are different methods to assemble a quaternion. In this case I aimed for building it via @N and @up vectors, which I usually set as visualizers to help me predict better what will the orientation be, because you cannot “see” where a quaternion points. Therefore, you either instance something within the points or visualize the vectors you build the quaternion with in order to have some kind of preview. I personally like rotating the vectors.
The tool randomly instances different uassets (wooden planks in this example) based on dynamic user´s input. That means the HDA doesn´t know any project route (asset reference) nor the amount of assets to choose from in advance. It could be one, two or fifty . However, I do need to know what asset to instance…How could I randomly pick from a changing handful of inputs? Wondered if any of the special attributes automatically created when marshalling data between Unreal and Houdini  could come in handy. After reading some posts, I found out that I could iterate over the inputs through a “foreach piece” loop in combination of the “unreal_input_mesh_name” attribute. Once I found this I wondered…“what if I just create a single point for each HDA input, which already contains the input´s name?” I realized that these two attributes unreal_input_mesh_name (prim attribute coming from Unreal´s end) and unreal_instance (point attribute exported from Houdini´s end) are basically the same info but wrapped differently: – unreal_input_mesh_name comes in the form of prim attribute: Static ‘uassetName` – unreal_instance needs to be a point attribute: StaticMesh `uassetName` So this was the key! I just needed to extract that info from the incoming geo and carve it on the points! For each asset, I get its size and unreal_input_mesh_name so that it is stored in a single point. If the HDA has one input (one wooden board), this will create a point. If it has two inputs (two wooden boards), this will create two points, and so on. Later, I locate unique names and assemble an array with them:

string list[]=uniquevals(0,"point","unreal_instance");
vector sizes[]={};

vector tempSize;
for(int i=0;i<npoints(0);i++)
{
tempSize=point(0,"size",i);
append(sizes,tempSize);
}

setdetailattrib(0,"list",list,"set");

Each attribute has a proper place to be written along the node stream. If you code it too early, you might lose it. I think safest option is doing the rough stuff first (like creating a VDB volume out of a point cloud and later morph it into actual geo) and fragile stuff later (assigning attributes). Quite often I get unexpected results when coding VEX due to I am querying an attribute that no longer exists. For instancing-based HDAs running inside UE4, I found these to be the bare minimum: @scale, @pscale, @orient (or @N), @unreal_instance Resuming our project, next topic is replacing the curve points with those including the proper attribute data. A funny yet handy trick is using the “copy to points” node to instance the “smart points” (holding the attributes) on top of the “silly ones”, which are just positions in 3D space. With this approach I replaced the curve points with the ones reporting which Unreal uasset needs to be instanced. (Instancing a 1x1x1m box I am able to visually check the effect of @pscale, @scale , @orient). This is easily done  with a “for each point” loop. But wait, using loops is not optimal (performance wise), specially when dealing with points, because loops lead to linear processing (do one iteration, then next, then next…) which is unefficient if compared with “point wranglers”. As far as I know it is related to multithread CPU processing. So why did I use the “for each point” loop? Well, they offer the “iteration” attribute among others, which provides a quick way to easily output a different result for every iteration (a different point pick in this example). In my case, the easiest was to grab the computed “smart points” commented in previous section and remove all of them except one.

int it=detail(1,"iteration");
float seed=chf("seed");

int pick=int(rint(fit01(rand(seed+it),0,@numpt-1)));

if(@ptnum!=pick) removepoint(0,@ptnum);

This covers most complex parts of the project. I hope you enjoyed it. If you have any suggestion, please leave a comment!

Output geometry

Creating actual geo is a double-edged sword. It eases things a lot and it provides a lot of flexibility, because you can use the Houdini vodoo to process data and output final polys through the method of user´s choice. This geo will be baked down to a unique static mesh eventually, which is not ideal: that might lead to creating a lot of custom shapes. Despite of being pretty common practice within some game developers, it needs to be balanced carefully. My goal is aiming for most optimized solutions initially and leave options that penalize performance as last resource.

Creating ropes and pipes

This leads to an issue that keeps bugging me: creating curves inside Unreal Engine through HDA and use them to feed a cable component. This would be optimal due to would combine Houdini power to create curves whilst would allow using in-engine-optimized native solution (which essentially instances and deforms a bunch of pipe/rope modules).

As far as I know this is not possible at this point so I keep procedurally generating actual geo for ropes and pipes.

Please, leave a comment if you already know how to deal with that!

Disclaimer: This content is product of my personal work, free to use in personal, educational and commercial projects. If you find this content useful, support me by crediting www.reveron3d.com , this is not mandatory, though.

Follow on Twitter:
@reveronadolfo

I try to highlight challenges so don´t take these series as step by step tutorials, they were created for supporting movie makers, game developers and the Houdini community in my spare time. It is product of my own expertise so it might contain mistakes. Browse www.sidefx.com for official reference.

Leave a Reply

Your email address will not be published. Required fields are marked *


Play Cover Track Title
Track Authors