Skip to content

Instantly share code, notes, and snippets.

@jasoncoon
Last active September 11, 2022 11:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jasoncoon/c5103fd5d0ba4d7da3677ed5187fc61c to your computer and use it in GitHub Desktop.
Save jasoncoon/c5103fd5d0ba4d7da3677ed5187fc61c to your computer and use it in GitHub Desktop.
Doom Fire Eye pattern for Pixelblaze & Lux Lavalier
/* DOOM Fire Eye
Slightly modified to polar coords by Jason Coon
Video demo here: https://twitter.com/jasoncoon_/status/1567672583436247046
Updated 2D Fire effect, with "enhanced" dragon's breath mode.
Now with More, Better fire! Version 2 has more dramatic flame,
and an improved wind algorithm.
The method is inspired by the low-res fire in the prehistoric PSX port of DOOM!
Details: https://fabiensanglard.net/doom_fire_psx/ It's purely convolution-based
fire -- no Perlin or other gradient, value or fractal noise fields.
Requires a 2D display and an appropriate mapping function.
MIT License
v2.0 JEM(ZRanger1) 00/02/2021
*/
// display size - enter the dimensions of your display here
var width = 8;
var height = 8;
// array is sized one row larger than display so we can permanently
// store the "source" fire in the last row, and two rows wider so
// we don't have to worry about clipping or wrapping.
var arrayWidth = width + 2;
var arrayHeight = height + 1;
var lastRow = arrayHeight - 1;
var lastCol = width + 1;
// Global variables for rendering
var buffer1 = array(arrayWidth); // main drawing surface
var buffer2 = array(arrayWidth); // secondary drawing surface
var pb1, pb2; // buffer pointers for swapping
var baseHue = 0;
var baseBri = 0.6;
var maxCooling = 0.25; // how quickly flames die down
var dragonMode = 0; // 0: plain old fire, 1: dragon's breath
var breathTimer; // dragon's breath cycle time
var wind = 0.30; // probability of wind direction change. 0 == no wind
var windDirection = 0; // current wind direction
var frameTimer = 9999; // accumulator for simulation timer
var simulationSpeed = 50; // min milliseconds between simulation frames
var perturb = perturbNormal; // pointer to fn that plays with fire
// UI
export function hsvPickerHue(h,s,v) {
baseHue = h;
baseBri = v;
}
export function sliderFlameHeight(v) {
v = (1-v); v = v * v;
maxCooling = max(4 * v,0.1);
}
export function showNumberFlameHeight() {return maxCooling}
export function sliderWind(v) {
wind = (v * v) / 2;
if (wind == 0) windDirection = 0;
}
export function showNumberWind() {return wind}
var lastDragonMode = dragonMode;
export function sliderDragonMode(v) {
dragonMode = (v > 0.5);
if (dragonMode == lastDragonMode) return;
lastDragonMode = dragonMode;
if (dragonMode) {
perturb = perturbDragonBreath;
} else {
initBuffers();
perturb = perturbNormal;
}
}
export function showNumberDragonMode() {return dragonMode}
// simulation speed (ms per frame). Adjust to taste
// for your display
export function sliderSpeed(v) {
simulationSpeed = v * 200;
}
export function showNumberSpeed() {return simulationSpeed}
// create two buffers for calculation
function allocateFrameBuffers() {
for (var i = 0; i < arrayWidth; i ++) {
buffer1[i] = array(arrayHeight);
buffer2[i] = array(arrayHeight);
}
pb1 = buffer1;
pb2 = buffer2;
}
// set the lowest row to 1 - this is the source of our fire
function initBuffers() {
for (var i = 0; i < arrayWidth; i ++) {
pb1[i][lastRow] = 1;
pb2[i][lastRow] = 1;
}
}
function perturbDragonBreath() {
for (var i = 0; i < arrayWidth; i ++) {
pb2[i][lastRow] = breathTimer+wave(-.21+(i/arrayWidth));
}
}
// change the base heat in a slow wave
function perturbNormal() {
for (var i = 0; i < arrayWidth; i ++) {
pb2[i][lastRow] = 0.9+wave(triangle(time(0.3))+(i/arrayWidth))/3;
}
}
function swapBuffers() {
var tmp = pb1; pb1 = pb2; pb2 = tmp;
}
// Fire is hottest at the bottom, and "cools" as it rises. Each pixel
// calculates it's value based on the one below it, with allowance for
// the current wind direction.
function doFire() {
swapBuffers();
if (wind > 0) windDirection = (random(1) < wind) ? floor(random(3)) - 1 : windDirection;
for (var x = 1; x < lastCol; x++) {
// cooling effect decreases with height, so very hot particles
// that don't cool early on get "carried" farther. It just looks better.
for (var y = 1; y < lastRow; y++) {
var r = random(maxCooling) * (y/lastRow);
var windFx = (abs((lastRow / 2) - y) / lastRow);
windFx = x + (random(1) < 0.5-windFx) * windDirection;
pb2[x][y] = max(0,pb1[windFx][y+1] - r);
}
}
}
// Initialization
allocateFrameBuffers();
initBuffers()
var xOffset = .5
var yOffset = .5
var pupilSize = .75;
export function beforeRender(delta) {
frameTimer += delta;
if (frameTimer > simulationSpeed) {
breathTimer = wave(time(0.1));
doFire();
perturb();
frameTimer = 0;
}
if (random(1) > .995) {
xOffset = random(.4) + .3 // random number between .3 and .7
yOffset = random(.4) + .3 // random number between .3 and .7
}
if (random(1) > .995) {
pupilSize = random(.2) + .55 // random number between .55 and .75
}
// static rotation
resetTransform()
translate(-.5, -.5)
rotate(.75 * PI2) // rotate 270 degrees
translate3D(xOffset, yOffset, .5) //optionally move back so the origin is in a corner instead of the center
}
function map(x, in_min, in_max, out_min, out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
export function render2D(index, x, y) {
x = (x - 0.5) * 2;
y = (y - 0.5) * 2;
dist = 1 - sqrt(x * x + y * y);
if (dist > pupilSize) return;
a = (atan2(y, x) + PI) / PI / 2;
x0 = a * width;
y0 = dist * height;
if ((x0 < 0 || x0 > width) || (y0 < 0 || y0 > height)) return;
bri = pb2[x0][y0];
bri = bri * bri * bri;
hsv(baseHue+((0.05*bri)), 1.3-bri/4,baseBri * bri);
}
@DoomHammer
Copy link

Where can we see it in action?

@jasoncoon
Copy link
Author

@DoomHammer
Copy link

Good enough for me! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment