Last active
September 11, 2022 11:07
-
-
Save jasoncoon/c5103fd5d0ba4d7da3677ed5187fc61c to your computer and use it in GitHub Desktop.
Doom Fire Eye pattern for Pixelblaze & Lux Lavalier
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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); | |
} |
Good enough for me! Thanks!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Only on Twitter at the moment: https://twitter.com/jasoncoon_/status/1567672583436247046