Hi guys,
Here’s a small Houdini Digital Asset I made that allows to quickly fracture a large number of .fbx objects and save each resulting part as a separate .fbx file.
This tool was done partially for research and partially for getting into learning Python and a big thanks to both sidefx and odforce community for guidance.

 

 

Introduction:

Researching destructible environments in games recently, I needed a way to quickly fracture a large number of props and get those pieces back in Unity.
The classic approach used in video games is to replace the destroyed prop with a fractured version made up of separated broken parts running a rigid body simulation – couple that with some Pfx for debris and dust and the effect should look great.

Because of it’s data management capabilities Houdini was the software of choice. Now, fracturing an object is pretty straight forward with the default tools, just use the Shatter Shelf Tool and it will setup all the required SOPs for a Voronoi Fracture – but that’s the easy part.

Things get a bit more complicated when you need to import an fbx file, fracture it in a relatively controlled manner and then export each of the resulting fragments in a specific place and in a format that can be used in Unity – and you need to do this for a lot of props. It’s still possible by using just the default tools in Houdini but there would be a lot of repetitive steps to be done for each prop and that just takes a lot of time and it’s error prone.

So this turned out to be a perfect first time Python exercise for me where scripting is used to automate some of the repetitive steps and speed up the process.
The HDA has some hard edges and limitations but works fine for what I needed and if it would be useful for you too feel free to download it and used wherever you want.

fracture_uix

 

Requirements and Implementation

1. Has to work with .fbx files, both for input and for output.

This might sound like an easy kill but was actually one of the most complicate to get right.
In Houdini, if you’re importing any kind of geometry – except .fbx – you would normally do it with a fileSOP. While the fileSOP can be tricked to read .fbx files it won’t read the embedded transforms and depending on how the file was exported initially using a fileSOP might not give the desired results. I did a quick test on a bunch of props exported from different applications and they never came right with the fileSOP.

Then there’s another way to import external geometry in Houdini and that’s by using the File/Import menu and this one has and Filmbox FBX option. Importing .fbx this way produces the desired results and one can quickly test the fileSOP import problem for .fbx by importing the same geometry both ways.

But using the File/Import menu isn’t very procedural, or even Houdini like and luckily there is another way to import .fbx geometry – using the hscript command fbximport. It doesn’t have a Python equivalent yet but you can run hscript commands in Python using hou.hscript.
The fbximport command however is creating a new Geometry Node and places all the .fbx embeded transforms there at the Geometry Level while I needed the .fbx to be imported at the SOP Level so it could be connected with the rest of the SOPs used to produce the fracture effect.

So I ended up combining the hscript fbximport command and the fileSOP:
- The .fbx geometry is imported twice by using the same path from the file parameter: once it’s imported at the Geometry Level using fbximport command and once it’s imported at the SOP Level using the fileSOP.
- I then copy the transforms from the node generated at the geometry level by the fbximport command and paste it to a transformSOP that get’s he’s input from the fileSOP.
- Lastly I delete the node created by the fbximport.

In this way I get all the correct transforms that the .fbx file has embedded and that the fbximport can read onto to fileSOP which resides at the level that I need.

 

2. Needs to have an unified interface to control the fracturing and give the artist useful information about the result.

The main idea was to have all the controls under one panel so you won’t need to dive into the network and select different nodes manually to control the fracture. The only exception I couldn’t fix is if you want to paint the fracture density across the surface then you need to go to the paintSOP. However I placed buttons both to take it you there and to take you back so hopefully it ended up as seamless as possible.

As you change parameters the tool offers you some quick info about the geometry: the number of piecesthe polycount for the original version and the polycount for the fractured version.
Keep in mind that the polycount reported by the 3d application is lower than the polycount that the game engines are seeing where UV seams, smoothing groups and other attributes are increasing the final vertex count. Still is should be a pretty good indication of how much the polycount multiplies after the fracture.

This part was easy to do – however the default options if you have for UI customization are pretty limited but it was enough for what I needed.

 

3. The default controls must offer good results but also offer the possibility for the user to intervene at SOP level for special props.

Since I would have to fracture a large amount of props this tool needs to provide good results in most cases with the default controls.

By default the Voronoi Algorithm produces good results for things made of concrete, stone or similar but not so much for wood – and concrete and wood are the materials most breakable props are made from.
So for wood there’s a quick trick that works most of the times: scaling the geometry down before the fracture and after you scale it back up – and this produces fractured pieces that look more like wood than concrete. I added 3 parameters to quickly scale the prop on the X, Y or Z and this should be enough in most cases.

There’s also included by default a paintSOP that’s easily accessible from the main interface. This allows you to paint the fracture density across the surface, so if the prop has a part where it would shatter in smaller pieces let’s say, you can easily paint that.

And with this being Houdini if one needs more control over how the fracture looks new SOP’s can be always appended in the network to change the geometry.

 

4. As error proof as possible – output path and filename automatically set based on the original prop location and name.

To speed up the process and reduce human error the output path and file name are based on the original prop location and name, for example:

- if the intact version of table long is placed here: $HIP/geo/table_long.fbx
- the fractured fbx files will be placed here: $HIP/geo/_FracturedModels/table_long/table_long_piece_[piece number].fbx
- with the folders _FracturedModels and table_long being generated automatically and with _piece plus the piece number added to the filename.

Also the polygons of each resulting piece are separated in two groups, one for the outside surface and one for the inside – this allows you to use two materials in Unity to define how the prop looks on the outside as well as on the inside.

Network View

Bellow you can see the contents of the main Geometry Node from the Fracture HDA, as I said before it’s pretty standard and it’s build around the Voronoi Fracture node.

I added the possibility to paint the fracture density across the surface, inside and outside materials based on the groups generated by the fracture – this is used by Unity to allow different materials on the same mesh – and a couple of visualisation switches.

network_view

Scripting
Most of the scripting was done in the HDA’s Python Module plus other short expressions on some parameters. With this being my first Python experience I’m sure it could have been done better so if you have any suggestions for improvement don’t hesitate to comment :)

Here’s the Python Module:

#Assigns the HDA node as global variable to be used in other functions
def onCreated(node):
    global HDA
    HDA = node

#Resets most of the HDA values to default - used by LoadGeo()
def Reset():
    HDA.parm('piecesnumber').set(10)
    HDA.parm('chunkcenters_seed').set(0)
    HDA.parm('voronoifracture_cutplaneoffset').set(0)
    HDA.parm('scaleX').set(0)
    HDA.parm('scaleY').set(0)
    HDA.parm('scaleZ').set(0)
    HDA.node('ObjectToDestroy/PaintFractureDensity').parm('clearall').pressButton()

#Loads the .fbx file
def LoadGeo():
    
    #Imports the FBX using the hscrip fbximport command to get the correct transforms
    _objecttodestroy = HDA.parm('objecttodestroy').eval()
    fbxtoimport = 'fbximport -m off "{}"'.format(_objecttodestroy)
    testnode = hou.hscript(fbxtoimport)[1]
    
    #Composes the name of the new node based on the file name - used to assign the new node to the 'importedgeo' variable
    newnodename = _objecttodestroy.split('/').pop().split('.')[0] + '_' +  _objecttodestroy.split('/').pop().split('.')[-1]
    importedgeo = hou.node('/obj/'+newnodename)
    
    #Sets the fileSOP path to the fbximport one
    HDA.node('ObjectToDestroy/file1').parm('file').set(_objecttodestroy)

    #Sets the fbxtransform node to same values as the ones from the fbximport
    fbxtranslate = HDA.node('ObjectToDestroy/fbxtransform').parmTuple('t')
    fbxrotate = HDA.node('ObjectToDestroy/fbxtransform').parmTuple('r')
    fbxscale = HDA.node('ObjectToDestroy/fbxtransform').parmTuple('s')
    for i in importedgeo.children():
        fbxtranslate.set(i.parmTuple('t').eval())
        fbxrotate.set(i.parmTuple('r').eval())
        fbxscale.set(i.parmTuple('s').eval())
    importedgeo.destroy()

    #Colors the node green and changes the name to reflect the .fbx file loaded
    HDA.setColor(hou.Color((0,1,0)))
    HDA.setName(newnodename)

    #Resets some of the parameters to default
    Reset()

#Exports the fracture pieces as separate .fbx files with the location based on the original .fbx
def Export ():

    #FBX ROP - used to render out each fracture piece as an fbx file
    geoOut = HDA.node('GeometryExport/export_fracture_geometry')
    _outputPath = geoOut.parm('sopoutput')
    _renderNode = geoOut.parm('startnode')
    _renderGeo = geoOut.parm('execute')
    
    #Partition Out node - used to count the number of fractured pieces
    sopNode = HDA.node('ObjectToDestroy/OUT_partition')
    sopGeo = sopNode.geometry()
    _sopGroups = sopGeo.primGroups()
    
    #Blast node - used in the ForLoop to separate each fractured piece
    blastNode = HDA.node('ObjectToDestroy/blast')
    _blastPiece = blastNode.parm('group')
    
    #Set the render path for the FBX ROP based on the input file path
    inputSop = HDA.node('ObjectToDestroy/file1')
    inputPath = inputSop.parm('file').eval().split('/')
    file = inputPath.pop().split('.')[:-1]
    inputPath.append('_FracturedModels')
    inputPath.append(''.join(file))
    geoOut.parm('startnode').set(HDA.node('PieceToExport').path())

    #Runs throug each fractured piece and exports it as a separate .fbx file
    for group in _sopGroups:
        _blastPiece.set (group.name())
        finalPath = ''.join(file)
        finalPath = ('/') + finalPath + '_' + group.name() + '.fbx'
        finalPath = ('/').join(inputPath) + finalPath
        _outputPath.set(finalPath)
        _renderGeo.pressButton()
        print finalPath
    
    #Displays a popup message after export
    hou.ui.displayMessage('Fractured "' + ''.join(file) + '" was exported succesfully!')

#Enter Paint Mode - selects the paint SOP while keeping the display flag on the fractured result
def EnterPaintMode():
    import toolutils
    viewer = toolutils.sceneViewer()
    paintNode = HDA.node('ObjectToDestroy/PaintFractureDensity')
    paintNode.setCurrent(True, True)
    viewer.enterCurrentNodeState()

#Selects the main HDA and puts the display/render flag on the final result
def DisplayResult():
    import toolutils
    viewer = toolutils.sceneViewer()
    sopnode = HDA.node('ObjectToDestroy/OUT_display')
    HDA.setCurrent(True, True)
    sopnode.setDisplayFlag(True)
    sopnode.setRenderFlag(True)
    viewer.enterCurrentNodeState()

#Display Paint Node
def DisplayPaintMode(node):
    if node == 1:
        paintNode = HDA.node('ObjectToDestroy/PaintFractureDensity')
        paintNode.setDisplayFlag(True)
        paintNode.setRenderFlag(True)
    else:
        sopnode = HDA.node('ObjectToDestroy/OUT_display')
        sopnode.setDisplayFlag(True)
        sopnode.setRenderFlag(True)

Limitations:

With this being my first Python experience the tool has some limitations:

- There can be only one Fracture Geometry HDA in a scene. Unfortunately using multiple instances of the tool doesn’t work and I have a pretty good suspicion that the culprit is the global variable that holds the HDA node.

- In some of the exported pieces the Inside and Outside groups switch places so it might be more difficult to select all pieces and assign the correct materials. I’m looking into fixing this either in Houdini or in Unity.

- Works only with .fbx files, both for input and output. This was by design since that’s the format used on the games I’m working on, but it’s also the most widespread in the industry. However using other format shouldn’t be hard to implement.

 

Download:

Feel free to download and use the Fracture Geometry HDA if you find it useful:

- Download Fracture Geometry HDA – includes Houdini scene and two .fbx props for example.