Anyone seen a pattern like this?

Flame effect from Dave’s Garage

I’ve got sparkfire going as the closest thing.
I don’t think the arduino based code will work on the PB so I was curious if anyone came across anything like this.

There are a handful of 1D and 2D fire patterns.
If none of them is enough of a match, we can look at his particular code and figure out what it does differently.

He’s actually about to open source his led code wrapper, sits on top of FastLED,etc and adds a few standards like circles, lines, and so on, and some directional wipes/etc.

Nothing PB can’t do now.

1 Like

Hey @Jsharkbite - What are the biggest differences between sparkfire and what you see in the video? Are you just referring to how the fire is coming from both sides?

Sparkfire can be a little unruly to tune, especially depending on how many LEDs you have and their spacing.

To me, it looks like spark fire, and maybe a 2d spark fire that could be just multiple lines of spark fire (no horizontal heat transfer).

The biggest difference in sparkfire is the strobing effect. Too frequently the entire strip of LEDs will flash white. I can’t seem to limit the frequency and or the amount of LEDs that partake in the strobing effect.

The end of the video captured the strobe.

Yes, it’s a bit too much white. Dave’s never does white but leans yellow.

There are a lot of fire simulator patterns, and folding the various techniques/methods into a customizable pattern would be good.

We also lack pallette support currently, which would allow further tweaking, not just to slightly tweak the color scheme but to allow things like “coldfire” (a blue fire), for example.

I’m thinking this might be a fun November theme for a contest… Make the best looking fire in 1D or 2D… Graded on things like realism, adjustability, and so on.

October is still Halloween themed contest, reminder.

@Jsharkbite

OK, here’s a version of sparkfire where you can play with all the parameters with sliders. I also added a way to select how white the hottest parts get. Note that this uses pow() inside render(), which is a classic way to slow down your framerate :slight_smile: Once you’ve found the right level of whiteness / saturation, you can make things faster by replacing it with similar math that doesn’t use pow().

I pre-populated it with parameters that looked good on my 4m of 60/m SK6812 (FYI mine are RGBW). Because sparks regenerate when they get to the end of the strip, your total strip length will affect how much fire is present at once.

Let me know how this goes!

Code for "sparkfire tunable"
/*
  Sparkfire
  
  What happens when you allow a pattern like 'sparks' to heat the air around it?
  You get spark fire! It's probably a good idea to read through the comments on
  'sparks' first.

  Depending on the number of pixels in your strip, you may need to adjust the
  first few variables to your liking; While there's meant to
  mostly work depending on the number of pixels, you should tune them to look
  good on your setup.
*/

// Number of sparks. Try 3-6.
numSparks = 3 + floor(pixelCount / 80)  

// Subtractive cooling. Try 0.02..0.3. Higher cools faster.
export var cooling1 = .0217
export function sliderCooling1 (_v) {
  cooling1 = _v * .2
}

// Coefficient cooling. Try 0.9..0.995. Lower cools faster.
export var cooling2 = .97
export function sliderCooling2 (_v) {
  cooling2 = .97 + sqrt((1-_v)) * .03
}

export var accel = .03    // How much sparks accelerate as they rise (try 0.02..0.05)
export function sliderAccel (_v) {
  accel = _v * .05
}

export var speed = .07    // Baseline speed of travel for each spark (try 0.03..0.1)
export function sliderSpeed (_v) {
  speed = _v * .1
}

export var whiteness = .33    // Baseline speed of travel for each spark (try 0.03..0.1)
export function sliderWhiteness (_v) {
  whiteness = 3 * (1 - _v)
}



// Energy of each spark. Affects pixel heating and spark speed.
export var sparks = array(numSparks)
sparkX = array(numSparks)  // X position of each spark, in units of pixel index
var pixels = array(pixelCount) // Brightness (heat energy) of each pixel

// Initialize sparks with random position and energy
for (i = 0; i < numSparks; i++) {
  sparkX[i] = random(pixelCount)
  // Further sparks are older and have less energy
  sparks[i] = .2 * (1 - sparkX[i] / pixelCount) + random(.4)
}

// Set up an initial heat value so init doesn't look empty
pixels[0] = 20

// Store the maximum energy seen each frame
export var maxEnergy = 0

export function beforeRender(delta) {
  delta *= speed // Scale the time between loops by speed.
  maxEnergy = 0

  for(i = 0; i < pixelCount; i++) {
    // When more time has passed, more subtractive cooling occurs
    cooldown = cooling1 * delta
   
    if(cooldown > pixels[i]) {
      pixels[i] = 0
    } else {
      // Coefficient cooling (`cooling2`) makes hotter sparks lose more energy.
      // Subtractive cooling makes all sparks lose some energy.
      pixels[i] = pixels[i] * cooling2 - cooldown
    }
    
    maxEnergy = max(maxEnergy, pixels[i]) // Keep track of max energy seen
  }
  /*
    Heat rises. Starting at the far end and working towards the first pixel,
    compute a weighted average of the heat from the preceding pixels and apply
    it to this one. Weighting several pixels behind higher creates a nice wispy
    pattern that can be seen more clearly by turning down `speed`.
  */
  for (k = pixelCount - 1; k >= 4; k--) {
      h1 = pixels[k - 1]
      h2 = pixels[k - 2]
      h3 = pixels[k - 3]
      h4 = pixels[k - 4]
      pixels[k] = (h1 + h2 + h3 * 2 + h4 * 3) / 7
    }
  
  for (i = 0; i < numSparks; i++) {
    // Reinitialize a spark that's passed the end and been reset
    if (sparks[i] <= 0) {
      sparks[i] = .1 + random(.4)
      sparkX[i] = 0
    }

    // Accelerate (add kinetic energy) to each spark
    sparks[i] += accel * delta
    
    // Stash the original x position of this spark 
    ox = sparkX[i]

    // Δd = r·Δt 
    // Sparks advance at a rate that's the square of their energy
    sparkX[i] += sparks[i] * sparks[i] * delta

    // Reset sparks that are past the end
    if (sparkX[i] > pixelCount) {
      sparkX[i] = 0
      sparks[i] = 0
      continue  // Skip the rest of the for() loop for this iteration
    }
    

    /*
      For all pixels between the new x position and the original x position,
      heat the pixels (add brightness). Higher energy sparks are travelling
      faster and thus don't heat each pixel of air as much.
    */
    for (j = ox; j < sparkX[i]; j++)
      pixels[j] += clamp(1 - sparks[i] * .4, 0 , 1) * .5
  }
}

export function render(index) {
  v = pixels[index]
  
  var pctOfMax = v / maxEnergy // This pixel's percentage of the overall highest energy pixel

  /*
    v * v is our typical gamma correction, and it's constrained to remain
    between 0 and 1. Multiplying this by 0.1 keeps us in the red-yellow range;
    higher energies will be yellow.
  */
  h = .1 * clamp(v * v, 0, 1) 
  
  /*
    Desaturate (to white) any pixel with energy values between 1 and 1.5;
    Saturation will be clamped to 0 for any energy value above 1.5. Without
    this, the hottest pixels are just yellow instead of white hot.
  */
  // s = 1 - (v - 1) * 2 // Default saturation code
  s = 1 - pow(pctOfMax, whiteness) // To greatly speed up pattern, replace this with equivalent scaling nce you've tuned your look with the whiteness slider

  // It's useful to remember that HSV clamps s and v for us to within 0..1
  hsv(h, s, v)
}

2 Likes

This sounds awesome! I will start tinkering with it as soon as my little one is in bed. I had all kinds of crazy ideas for candy corn going along the roof and candy canes (1D stix). This is probably the most helpful forum i’ve been on in a long time!

Yep! this is very cool! I’ve got it running and I see what you mean by it being very dependent upon the number of pixels I have going. So it got me wondering, since the gable above the garage is about 90 pixels and the two smaller gables are only 40-60, is it possible to have each of the strands running different settings (slider options)? So channel 1 would have a different speed/acceleration LED count than channel 2 etc, etc…

Yes, you’d have to add sliders and some conditional changes based on which strand (by index #) to use the “right” values.

For the next version of PB, I’m thinking we should refactor Sparkfire to generate new sparks at a probabilistic rate, instead of one that depends so much on recycling the cooled sparks that made it to the end.

3 Likes

We also lack pallette support currently, which would allow further tweaking, not just to slightly tweak the color scheme but to allow things like “coldfire” (a blue fire), for example.

Although it’s not 1D (for now) Doom Fire has color control. You can set the base hue to anything you want, and the rest of the fire colors are determined by the “temperature” - basically small random shifts in hue, plus desaturation (towards white) as it heats up. Try the updated 2.0 version from the library, set it in “Dragon” mode, crank the flames up a bit, add a little wind and…

Also, I’ve got this more “explode-y” noise based fire in the works. Not sure it’s better, but it’s definitely different.
fire2

2 Likes

The new 2.0 version is nifty, and yes, it’s got a hue option.

We really don’t have any patterns with palettes, because we don’t have palettes (yet).
Hopefully soon, based on something I know Ben’s doing with arrays, it’ll be easier to do so.

Related to the original fire at the top:

Dave of Dave’s Garage has just released NightDriver…his esp32 led code all organized.
I suspect all of the usual suspects will be poring over the code and playing with it to see what different he did.