Fire Foam Trajectory

Quick Start

When we fire a jet of foam with an angle and velocity, what trajectory will it follow, and what are the effects of wind and updraught from the fire.

In this first version we fire foam balls one by one, just to get an intuition of the scale of the effects


The app is part of the work of the LASTFIRE team working towards improved on-site knowledge of foam properties relevant to fighting fires.

Fire Foam Trajectory

Jet Velocity m/s
Jet Angle θ°
Diameter cm
Wind Velocity m/s
//One universal basic required here to get things going once loaded
window.onload = function () {
    //restoreDefaultValues(); //Un-comment this if you want to start with defaults
//Any global variables go here

//Main is hard wired as THE place to start calculating when input changes
//It does no calculations itself, it merely sets them up, sends off variables, gets results and, if necessary, plots them.
function Main() {

    //Send all the inputs as a structured object
    //If you need to convert to, say, SI units, do it here!
    const inputs = {
        phi: 1 - 1 / sliders.SlideExpansion.value, //From Expansion to φ
        V: sliders.SlideV.value,
        theta: sliders.Slidetheta.value * Math.PI / 180, //to radians
        R: sliders.SlideD.value / 200, //cm diameter to m radius
        WV: sliders.SlideWV.value,
        AMode: document.getElementById('Angle').checked,
        VMode: document.getElementById('Velocity').checked,
        PMode: document.getElementById('Pressure').checked,

    //Send inputs off to CalcIt where the names are instantly available
    //Get all the resonses as an object, result
    const result = CalcIt(inputs)

    //Set all the text box outputs
    document.getElementById('Data').value = result.Data
    //Do all relevant plots by calling plotIt - if there's no plot, nothing happens
    //plotIt is part of the app infrastructure in
    if (result.plots) {
        for (let i = 0; i < result.plots.length; i++) {
            plotIt(result.plots[i], result.canvas[i]);

    //You might have some other stuff to do here, but for most apps that's it for CalcIt!

//Here's the app calculation
//The inputs are just the names provided - their order in the curly brackets is unimportant!
//By convention the input values are provided with the correct units within Main
function CalcIt({ phi, R, V, theta, WV, AMode, VMode, PMode }) {
    const pi = Math.PI, g = 9.81
    let Comments = ""
    let Hnow = 0, Vnow = V, Vp=0,Anow = pi * R * R, Vh = V * Math.cos(theta), Vv = V * Math.sin(theta)
    let dt = 0.001, tnow = 0, acc = 0
    const VolFoam = 4 / 3 * pi * Math.pow(R, 3)
    const rhowater = 1000, rhoair = 1.225
    const rhofoam = phi * rhoair + (1 - phi) * rhowater
    const m = VolFoam * rhofoam
    const AF = 0.1806, BF = 0.6459, CF = 0.4251, DF = 6880.95
    const ReP = 2 * R / 1.8e-5 //Pre-reynolds number for air
    const rhooil=800 //A typical value, no need for an extra slider input
    const thetar = 18/Math.sqrt(rhofoam/rhooil)
    let HPlot = [], VPlot = [], x = 0, y = 0,maxH=0,maxD=0, Dr=0, Hr=0, passedMax=false

    while (y >= 0 && tnow < 10) {
        Re = Math.abs(ReP * (Vh - WV))
        CD = 24 / Re * (1 + AF * Math.pow(Re, BF)) + CF / (1 + DF / Re)
        acc = (- 0.5 * rhoair * Anow * CD * Math.pow(Vh - WV, 2)) / m
        Vh += acc * dt
        Re = ReP * Math.max(Math.abs(Vv), 0.5)
        CD = 24 / Re * (1 + AF * Math.pow(Re, BF)) + CF / (1 + DF / Re)
        acc = (m * g - 0.5 * rhoair * Anow * CD * Vh * Vh) / m
        Vv -= acc * dt
        Vnow = Math.sqrt(Vh * Vh + Vv * Vv)
        Vp= Math.sqrt(Math.pow(Vh-WV,2) + Vv * Vv)
        Re = Math.abs(ReP * (Vp))
        CD = 24 / Re * (1 + AF * Math.pow(Re, BF)) + CF / (1 + DF / Re)
        Pnow = (0.5 * rhoair * Anow * CD * Math.pow(Vp, 2)) / m / Anow //i.e. force/area
        thetanow = Math.abs(Math.atan2(Vv, Vh) * 180 / pi)
        if (thetanow<1) passedMax=true //Crude check
        if (passedMax && thetanow>thetar && Dr==0){
        y += Vv * dt
        x += Vh * dt
        tnow += dt
        HPlot.push({ x: x, y: Math.max(y, 0) })
        if (AMode) VPlot.push({ x: x, y: thetanow })
        if (VMode) VPlot.push({ x: x, y: Vnow })
        if (PMode) VPlot.push({ x: x, y: Pnow })

    let L2 = "", Y2 = ""
    if (AMode) { L2 = "Angle"; Y2 = "θ& °" }
    if (VMode) { L2 = "Velocity"; Y2 = "v& m/s" }
    if (PMode) { L2 = "Pressure"; Y2 = "P& Pa" }
    //Now set up all the graphing data detail by detail.
    let plotData = [HPlot, VPlot]
    let lineLabels = ["Height", L2] //An array of labels for each dataset
    const prmap = {
        plotData: plotData,
        lineLabels: lineLabels,
        xLabel: "Distance& m", //Label for the x axis, with an & to separate the units
        yLabel: "Height& m", //Label for the y axis, with an & to separate the units
        y2Label: Y2, //Label for the y2 axis, null if not needed
        yAxisL1R2: [1, 2], //Array to say which axis each dataset goes on. Blank=Left=1
        logX: false, //Is the x-axis in log form?
        xTicks: undefined, //We can define a tick function if we're being fancy
        logY: false, //Is the y-axis in log form?
        yTicks: undefined, //We can define a tick function if we're being fancy
        legendPosition: 'top', //Where we want the legend - top, bottom, left, right
        xMinMax: [,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        yMinMax: [0,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        y2MinMax: [0,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        xSigFigs: 'F2', //These are the sig figs for the Tooltip readout. A wide choice!
        ySigFigs: 'F1', //F for Fixed, P for Precision, E for exponential

    //Now we return everything - text boxes, plot and the name of the canvas, which is 'canvas' for a single plot

    return {
        Data: "θ_r = "+ thetar.toFixed(0) + "°, D_r = "+Dr.toFixed(1) + "m, H_r = "+Hr.toFixed(1)+"m\n D_max = "+maxD.toFixed(1)+"m, H_max = "+maxH.toFixed(1) + "m",
        plots: [prmap],
        canvas: ['canvas'],



Getting foam to a fire is a complex problem. It has to travel the distance to the correct part of the fire and often has to travel over a barrier (e.g. the side of an oil tank) to get to the fire. The foam is relatively light so the drag caused by motion through the air can slow it significantly. Then there are head- or tail-winds to complicate matters.

As the Foam Fall app discusses, there is an extra concern. If the foam drops at high speed into burning fuel it can pick up fuel and add to the fire when it comes back to the surface. But if it lands at an angle below the "ricochet" angle, it bounces off. So aiming the foam over a tank wall while getting it to land below the ricochet angle is a possible strategy for improving the effectiveness of the foam.

You choose an expansion ratio E, a Jet Velocity V, a Jet Angle θ and the Diameter, D, of your jet. For simplicity, the app calculates what happens to a foam ball of that diameter rather than an entire jet. With the Wind Velocity you can have a tail wind (+ve) or head wind (-ve) to see the effect.

The calculation takes into account the drag in both the horizontal and vertical directions, along with the pull of gravity. The drag coefficient of a sphere is not the simple "0.5" that is always quoted but depends strongly on the Reynolds number.

In addition to the graph of height versus distance, you can plot one of 3 parameters.

  1. Angle. This shows the angle decreasing to zero at the top then increasing as it falls. If you are interested in the ricochet effect, you can see how best to reach your desired "sweet spot".
  2. Velocity. This is the velocity in direction of flight so doesn't (usually) go to zero at the top of the curve.
  3. Pressure. The drag from the travel through the air provides a pressure on the foam. If this is greater than the yield stress then, in principle, the foam ball will break up. Given that foam yield stresses are generally rather small, the values calculated here indicate instant foam break-up. Future versions of the app will try to address this issue.

Although you can use the mouse to read most values off the graph, the Data box provides some useful values. For the ricochet calculations we see θr which is the critical angle, then Dr which is the Distance travelled to that point and Hr which is the Height of that point. You also get Dmax and Hmax which are the maximum distance travelled and height attained.

Of course we don't fight fires with foam balls. But the app is a start. If anyone wants to help make it more realistic with proper foam jet, I will be happy to improve the app and, of course, acknowledge the contribution.