Fluid Simulation for Video Games (part 11)

By Dr. Michael J. Gourlay

Download Article and Source Code

Download Fluid Simulation for Video Games (part 11) [PDF 1.24MB]
Download Fluid Simulation Source Code (part 11) [RAR 1.94MB]

Figure 1.Combustion. A fuel-laden fluid emits from the lower ball; ignites, combusts, and convects upward; and heats the upper ball. The camera rotates around the scene.

Combustion: Fuel + Oxygen + Heat -> Fire

Combustion is a chemical reaction in which fuel oxidizes and releases heat. Although that definition might sound simple, the process is complicated. Visual effects artists, however, neither want nor need all of that complexity. For visual effects, video games can simulate combustion as a fluid with a few components: fuel, flame, smoke, and-of course-heat.

Visual effects traditionally model flames and smoke as different layers in a particle effect. This article explains how to model them as a single continuous process. Doing so makes the simulation more emergent and interactive. For example, a user could squirt fuel onto a hot object that would cause the fuel to burn and smoke to rise. This could, in turn, heat a balloon above it, causing it to expand and float away. This article describes how to modify the vortex fluid simulation presented in earlier articles to include a fast and simple but effective model of combustion.

Combustion Theory

To understand combustion, you need to understand how chemicals move, how they interact with each other, and how they use and produce heat.

Whereas part 10 introduced the notion that a fluid can have variable density, this article introduces the notion that a fluid can have multiple constituents, also called chemical species. For example, if a fluid contains methane (CH4) and oxygen (O2), it contains two species.

The following chemical equation models how a hydrocarbon fuel (Cx Hy) can combine with oxygen (O2) to produce exhaust (carbon dioxide,CO2, and water, H2O):

Although the above equation indicates how much fuel makes how much exhaust, it does not tell us how fast the reaction occurs nor how much energy is required to trigger the reaction nor how much energy it releases. We need to know each of these things to model combustion. The sciences of thermochemistry and chemical kinetics endeavor to answer such questions.

Furthermore, the equation above assumes that the reaction has just enough fuel to mix with available oxygen and that the reaction will end perfectly. (Such an equation is called stoichiometric.) In reality, the reaction might not finish, and the resulting products might contain other compounds such as soot. Those “dirty” compounds constitute most of the visible parts of smoke.

The reaction–diffusion equation models how the concentration of a chemical species k can change under the influence of the processes of diffusion and reaction:

where represents the fraction of mass of species k in a fluid parcel with mass m, and Rk represents the rate at which a reaction produces species k.

Previous articles already explained how to model advection and diffusion. This article introduces a model for how chemical reactions can create and destroy species.

The Arrhenius equation models the rate Rk to produce species k from a reaction between species i and j:

Think of A as a frequency factor that relates to how often molecules of species i and j collide with each other (their collision frequency). The activation temperature, Ta, determines the number of molecules that have enough energy (the activation energy) to form the bonds to create species k, as Figure 2 shows. The temperature, T, is measured in an absolute scale (such as Kelvin), where zero is the coldest temperature matter can become. The term comes from a probability distribution. It basically tells you the number of molecules that have a certain energy, given that they collectively have a given temperature.

Figure 2. Activation energy of chemical reactions

Fuel combines with oxygen and releases heat. The amount of heat released is called the enthalpy of formation. Model that using this equation, where Gk is the amount of heat released per unit mass when forming species k:

Simplifying Assumptions

Combustion is an extremely complicated process, and video game hardware lacks the computational resources to simulate all of it in real time. Visual effects authors also do not always wish for perfectly realistic combustion; they also want magical effects, so they want more control than mere reality affords. This article, therefore, presents a toy model that simplifies the process.

In this simplified model, fuel, oxygen, flame, and smoke all advect identically, and within the same spatial region, they have the same temperature. The fluid has an unlimited supply of oxygen, and it is everywhere; the reaction is never limited by a lack of oxygen. In this limited sense, the flame model could be said to be “premixed.” (In contrast, in a diffusion flame, fuel and oxygen meet upon combustion.) Furthermore, this model has all components share fluid and thermodynamic properties such as viscosity, diffusivity, and thermal conductivity. This model behaves as though those properties (such as viscosity) are independent of temperature, pressure, and composition. As before, this model assumes that density differences are small except in buoyancy terms.

None of these assumptions is valid, which makes this model useless for engineering or scientific purposes. This model also omits any notion of lean versus rich combustion and provides no notion of intermediate species. Real fuel can have hundreds of components, real combustion can have hundreds of intermediate reactions, and real exhaust can have hundreds products. This model only includes fuel, flame, and exhaust as components and treats each of them as a single species.

Technically, “flame” is not a chemical compound; in this model, it represents smoke that is hot enough to glow. In reality, smoke is basically the same material as the orange part of the flame-just cooler. The light in the blue part of flame comes from spectral lines of intermediate reactions. (Figure 3 shows a schematic of a flame.) This model does not explicitly model that portion, but the end of the article presents some ideas for possible ways to model the blue core without adding much complexity to the model.

Figure 3.Components of a flame

In short, the model used here deviates far from reality and neglects numerous important effects. It retains only the most rudimentary aspects of a combustion model. Nevertheless, it looks nice and runs fast.

Note: The notion that the orange part of the flame comes strictly from blackbody radiation is popular in textbooks and scientific literature but is not strictly true. The orange part is not hot enough to match the spectrum that comes from it if it radiated as a blackbody. But the important part of this story is that the blue and orange parts glow because of different processes.

In this model, heat only propagates by convection and diffusion. But heat should also propagate by radiation, which depends on temperature to the fourth power. Modeling this process would imply tracing rays that travel at the speed of light-effectively instantaneously. The absorption would depend on the capture cross-section of each fluid parcel, which is immensely expensive to calculate. So, this simplified model omits radiative effects (though the end of the article presents some ideas for how you might fake it).

Combustion has two regimes, called deflagration and detonation. In deflagration, heat propagates slower than the speed of sound by conducting to adjacent material. In detonation, heat propagates faster than the speed of sound via shockwaves compressing adjacent material. Detonation is a more sudden and loud process than deflagration. This model intentionally lacks pressure waves, because including them requires simulation time steps too tiny for real time. Therefore, this simulation does not model detonation.

Putting Theory to Use

Although the details may interest some, visual effects authors can ignore most details of combustion theory; they only need to deal with the parameters summarized in Table 1: Y, which determines how much fuel the fluid contains; A and Ta , which control the rate of converting fuel to flame; and Ck, which controls how much heat the combustion releases. The model also includes parameters that control how quickly to convert flame to smoke.

Table 1.Combustion tuning parameters

ParameterMeaningHow to use it

Fraction of fluid that is fuelIncrease if you want more flames

Reaction rate factor for burning fuelIncrease if you want more intense flames

Activation temperature for burning fuelDecrease if you want flames to occur at a lower temperature

Heat released by burning fuelIncrease if you want fluid to move (buoy) more as a result of burning

Changes to the Simulation Code

To put this theory to use, the fluid simulation code presented in previous articles needs some data and code additions to particles and to the simulation.

Particle Additions

To the Particle class, add a member variable for the mass fraction for each material component: fuel, flame, and smoke. (Oxygen could also be a component, but this article omits it.) Each value must be between 0 and 1 (inclusive), and in this model, their sum must remain below 1.

// Each particle has 3 fractions: fuel, flame and exhaust (smoke).
    float mFuelFraction  ; ///< Fraction of mass which is fuel.
    float mFlameFraction ; ///< Fraction of mass which is flame.
    float mSmokeFraction ; ///< Fraction of mass which is smoke.

Figure 4 shows various components of flame rendered separately: fuel, flame, and smoke.

Figure 4.Components of simulated fluid combustion

Vorton Simulation Additions

To the VortonSim class, add the following members:

  • mCombustionTemperature: Activation temperature (Ta) for reacting fuel into flame
  • mCombustionRateFactor: Pre-exponential factor (A) for combustion rate
  • mSpecificFreeEnergy: Amount of energy, per unit mass of fuel, released during combustion

As mentioned above, the difference between flame and smoke in this simulation is basically a question of temperature: orange flame is glowing hot smoke. But (as explained below) the renderer treats flame and smoke as completely different kinds of material. So the simulation also treats them as separate kinds of matter.

This simulation models smoke as though it emerges from flame via a reaction. So, in this simulation, smoke also has a threshold temperature and a pre-exponential factor:

  • mSmokeTemperature: Temperature below which flame begins to turn into smoke
  • mSmokeRateFactor: Pre-exponential factor for converting flame to smoke

Remember that this combustion model lacks radiative effects. One artifact of this is that the simulated flame does not cool as quickly as it should. This would otherwise lead to fluid remaining in a glowing-hot flame state for too long; but because it models flame-to-smoke as a separate process, visual effects authors can control how long flame persists independently of its radiative properties. This ability compensates for the lack of a radiative cooling model and lets visual effects authors control the look of the flames and smoke with finer detail than reality would allow.

Transferring Mass Fractions from Vortons to Tracers

The whole point of this simulation is to visualize it. Remember from part 8 that the renderer depicted heavy fluid and light fluid differently. The renderer accomplished that by using tracer density to control how to render tracer particles. But the simulation evolves vorton (not tracer) density. To give tracers density that matches the simulation, the VortonSim transfers density from vortons to tracers via a grid. Now that “density” effectively has multiple components (fuel, flame, and smoke), the simulation must now also transfer the mass fractions from vortons to tracers using the same approach. The bolded, blue-colored code below is new:

// Populate density and mass fraction grids.
const size_t numParticles = particles.Size() ;
for( size_t uParticle = 0 ; uParticle < numParticles ; ++ uParticle )
{   // For each particle in the array...
const Particle  &   rParticle   = particles[ uParticle ] ;
const Vec3      &   rPosition   = rParticle.mPosition   ;
const float volumeCorrectedDensity= rParticle.GetMassDeviation()* oneOverCellVolume ;
densityGrid.Accumulate( rPosition , volumeCorrectedDensity ) ;
mFuelFractionGrid.Accumulate ( rPosition , rParticle.mFuelFraction  )  ;
mFlameFractionGrid.Accumulate( rPosition , rParticle.mFlameFraction )  ;
mSmokeFractionGrid.Accumulate( rPosition , rParticle.mSmokeFraction )  ;

Note:It is likely that the process of populating the grid from vortons is bound by memory bandwidth, in which case Accumulate would probably run faster if it copied all fields in a single pass, instead of copying a single field in each of multiple passes. That is because the source values all reside near each other in memory, so loading one likely loads others into the same cache line-whether it is used or not (and it isn't). The code above could probably run two to four times faster by consolidating it into a single procedure. The drawback would be that Accumulate would then be special-purpose and brittle instead of being generic and flexible, as it is now. That's probably a valid trade-off to make in production code.

Combustion Reactions

The main attraction for this article is, of course, the actual combustion of fuel into flame and smoke. The code to do this simply integrates the rate given by the Arrhenius equation:

    for( size_t iPcl = iPclStart ; iPcl < iPclEnd ; ++ iPcl )
    {   // For each particle in this slice...
        Vorton &    rVorton           = (*mVortons)[ iPcl ] ;
        const float vortonTemperature = rVorton.GetTemperature( mAmbientDensity ) ;
        if( rVorton.mFlameFraction > 0.0f )
        {   // This particle is on fire.
            const float arg1 = vortonTemperature / mSmokeTemperature ;
            const float arg2 = arg1 * arg1 ;
            const float temperatureDependence = exp( - arg2 ) ;
            const float smokeRate             =
                         mSmokeRateFactor * temperatureDependence * rVorton.mFlameFraction ;
            const float flameToSmokeChange      = smokeRate * timeStep ;
            // Decrease amount of flame.  (This implicitly increases amount of smoke.)
            rVorton.mFlameFraction -= flameToSmokeChange ;
        if( rVorton.mFuelFraction > 0.0f )
        {   // This particle has some fuel.
            const float temperatureDependence =
                            exp( - mCombustionTemperature / vortonTemperature ) ;
            const float combustionRate  =
                     mCombustionRateFactor * temperatureDependence * rVorton.mFuelFraction ;
            const float fuelToFlameChange = combustionRate * timeStep ;
            // Decrease amount of fuel and increase amount of flame.
            rVorton.mFuelFraction  -= fuelToFlameChange ;
            rVorton.mFlameFraction += fuelToFlameChange ;
            // Change temperature due to heat released by combusting fuel:
            const float temperatureChange =
                            mSpecificFreeEnergy * fuelToFlameChange *
                            rVorton.GetDensity( mAmbientDensity ) * mSpecificHeatCapacity ;
            rVorton.SetTemperature( mAmbientDensity, vortonTemperature + temperatureChange );
        rVorton.mSmokeFraction = 1.0f - ( rVorton.mFuelFraction + rVorton.mFlameFraction ) ;

Those are the only changes to the simulation code: a few variables and a couple dozen lines of code. Now, we want to see flames burn!

Rendering Additions

This article series focuses on simulation dynamics, not on rendering. Fluid rendering is a complicated topic that warrants its own series of articles. Instead of delving into the details of how this particular demo renders flames, I mention only a few aspects that are probably common to many fluid particle rendering techniques you would likely see in any video game.

Note:I will mention in passing, however, that the demo code accompanying this article uses pedestrian, nearly obsolete fixed-function rendering and no programmable shaders. Compared to the state of the art, this rendering is laughable. Yet another exercise for the reader.

Even though the simulation treats fuel, flame, and smoke in a unified way, the renderer uses different materials for each. If the only difference were texture, we could render all of those in a single pass and select texture based on composition. But alas, there is a more fundamental difference between flame (which illuminates itself) and fuel or smoke (which do not), and this difference manifests as a change in render state, which therefore requires multiple draw calls. Also, we will often want to render both flame and smoke at the same location.

Particle Rendering Techniques

Although the demo has one particle system, it is rendered three times-one for each component: fuel, flame, and smoke (which Figure 2 shows). Fuel and smoke are rendered using the same opacity-blended technique as in previous articles. Fuel uses a blue and smoke uses a gray texture. The flames, however, use additive blending so they give the impression of self-illumination. Flames use an orange texture.

All particles are modulated according to their mass fraction. Opacity-blended particles (fuel and smoke) have their opacity modulated according to their mass fraction. Additive-blended particles (flame) have their color modulated according to their mass fraction. Effects authors can control the modulation coefficients to control the brightness and opacity of each layer.

Lighting Trick

The demo sets up an orange point light somewhere within the flames and fluctuates its intensity to give a rough suggestion that light emanates from the flame. In the demo, you can see light flicker on the balls.


Figure 5 shows a variety of burning effects you can achieve by tuning various combustion parameters.

Figure 5. Various kinds of flames achieved by changing parameters

Figure 6 shows a ball emitting fuel-laden fluid that, because its temperature is above the activation temperature, ignites and combusts. The heat it generates causes the fluid to convect upward. It makes contact with another ball and heats it. The camera rotates around the scene.

Figure 6.Combustion with diagnostic information

In the demo, the upper ball is initially neutrally buoyant and free to move. Upon heating, it becomes positively buoyant and floats away.


As with previous articles, the demo spends most of its CPU time on rendering: 47 percent of the total time is spent filling vertex buffers, setting materials, and drawing. In addition, another 16 percent of the time is spent transferring density and mass fraction values from vortons, via a grid, to tracers-a process needed only to render the tracers with different materials.

The large portion of time spent on rendering matters, especially in light of the fact that this demo uses neither modern programmable shaders nor vertex stream frequency division or instancing, which would dramatically speed up particle rendering-perhaps by a factor of 4 to 8.


Transferring density and mass fraction values was previously a serial process that consumed 38 percent of the CPU time. It is an embarrassingly parallel process, so use Intel® Threading Building Blocks (Intel® TBB) to parallelize it.

Write a routine to perform assignment for a subset of its destination values:

static void AssignScalarFromGridSlice( Vector< Particle > & particles
  , size_t memberOffsetInBytes , const UniformGrid< float > & scalarGrid , size_t iPclStart
  , size_t iPclEnd )
    for( size_t iPcl = iPclStart ; iPcl < iPclEnd ; ++ iPcl )
    {   // For each particle in this slice...
        Particle &  rParticle = particles[ iPcl ] ;
        float value ;
        scalarGrid.Interpolate( value , rParticle.mPosition ) ;
        *(float*)(((char*)&rParticle) + memberOffsetInBytes) = value ;

Write a functor to wrap that call and allow Intel® TBB to use it:

class Particles_AssignScalarFromGrid_TBB
        Vector< Particle >  &           mParticles              ;
        size_t                          mMemberOffsetInBytes    ;
        const UniformGrid< float > &    mScalarGrid             ;
        void operator() ( const tbb::blocked_range & r ) const
        {   // Assign scalar from grid to a subset of particles.
            AssignScalarFromGridSlice( mParticles , mMemberOffsetInBytes , mScalarGrid
                                     , r.begin() , r.end() ) ;
        Particles_AssignScalarFromGrid_TBB( Vector< Particle > & particles
                    , size_t memberOffsetInBytes , const UniformGrid< float > & scalarGrid )
            : mParticles( particles )
            , mMemberOffsetInBytes( memberOffsetInBytes )
            , mScalarGrid( scalarGrid )
} ; 

Then, use Intel® TBB’s parallel_for to invoke the functor:

void Particles::AssignScalarFromGrid( Vector< Particle > & particles
                    , size_t memberOffsetInBytes , const UniformGrid< float > & scalarGrid )
    if( scalarGrid.HasZeroExtent() )
    {   // Scalar grid is empty.  Probably first iteration.
        return ; // Nothing to do.
    const size_t    numParticles = particles.Size() ;
        // Estimate grain size based on size of problem and number of processors.
        const size_t grainSize =  MAX2( 1 , numParticles / gNumberOfProcessors ) ;
        // Assign scalar from grid using threading building blocks.
        parallel_for( tbb::blocked_range ( 0 , numParticles , grainSize )
    , Particles_AssignScalarFromGrid_TBB( particles , memberOffsetInBytes , scalarGrid ) ) ;

Table 2 shows how runtimes scale with the number of threads on a four-core CPU. Note that although AssignScalarFromGrid is parallelized with Intel® TBB, PartitionVortons is not, so its runtime should not significantly depend on the number of threads.

Table 2. Run durations for processes on an Intel® 3.4-GHz Core™ i7-2600 processor



*Note:The CPU on which this test ran has only four cores, but they are hyperthreaded.

Potential Improvements

As with all of the articles in this series, the accompanying code constitutes a mere prototype of a full particle system. These articles attempt to focus on the clarity of concepts. Production code is more robust, data-driven, and (alas) more complicated. But that complication is necessary to make the code useful. So, manifesting the ideas in these articles as useful code would require much more effort-an exercise for the reader.

In the demo code that accompanies these articles, the simulation and visualization are too coupled. In a scientific setting, that consistency might be an advantage, but for visual effects, authors want to tune motion and appearance independently. Fortunately, that coupling is merely an artifact of how this demo code is written and not an intrinsic problem with using vortex particles. Using programmable shaders would reduce problem. It’s a straightforward problem to solve and absolutely crucial for making a useful tool, but it goes beyond the scope of these articles.

This model could be enhanced to (very roughly) approximate radiation by introducing a “radiative loss” term that would depend on T4. You could also approximate radiation incorporating another diffusion term that would depend on temperature or simply by changing the thermal diffusivity to depend on temperature: Diffusivity could increase with temperature to the fourth power.

You could also explicitly model the presence and consumption of oxygen, for example, to model a flame extinguishing itself in a closed environment. A future article could describe how to handle enclosed environments. In principle, the inclusion of oxygen would entail adding another mass fraction parameter and consuming oxygen at a rate proportional to the fuel-to-flame reaction rate in addition to making the fuel-to-flame reaction rate depend on the fraction of oxygen.

You could try this cheap trick to visualize a blue core: Tag particles that just turned from fuel to flame for the first time. For that one frame, render those particles as luminous blue. For subsequent frames, render them as a regular (orange) flame. Another idea: Use a color ramp, where particles near the activation temperature are blue and slightly cooler (but still glowing hot) flames are orange. Again, using a programmable shader would make this kind of experimentation easy.

You could compute per-particle shading to simulate self-illumination and self-shadowing. Images in the third article demonstrated that approach for the mushroom cloud rendering. Now that the simulation includes actual combustion, the visual effect of internal glowing could look pretty nice.

Future Articles

The next article in this series will revisit the vorticity-from-velocity algorithm. Liquids take the shape of their containers on all but one surface, so modeling liquids also implies modeling containers. Future articles will include extending boundary conditions to include planes, convex hulls, and interiors, which will allow for creating containers. That will pave the way for a discussion of free surface tracking and surface tension-properties of liquids.

Further Study

Richard Feynman (1983): Fun to Imagine: Fire. BBC TV series.

Jos Stam & Eugene Fiume (1995): Depicting fire and other gaseous phenomena using diffusion processes. Proceedings of the 22nd Annual Conference on Computer Graphics and Interactive Techniques, pp. 129–136.

Sten Odenwald (1995): The astronomy cafe.

Nguyen, et al.: Physically based modeling and animation of fire.

Thierry Poinsot & Denis Veynante (2005): Theoretical and Numerical Combustion. Philadelphia: R.T. Edwards, Inc.

Thierry Poinsot (2011): Introduction to combustion. Video series on YouTube*, University of Toulouse

Related Articles

Fluid Simulation for Video Games (part 1)
Fluid Simulation for Video Games (part 2)
Fluid Simulation for Video Games (part 3)
Fluid Simulation for Video Games (part 4)
Fluid Simulation for Video Games (part 5)
Fluid Simulation for Video Games (part 6)
Fluid Simulation for Video Games (part 7)
Fluid Simulation for Video Games (part 8)
Fluid Simulation for Video Games (part 9)
Fluid Simulation for Video Games (part 10)
Fluid Simulation for Video Games (part 11)
Fluid Simulation for Video Games (part 12)
Fluid Simulation for Video Games (part 13)
Fluid Simulation for Video Games (part 14)
Fluid Simulation for Video Games (part 15)
Fluid Simulation for Video Games (part 16)
Fluid Simulation for Video Games (part 17)
Fluid Simulation for Video Games (part 18)
Fluid Simulation for Video Games (part 19)

About the Author

Dr. Michael J. Gourlay works as a Senior Software Engineer at Electronic Arts. He currently works as the software architect for the Football Sports Business Unit. He previously worked as a senior lead engineer on Madden NFL, on the procedural animation system used by EA, on Mixed Martial Arts (MMA), and as a lead programmer on NASCAR. He architected the visual effects system used in EA games worldwide and patented algorithms for interactive, high-bandwidth online applications . He also developed curricula for and taught at the University of Central Florida (UCF) Florida Interactive Entertainment Academy (FIEA) , an interdisciplinary graduate program that teaches programmers, producers and artists how to make video games and training simulations. Prior to joining EA, he performed scientific research using computational fluid dynamics (CFD) and the world's largest massively parallel supercomputers. His previous research also includes nonlinear dynamics in quantum mechanical systems, and atomic, molecular and optical physics . Michael received his degrees in physics and philosophy from Georgia Tech and the University of Colorado at Boulder.

For more complete information about compiler optimizations, see our Optimization Notice.


Dr. Michael J. Gourlay's picture

jkirugumi, a friend of mine ported this code to iOS and showed me a screenshot of it running on his iPhone.

As I understand it, Intel supports TBB for Mac OS (among other operating systems) so it's possible TBB also runs on iOS -- but I'm not sure.

(I don't personally have experience with iOS, so I'm reporting only what I've been told.)

If anybody ports this to iOS, I'm eager to hear about any changes you needed to make, or other feedback.


jkirugumi's picture

can this run on ios?

Dr. Michael J. Gourlay's picture


Article 12 is up: http://software.intel.com/en-us/articles/fluid-simulation-for-video-games-part-12/

It explains and repairs jerkiness.

Dr. Michael J. Gourlay's picture

Note that the first section head should read "Combustion: Fuel + Oxygen + Heat --› Fire" with an arrow between Heat and Fire.

jm77's picture

Hi Michael,

Wow, thanks for the detailed response. I see you are right, I had thought that vNeighborToSelf was normalized, hence the gap in my understanding. I've been doing quite a bit of reading on kernels, smoothing, cutoffs and all these terms. I actually like your formulation a lot, due it's computational and conceptual simplicity. The paper by Robert Speck on multipole methods is also very cool but seems very challenging.

Looking forward to the article on fixing the grid jerkiness. I did implement a version of the grid code, but I eventually switched to summation over a finite area. However as my understanding is the kernel in question does not (or maybe does?) have compact support, but extends out to infinity, I'm now searching for a way of convolving this kernel or modifying it in some way such that it reaches zero at a finite radius.

Also the debug visualizations are very helpful. :)

Thanks again for your time!

Dr. Michael J. Gourlay's picture

Wow, and I spelled "insightful" wrong. Next time I'll resist the temptation to compose replies at 2am. I seem to be a mistake generator. :)

Dr. Michael J. Gourlay's picture


One final note: In the comments above I carelessly wrote "cutoff" instead of "kernel". They're related to each other but not the same. In your question you correctly used the term "kernel".

Too bad I can't edit comments :)

Dr. Michael J. Gourlay's picture


In case I didn't explain it elsewhere, you can move the camera:

* left-click-drag orbits the camera eye around the camera look-at point.
* middle-click-drag translates the camera eye and look-at points together.
* right-click-drag moves the camera eye toward or away from the look-at point.

Have fun!

Dr. Michael J. Gourlay's picture


Here's an "easter egg": The demo code that accompanies article 11 (this one) has a bunch of diagnostic features. You can press various keys to cycle through or toggle different renderings:

* v - toggles vorton rendering. Vortons are rendered as translucent disks with a sharp outline and a dot in the center, plus a faint square on the outer edge of the texture. You can barely see them with all the tracers in place though, so you'll want to turn off tracers.
* t - cycles through different tracer rendering. You can view fuel, flame, smoke, all or none. To diagnose issues, "none" is the best because it lets you see other diagnistics.
* g - cycles through grid rendering. One of the options is to render all grid cells. But grid cells are also made more translucent and transparent, the farther away they are from the camera look-at point. So if you move the camera, grid cells will appear and disappear.
* d - cycles through diagnostic text and line rendering. One mode lets you see information about every grid cell and every vorton. The amount of information is so huge that it's incomprehensible and illegible unless you zoom in very close. Also, the text is (like the grid cells) transparent everywhere except near the camera look-at point.
* down-arrow - pauses time.
* right-arrow - when paused, advances time forward by one tick.
* up-arrow - resumes time.

If you enable vortons, grid cells and diagnostic text, and disable tracers, pause time and step through the simulation tick by tick, you can see vortons that occasionally pass over a grid point (the corner of a grid cell). When this happens, the diagnostic text for a vorton turns red.

That happens somewhat rarely and briefly, which is why you have to single-step through the simulation to catch it.

If you pay very close attention, you'll find that whenever you see jerking, it corresponds with a vorton getting near a gridpoint. This is the topic of the next article (12).

Meanwhile, I encourage you to try the different cutoff functions discussed here:
* The original that the demo code uses (constant)
* Linear (using code I provided in the comment above)
* Quadratic (using the formula you propose).

You'll find that the choice does not make a huge qualitative difference in the overall fluid behavior. And that's a good thing because otherwise that would imply that an arbitrary numerical choice leads to significantly different simulated results -- which it should not.

Put another way, if the time step was very tiny and the computer had infinite precision, then the choice of cutoff should not matter (as long as it obeys the constraints I mentioned above).

Dr. Michael J. Gourlay's picture


So, why do I claim that dist/r^4 yields a quadratic dependenct on dist, when clearly "dist/r^4" is linear in "dist"?

Because that gets multiplied by vNeighborToSelf whose magnitude is dist. We could write vNeighborToSelf as "dist * vNeighborToSelfDir" where the "...Dir" form is the unit vector.

(And indeed, the code has a variable called vNeighborToSelfDir initialized to vNeighborToSelf * oneOverDist but was never used. So your question prompted me to discover this unused variable and remove it. Curious that the compiler didn't complain about it since I have warnings cranked up.)

Still, as I mentioned in the comments above, the formula in the code does not match what article 3 describes so no matter how you slice it, I published a mistake, and you pointed it out.

I have changed the code to match what article 3 describes.

I'll follow up with one more comment that explains what the impact of all this is. Short version: not much, but it's fun and interesting to investigate. And that exercise also leads to an understanding of why this demo code looks so jerky.


Add a Comment

Have a technical question? Visit our forums. Have site or software product issues? Contact support.