I’ve been waiting about a year to use that blog post title. Don’t judge me…
I bought Houdini Indie about a year ago, and up until a few months ago I hadn’t used it.
In the last few months, I’ve started learning fracturing and pyro effects (smoke, fire, etc).
In this video, I’m fracturing an object and generating “smoke” (dust is the intention, but I haven’t added particles to it, so it definitely looks like smoke).
Brief background on Houdini fluid sims
Very brief, because I’m still learning 😛
In Houdini, you create volumetric fields of data that drive fluid simulations, much like in other software like FumeFX.
For a smoke sim, you can get away with just Density and Heat. The Density controls how much smoke gets added per frame (although like everything in Houdini, this is a loose definition). The Heat will move the smoke around using gas pressure simulations.
I’m also using a Velocity field, because it’s one way of getting the pieces of my fractured geometry to disturb the smoke as they move through the fluid.
Each piece of the fractured geometry is glued to pieces next to it using “glue constraints”. These break either when I manually break them, or when a certain amount of force is applied to them.
The goal of this scene
There are plenty of ways of setting up the Smoke Density, and the most common one I’ve seen is just adding Density to the fluid in places where geometry is moving at a certain speed.
Instead of that, I wanted to add dust when a constraint breaks (based off the mass of the pieces), and only add it to the sim if the piece is moving above a certain speed.
The end results are not a great deal different, but there’s a few things I like:
- Small pieces can shed all their dust before they hit the ground. You don’t end up with streamers of dust all the way to the ground just because something is moving fast.
- There’s good variation in the amount of smoke/dust that pieces generate, due to the mass being factored in.
- A group of pieces can fall off as a chunk, generating some smoke for a few frames. When that chunk hits the ground and breaks again, the broken constraints can generate more smoke. This could look really nice if I had a more complicated scene setup 🙂
The setup
This is what my scene looks like:
There is a tube that I fracture, a ground plane, two simulations (fracturing and the smoke fluid sim) and “SmokeSource” which is where I generate the fields for the fluid simulation.
Tube object (fracture setup)
I won’t go too much into the Fracture setup, because it’s pretty standard, but here’s what that network looks like:
So the top bit does a voronoi fracture on the geometry, the middle bit adds a “depth” value attribute, which is how far each point is from the original surface of the object (I intended to use this for something, but then… didn’t).
The left side sets up which pieces of geo are active, using a box to select the ones I want (everything except the base of the cylinder, basically). The right side creates the glue constraints.
There’s a few File nodes to cache things out to disk.
Most of this is set up through standard shelf tools.
Collapse Sim
Again, pretty basic stuff, most of this is created when you use shelf tools to setup a sim.
The only interesting bits in this are the “Geometry Wrangle” node at the top, and a few things I added to the “Remove Broken” solver.
Geometrywrangle_dust
This is where I’m doing most of the dust setup work (although probably shouldn’t, more on that later…).
Here’s the VEX code:
vector c = point("op:/obj/tube_object1/OUT_ACTIVEPOINTS", "Cd", @ptnum); i@active = (int)c.r; float DustPerKilo = 0.2; float DustLiberatedPerMetrePerSecond = 350.0; float MinimumSpeedForDustLiberation = 0.4; float MaximumSpeedForDustLiberation = 6.0; float LiberatedDustDissipationRate = 60.0; string GlueConstraintPath = "op:/obj/CollapseSim:Relationships/glue_tube_object1/constraintnetwork/Geometry"; string GeoPath = "op:/obj/CollapseSim:tube_object1/Geometry"; int NumPieceAttributes = 2; for (int PieceCount = 1; PieceCount <= NumPieceAttributes; PieceCount++) { /* * This is horrible, and would break down for constraints that had more than 2 pieces... * Attributes are "Piece1, Piece2" the first time through. "Piece2, Piece1" the next */ string AttributeToFind = "Piece" + itoa(PieceCount); string AttachedPieceAttribute = "Piece" + itoa((PieceCount%NumPieceAttributes) + 1); // First, get the number of glue constraints that have this piece as "piece 1" int NumberOfGlues = findattribvalcount(GlueConstraintPath, "prim", AttributeToFind, @ptnum); int ConnectedPieces[] = {}; for (int Count = 0; Count < NumberOfGlues; Count++) { int Success; int CurrentGlueConstraintIndex = findattribval(GlueConstraintPath, "prim", AttributeToFind, @ptnum, Count); int PieceVertIndex = primattrib(GlueConstraintPath, AttachedPieceAttribute, CurrentGlueConstraintIndex, Success); string PieceName = pointattrib(GlueConstraintPath, "name", PieceVertIndex, Success); string Bits[] = split(PieceName, "/"); ConnectedPieces[len(ConnectedPieces)] = atoi(re_find("([0-9]+)", Bits[1])); } } int RemovedPieces[] = {}; float RemovedPieceMass = 0.0; // Check to see if any constraints have been removed foreach(int PreviousPieceIndex; i[]@aConnectedPieces) { int found = 0; // Search current array against last, etc foreach(int CurrentPieceIndex; ConnectedPieces) { if (CurrentPieceIndex == PreviousPieceIndex) { found = 1; break; } } // For every broken constraint, add some dust if (found == 0) { RemovedPieces[len(RemovedPieces)] = PreviousPieceIndex; int Success; RemovedPieceMass = RemovedPieceMass + pointattrib(GeoPath, "mass", PreviousPieceIndex, Success); } } /* * Increase the dust amount if we have removed some pieces and use the mass * of those pieces to scale how much dust is generated */ if (RemovedPieceMass > 0.0) { @DustAmount = @DustAmount + (DustPerKilo * RemovedPieceMass); } f@VelocityMag = length(@v); // Disperse the freed up dust a little each frame if (@DustLiberated > 0.0) @DustLiberated = max(@DustLiberated - LiberatedDustDissipationRate, 0.0); /* * Based off the speed of this piece, transfer some * of the Dust to "liberated". * This allows the dust to be used up over a number * of frames, faster for fast moving pieces */ float VelocityMultiplier = (f@VelocityMag - MinimumSpeedForDustLiberation) / (MaximumSpeedForDustLiberation - MinimumSpeedForDustLiberation); VelocityMultiplier = clamp(VelocityMultiplier, 0.0, 1.0); float DustAmountToLiberate = VelocityMultiplier * DustLiberatedPerMetrePerSecond; DustAmountToLiberate = min(DustAmountToLiberate, @DustAmount); @DustLiberated = @DustLiberated + DustAmountToLiberate; @DustAmount = @DustAmount - DustAmountToLiberate; addvariablename(geoself(), "DustLiberated", "DUSTLIBERATED"); // Store the connected pieces as an attribute (used when comparing between frames) i[]@aConnectedPieces = ConnectedPieces;
The first loop is pretty ugly to look at. It used to be two separate loops with a bunch of copy-pasted code, not sure it’s any better now that I “cleaned” it up.
Anyway, this code searches through all the Constraints in the scene, and finds any constraint that is connected to the current piece
For each Constraint it finds, it keeps track of the piece of geometry that this one is connected to, and puts it into a “connected pieces” array.
The “connected pieces” array is stored on the geometry as an attribute. Each frame the sim runs, you have access to the previous attribute values in this Geometry Wrangle.
If a piece was connected last frame, but not this frame I use the mass of the no longer connected piece to add a “DustAmount” to our current piece.
Each frame I transfer a bit of the DustAmount (if there is any) to “DustAmountLiberated” based on the velocity of the piece. This “DustAmountLiberated” is what I’m using to create the smoke density.
Phew! So not exactly neat code, sorry about that, but hopefully that makes sense.
Remove Broken solver
Nothing very exciting here, but each frame I have a sphere that expands that deletes constraint primitives.
It leaves the points alone, because I still need to look up the points for constraints that have been broken, so keeping the points makes life easier 🙂
After a bunch of frames, it looks like a packman cylinder. I think that warrants a screenshot:
DeletePrimitivesBasedOnPoints
This is another attribute wrangle, which checks to see if the constraint is marked for delete, or if either point in the constraint is marked for delete (by the big sphere of death).
If any of that is true, the whole primitive if marker with the “ToDelete” attribute.
int Point1 = primpoints(0, @primnum)[0]; int Point2 = primpoints(0, @primnum)[1]; i@Point1Delete = point(0, "StuffToDelete", Point1); i@Point2Delete = point(0, "StuffToDelete", Point2); i@ToDelete = (i@ToDelete || i@Point1Delete || i@Point2Delete);
Smoke Source
This network imports the results of the Collapse sim, so that it can generate the fields that I need to pass to the Smoke Simulation.
I mentioned that I’m using heat, density and velocity, but I’m actually just using the density as heat. That makes no sense, but I didn’t bother coming up with a better plan 🙂
Anyway, the Density is generated just from chunks of geo with “liberated dust” amounts.
The Velocity is generated from all geometry pieces:
The Velocity field is kinda cute.
Network wise, there’s not a lot fancy here. A little bit of hackery to avoid errors on the first frame, because the DustLiberated attribute doesn’t exist at that time (hence the switch node, which just uses a condition of “if we are on the first frame do X”, where X is ignore all the geo).
Probably worth noting that for the density, I’m using points scattered on the surface of the geometry, but I’m deleting the exterior faces, because they are never connected to anything 🙂
Smoke Sim
Nothing very exciting here either, pretty much a standard pyro shelf setup with a wind node thrown in.
I also added a switch node so I could quickly change between a few fluid grid setups for quick previews.
Well that was fun!
So that’s it! Sorry it was a bit of a wall of text.
This was a fun exercise, although it took me a long time to sort this all out, it really helped me learn more about pyro sims, and Houdini in general.
Aside from making an actual scene to destroy, creating particles for the dust, and tweaking the fluid sim settings to make it better, there’s a few things I thought of half way through this that I’d like to try:
- Use surface area instead of mass to drive the amount of dust.
- Combined with the above, instead of generating the dust all over the piece of geometry, I could convert the two pieces of geo to volumes, intersect those volumes and generate the dust only on the intersecting places.
- The turbulence looks horrible. Yuck. It looks like the smoke is wriggling about in jelly (or jello for those living in America)
- I think I could probably move the dust calculations into SmokeSource. Currently, if I want to tweak dust amounts, I need to re-sim just about anything, which is annoying.
- Non linear reduction of dust amount might be nice, sometimes the dust cutoff is a bit sudden
So… Are you still doing Unreal and Half Life inspired stuff, or did you just get bored and wander off?…
Yeah. Well.
So I was intending to use Houdini to do a bunch of stuff for that, but we’ll see 🙂
The first thing I started trying out (when I had no idea what I was doing) was smashing up my chamber: