1
Fork 0

Funny proof of concept here.

This commit is contained in:
Marisa the Magician 2023-10-08 22:27:33 +02:00
commit 68e9f04b4a
12 changed files with 1151 additions and 0 deletions

View file

@ -0,0 +1,215 @@
// This is a very awful and hacky and gross attempt at replicating Unreal's
// FireTexture class in ZScript using canvas drawing
//
// Abandon all hope ye who enter here
class FireSpark
{
int type; // see above enums
int heat; // heat of this spark
int x, y; // location
int args[4]; // arguments
// 0: usually x speed
// 1: usually y speed
// 2: usually age or emitter frequency
// 3: usually expiration time
}
class FireTexture
{
enum EFireSpark
{
SPARK_Burn, // stationary spark with randomized intensity
SPARK_Sparkle, // sparks that jitter
SPARK_Pulse, // sparks that turn on and off
SPARK_Signal, // akin to burn but with some tweakable behavior (?)
SPARK_Blaze, // sparks emitted in random directions
SPARK_OzHasSpoken, // sparks that move up and fade out
SPARK_Cone, // akin to blaze but with gravity
SPARK_BlazeRight, // blaze but aiming to the right
SPARK_BlazeLeft, // blaze but aiming to the left
SPARK_Cylinder, // sparks that ping-pong horizontally
SPARK_Cylinder3D, // like above but sparks are invisible on their way back
SPARK_Lissajous, // sparks that ping-pong both horizontally and vertically at different frequencies
SPARK_Jugglers, // like cylinder but vertical
SPARK_Emit, // sparks that move in a random straight direction and fade out
SPARK_Fountain, // like emit but with gravity
SPARK_Flocks, // bunch of sparks that jitter smoothly in a circular area
SPARK_Eels, // like emit except not (?)
SPARK_Organic, // sparks that move up with slightly randomized angles
SPARK_WanderOrganic, // hell if I know
SPARK_RandomCloud, // ???????
SPARK_CustomCloud, // ???????????????
SPARK_LocalCloud, // ????????????????????
SPARK_Stars, // single pixels of constant intensity
SPARK_LineLightning, // lightning bolts that zap between two spots
SPARK_RampLightning, // like above, but the intensity fades towards the endpoint
SPARK_SphereLightning, // like above, but the bolts spread out in all directions
SPARK_Wheel, // sparks that move in circles
SPARK_Gametes, // like Eels but sorta wriggly
SPARK_Sprinkler, // what it says on the tin
NUM_SPARK_TYPES
};
enum EFireSparkInternal
{
// special types
SPARK_LissajousX = NUM_SPARK_TYPES,
SPARK_LissajousY,
// spawned sparks (names are my own here, based on guesswork)
SSPARK_Unused, // not used at all, may have been some form of padding?
SSPARK_Ember, // ember from a Blaze type, constant speed, fades out
SSPARK_EmberCustom, // like above but fades based on args[3], used by OzHasSpoken and Emit
SSPARK_Gravity, // y speed is affected by gravity, dies after a set timeout, used by Cone and BlazeRight/Left
SSPARK_Rising, // fading out, constant upwards speed, used by Organic types
SSPARK_CloudY, // same but fades in instead of out, used by Random/LocalCloud
SSPARK_CloudXY, // this one moves in all directions, used by CustomCloud
SSPARK_Eel, // moves in a chosen direction and fades out very slowly, spawned by Eels
SSPARK_Twirl, // horizontal and vertical speed spin in a circle, used by Flocks and Wheel
SSPARK_Sprinkle, // rises up at constant speed, horizontal speed is sinusoidal, used by Sprinkler
SSPARK_EmberCustom2, // identical but uses args[2] instead, seemingly unused
SSPARK_GravityFade, // used by Fountain, has half gravity and fades out
SSPARK_Gamete // like eels but moving erratically, spawned by Gametes
};
private bool initd; // true if the texture has been initialized
private int sparklimit; // maximum sparks allowed to be rendered
private uint phase; // global timer
private Array<FireSpark> sparks;// all the active sparks
private bool bhasstars; // true if any sparks with type SPARK_Stars exist
// parses a fire texture from a file
static FireTexture Load( string path )
{
static const String typetable[] =
{
"Burn", "Sparkle", "Pulse", "Signal", "Blaze",
"OzHasSpoken", "Cone", "BlazeRight", "BlazeLeft",
"Cylinder", "Cylinder3D", "Lissajous", "Jugglers",
"Emit", "Fountain", "Flocks", "Eels", "Organic",
"WanderOrganic", "RandomCloud", "CustomCloud",
"LocalCloud", "Stars", "LineLighting", "RampLighting",
"SphereLighting", "Wheel", "Gametes", "Sprinkler"
};
int lmp = Wads.CheckNumForFullName(path);
if ( lmp == -1 ) ThrowAbortException("Could not find fire texture '%s'.",path);
String dat = Wads.ReadLump(lmp);
dat.Replace("\r","");
Array<String> lines, line;
dat.Split(lines,"\n",TOK_SKIPEMPTY);
String cname;
uint width, height;
lines[0].Split(line," ");
cname = line[0];
width = line[1].ToInt();
height = line[2].ToInt();
FireTexture ftx = new("FireTexture");
ftx.Init(width,height);
ftx.cv = TexMan.GetCanvas(cname);
if ( !ftx.cv ) ThrowAbortException("Could not find canvas '%s'",cname);
lmp = Wads.CheckNumForFullName("palettes/"..lines[1]..".pal");
if ( lmp == -1 ) ThrowAbortException("Could not find palette '%s'",lines[1]);
dat = Wads.ReadLump(lmp);
for ( uint i=0; i<256; i++ )
{
ftx.pal[i].a = 255;
ftx.pal[i].r = dat.ByteAt(i*3);
ftx.pal[i].g = dat.ByteAt(i*3+1);
ftx.pal[i].b = dat.ByteAt(i*3+2);
}
line.Clear();
lines[2].Split(line," ");
ftx.brising = !!line[0].ToInt();
ftx.renderheat = line[1].ToInt();
ftx.sparklimit = line[2].ToInt();
ftx.sparks.Clear();
for ( int i=3; i<lines.Size(); i++ )
{
line.Clear();
lines[i].Split(line," ");
FireSpark s = new("FireSpark");
int tfn = -1;
for ( int j=0; j<NUM_SPARK_TYPES; j++ )
{
if ( !(line[0] ~== typetable[j]) ) continue;
tfn = j;
break;
}
if ( tfn == -1 ) ThrowAbortException("Invalid spark type '%s'",line[0]);
s.type = tfn;
s.heat = uint(line[1].ToInt())&255;
s.x = uint(line[2].ToInt())&ftx.umask;
s.y = uint(line[3].ToInt())&ftx.vmask;
s.args[0] = uint(line[4].ToInt())&255;
s.args[1] = uint(line[5].ToInt())&255;
s.args[2] = uint(line[6].ToInt())&255;
s.args[3] = uint(line[7].ToInt())&255;
// simplify Lissajous
if ( s.type == SPARK_Lissajous )
{
if ( !s.args[1] && !s.args[3] ) s.type = SPARK_LissajousX;
else if ( !s.args[0] && !s.args[2] ) s.type = SPARK_LissajousY;
}
ftx.sparks.Push(s);
}
ftx.PostLoad();
return ftx;
}
void PostLoad()
{
PostInitTables();
// always assume there's stars at first
bHasStars = true;
}
bool Initialized()
{
return initd;
}
int GetWidth()
{
return width;
}
int GetHeight()
{
return height;
}
void Init( uint usize, uint vsize )
{
// check that texture size is in powers of two
// and is smaller than 64KiB
if ( usize&(usize-1) != 0 ) ThrowAbortException("Tried to init FireTexture with non power of two width.");
if ( vsize&(vsize-1) != 0 ) ThrowAbortException("Tried to init FireTexture with non power of two height.");
if ( usize > 256 ) ThrowAbortException("Tried to init FireTexture with width above 256.");
if ( vsize > 256 ) ThrowAbortException("Tried to init FireTexture with height above 256.");
width = usize;
height = vsize;
umask = width-1;
vmask = height-1;
InitTables();
initd = true;
}
void Tick()
{
RedrawSparks();
if ( bRising ) CalcWrapFire();
else CalcSlowFire();
PostDrawSparks();
}
void Render()
{
// blit to canvas, one pixel at a time (oof)
for ( uint y=0; y<height; y++ )
for ( uint x=0; x<width; x++ )
cv.Clear(x,y,x+1,y+1,GetPixelRGB(x,y));
}
}

View file

@ -0,0 +1,46 @@
// math functions and whatnot
extend class FireTexture
{
private uint randindex;
private uint8 randtable[512]; // fast rng table
private uint8 phasetable[256]; // unsigned sine table
private int8 signedtable[256]; // signed sine table
private uint8 lighttable[256]; // sine table but with an offset
private uint8 firetable[1024]; // color mapping table
// (for blending 4 pixels together)
// (influenced by heat, also)
// (original has 4 more bytes, probably
// to prevent overflows, but that's not
// needed here, we can just clamp)
// sets up rng and sine tables
private void InitTables()
{
for ( uint i=0; i<256; i++ )
phasetable[i] = uint(round(127.45+127.5*sin((i/256.)*360.)));
for ( uint i=0; i<256; i++ )
{
lighttable[i] = clamp(phasetable[i]+32,0,255);
signedtable[i] = -128+phasetable[i];
}
for ( uint i=0; i<512; i++ )
randtable[i] = Random[FireTexture](0,255);
}
// sets up fire table
private void PostInitTables()
{
for ( uint i=0; i<1024; i++ )
firetable[i] = uint(clamp(i/4.+1.-(255-renderheat)/16.,0.,255.));
}
// PRNG
private uint FastRand()
{
randindex = (randindex+1)&63;
return (randtable[(randindex+31)&63] ^= randtable[randindex]);
}
}

View file

@ -0,0 +1,79 @@
// raw pixel data handling
extend class FireTexture
{
private Canvas cv; // where to blit the data to
private uint width, height; // the expected width and height of this texture
// (can't be greater than 256x256)
private uint umask, vmask; // used for wrapping texture coordinates
private Color pal[256]; // loaded palette
private uint8 pixels[65536]; // internal texture data
private bool brising; // heat is propagated upwards
private int renderheat; // overall intensity
// direct pixel access wrapper functions
private uint GetPixel( uint x, uint y )
{
// ensure coords are wrapped
x &= umask;
y &= vmask;
return pixels[x+y*width];
}
private void PutPixel( uint x, uint y, uint c )
{
// ensure coords are wrapped
x &= umask;
y &= vmask;
pixels[x+y*width] = c;
}
private Color GetPixelRGB( uint x, uint y )
{
// ensure coords are wrapped
x &= umask;
y &= vmask;
return pal[pixels[x+y*width]];
}
private uint LookupFiretable( uint a, uint b, uint c, uint d )
{
return firetable[min(a+b+c+d,1023)];
}
// heat propagation with bRising (triangle shifted two pixels down)
private void CalcWrapFire()
{
uint mid, left, right, up;
for ( uint y=0; y<height; y++ )
{
for ( uint x=0; x<width; x++ )
{
mid = GetPixel(x,y+1);
left = GetPixel(x-1,y+1);
right = GetPixel(x+1,y+1);
up = GetPixel(x,y+2);
PutPixel(x,y,LookupFiretable(mid,left,right,up));
}
}
}
// ... without bRising (triangle shifted one pixel down)
// on last row, we must sample from a copy of the zeroth row, otherwise
// we would be sampling from an already modified pixel
private void CalcSlowFire()
{
uint temp[256];
for ( uint i=0; i<width; i++ ) temp[i] = pixels[i];
uint mid, left, right, up;
for ( uint y=0; y<height; y++ )
{
for ( uint x=0; x<width; x++ )
{
mid = GetPixel(x,y);
left = GetPixel(x-1,y);
right = GetPixel(x+1,y);
up = (y==height-1)?temp[x&255]:GetPixel(x,y+1);
PutPixel(x,y,LookupFiretable(mid,left,right,up));
}
}
}
}

View file

@ -0,0 +1,692 @@
// spark handling
// perhaps one of the most blatantly reverse-engineered parts here
extend class FireTexture
{
// movement according to specified speed
private void MoveSparkXY( FireSpark s, int xspeed, int yspeed )
{
if ( xspeed < 0 )
{
if ( (FastRand()&127) < uint(-xspeed) )
s.x = umask&(s.x-1);
}
else
{
if ( (FastRand()&127) < uint(xspeed) )
s.x = umask&(s.x+1);
}
if ( yspeed < 0 )
{
if ( (FastRand()&127) < uint(-yspeed) )
s.y = vmask&(s.y-1);
}
else
{
if ( (FastRand()&127) < uint(yspeed) )
s.y = vmask&(s.y+1);
}
}
// movement according to spark speed
private void MoveSpark( FireSpark s )
{
MoveSparkXY(s,s.args[0],s.args[1]);
}
// ???
private void MoveSparkTwo( FireSpark s )
{
if ( s.args[0] < 0 )
{
if ( (FastRand()&127) < uint(-s.args[0]) )
s.x = umask&(s.x-1);
}
else
{
if ( (FastRand()&127) < uint(s.args[0]) )
s.x = umask&(s.x+1);
}
s.y = vmask&(s.y-2);
}
// random movement according to specified angle
private void MoveSparkAngle( FireSpark s, int angle )
{
int xdir = -127+phasetable[angle&255];
int ydir = -127+phasetable[(angle+64)&255];
MoveSparkXY(s,xdir,ydir);
}
// draws a lightning bolt with a set origin and dimensions, fading from two palette indices
private void DrawBolt( int x, int y, int w, int h, uint fromcol, uint tocol )
{
uint flasharray[256];
int xstep, ystep, ylen, xlen;
// is this cache-friendly hack needed? idk
if ( ((h&1) && ((h*2) >= w)) || ((w&1) && ((h*2) < w)) )
{
x += (w&1)?-w:w;
y += (h&1)?-h:h;
w ^= 1;
h ^= 1;
uint tmp = fromcol;
fromcol = tocol;
tocol = tmp;
}
int longest = 1|((w>=h)?w:h);
uint fpos = 0;
for ( int f=0; f<longest; f++ )
fpos += (flasharray[f]=FastRand());
if ( h&1 )
{
ystep = -1;
ylen = -h;
}
else
{
ystep = 1;
ylen = h;
}
if ( w&1 )
{
xstep = -1;
xlen = -w;
}
else
{
xstep = 1;
xlen = w;
}
int rcol = fromcol<<23;
int cslope = ((tocol-fromcol)<<23)/longest;
if ( w >= h )
{
int yz = (y<<6);
int bias = ((ylen<<6)-fpos)/longest;
for ( int f=0; f<w; f++ )
{
yz += flasharray[f]+bias;
PutPixel(x,yz>>6,(rcol+=cslope)>>23);
x += xstep;
}
}
else
{
int xz = (x<<6);
int bias = ((xlen<<6)-fpos)/longest;
for ( int f=0; f<h; f++ )
{
xz += flasharray[f]+bias;
PutPixel(xz>>6,y,(rcol+=cslope)>>23);
y += ystep;
}
}
}
// handle all sparks (big heckin' function here)
private void RedrawSparks()
{
uint newx, newy, newh;
uint ns;
phase++;
for ( int i=0; i<sparks.Size(); i++ )
{
FireSpark s = sparks[i];
switch ( s.type )
{
// normal sparks
case SPARK_Burn:
{
PutPixel(s.x,s.y,FastRand());
break;
}
case SPARK_Sparkle:
{
newx = s.x+((FastRand()*s.args[0])>>8);
newy = s.y+((FastRand()*s.args[1])>>8);
PutPixel(newx,newy,s.heat);
break;
}
case SPARK_Pulse:
{
PutPixel(s.x,s.y,s.heat);
s.heat = (s.heat+s.args[3])&255;
break;
}
case SPARK_Signal:
{
if ( s.heat > s.args[2] )
PutPixel(s.x,s.y,s.heat);
s.heat = (s.heat+s.args[3])&255;
if ( s.heat < s.args[3] )
s.heat = FastRand();
break;
}
case SPARK_Cylinder:
{
newh = min(phasetable[(s.args[0]+64)&255]+s.heat,255);
newx = s.x+((phasetable[s.args[0]]*s.args[1])>>8);
PutPixel(newx,s.y,newh);
s.args[0] = (s.args[0]+s.args[3])&255;
break;
}
case SPARK_Cylinder3D:
{
if ( ((s.args[0]+64)&255) < 128 )
{
newh = min(phasetable[(s.args[0]+64)&255]+s.heat,255);
newx = s.x+((phasetable[s.args[0]]*s.args[1])>>8);
PutPixel(newx,s.y,newh);
}
s.args[0] = (s.args[0]+s.args[3])&255;
break;
}
case SPARK_Jugglers:
{
newh = min(phasetable[(s.args[0]+64)&255]+s.heat,255);
newy = s.y+((phasetable[s.args[0]]*s.args[1])>>8);
PutPixel(s.x,newy,newh);
s.args[0] = (s.args[0]+s.args[3])&255;
break;
}
case SPARK_Lissajous:
{
newh = lighttable[(s.args[0]+64)&255];
newx = s.x+((phasetable[s.args[0]]*s.heat)>>8);
newy = s.y+((phasetable[s.args[1]]*s.heat)>>8);
PutPixel(newx,newy,newh);
s.args[0] = (s.args[0]+s.args[2])&255;
s.args[1] = (s.args[1]+s.args[3])&255;
break;
}
case SPARK_LissajousX:
{
newh = lighttable[(s.args[0]+64)&255];
newx = s.x+((phasetable[s.args[0]]*s.heat)>>8);
PutPixel(newx,s.y,newh);
s.args[0] = (s.args[0]+s.args[2])&255;
break;
}
case SPARK_LissajousY:
{
newh = lighttable[(s.args[0]+64)&255];
newy = s.y+((phasetable[s.args[1]]*s.heat)>>8);
PutPixel(s.x,newy,newh);
s.args[1] = (s.args[1]+s.args[3])&255;
break;
}
// emitter types
case SPARK_Blaze:
{
if ( (sparks.Size() >= sparklimit) || (FastRand() >= 128) )
break;
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Ember;
ns.heat = s.heat;
ns.x = s.x;
ns.y = s.y;
ns.args[0] = FastRand()-128;
ns.args[1] = FastRand()-128;
ns.args[2] = s.args[2];
ns.args[3] = s.args[3];
sparks.Push(ns);
break;
}
case SPARK_OzHasSpoken:
{
if ( (sparks.Size() >= sparklimit) || (FastRand() >= 128) )
break;
FireSpark ns = new("FireSpark");
ns.type = SSPARK_EmberCustom;
ns.heat = s.heat;
ns.x = s.x;
ns.y = s.y;
ns.args[0] = (FastRand()&127)-63;
ns.args[1] = -127;
ns.args[3] = 2;
sparks.Push(ns);
break;
}
case SPARK_Cone:
{
if ( (sparks.Size() >= sparklimit) || (FastRand() >= 64) )
break;
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Gravity;
ns.heat = s.heat;
ns.x = s.x;
ns.y = s.y;
ns.args[0] = (FastRand()&127)-63;
ns.args[1] = 0;
ns.args[2] = 50;
sparks.Push(ns);
break;
}
case SPARK_BlazeRight:
{
if ( (sparks.Size() >= sparklimit) || (FastRand() >= 128) )
break;
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Gravity;
ns.heat = s.heat;
ns.x = s.x;
ns.y = s.y;
ns.args[0] = (FastRand()&63)+63;
ns.args[1] = -29;
ns.args[2] = s.args[2];
sparks.Push(ns);
break;
}
case SPARK_BlazeLeft:
{
if ( (sparks.Size() >= sparklimit) || (FastRand() >= 128) )
break;
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Gravity;
ns.heat = s.heat;
ns.x = s.x;
ns.y = s.y;
ns.args[0] = (FastRand()&63)-128;
ns.args[1] = -29;
ns.args[2] = s.args[2];
sparks.Push(ns);
break;
}
case SPARK_Emit:
{
if ( (sparks.Size() >= sparklimit) || (FastRand() >= 64) )
break;
FireSpark ns = new("FireSpark");
ns.type = SSPARK_EmberCustom;
ns.heat = s.heat;
ns.x = s.x;
ns.y = s.y;
ns.args[0] = s.args[0];
ns.args[1] = s.args[1];
ns.args[2] = s.args[2];
sparks.Push(ns);
break;
}
case SPARK_Fountain:
{
if ( (sparks.Size() >= sparklimit) || (FastRand() >= 64) )
break;
FireSpark ns = new("FireSpark");
ns.type = SSPARK_GravityFade;
ns.heat = s.heat;
ns.x = s.x;
ns.y = s.y;
ns.args[0] = s.args[0];
ns.args[1] = s.args[1];
ns.args[2] = s.args[2];
sparks.Push(ns);
break;
}
case SPARK_Organic:
{
if ( (sparks.Size() >= sparklimit) || (FastRand() >= 128) )
break;
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Rising;
ns.x = (s.x+((FastRand()*s.args[2])>>8))&umask;
ns.y = (s.y+((FastRand()*s.args[2])>>8))&vmask;
ns.args[0] = FastRand()-128;
ns.args[1] = -1;
ns.args[2] = 255;
sparks.Push(ns);
break;
}
case SPARK_WanderOrganic:
{
if ( sparks.Size() < sparklimit )
{
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Rising;
ns.x = (s.x+(FastRand()&31))&umask;
ns.y = (s.y+(FastRand()&31))&vmask;
ns.args[0] = FastRand()-128;
ns.args[1] = -1;
ns.args[2] = 255;
sparks.Push(ns);
}
if ( FastRand()&15 == 15 ) s.x = (s.x+(FastRand()&15)-7)&umask;
if ( FastRand()&15 == 15 ) s.y = (s.y+(FastRand()&15)-7)&vmask;
break;
}
case SPARK_RandomCloud:
{
if ( sparks.Size() < sparklimit )
{
FireSpark ns = new("FireSpark");
ns.type = SSPARK_CloudY;
ns.x = (s.x+(FastRand()&31))&umask;
ns.y = (s.y+(FastRand()&31))&vmask;
ns.args[0] = (FastRand()&31)-15;
ns.args[1] = -1;
ns.args[2] = 0;
sparks.Push(ns);
}
if ( FastRand()&15 == 15 ) s.x = (s.x+(FastRand()&15)-7)&umask;
if ( FastRand()&15 == 15 ) s.y = (s.y+(FastRand()&15)-7)&vmask;
break;
}
case SPARK_Eels:
{
if ( (sparks.Size() < sparklimit) && (FastRand() < 20) )
{
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Eel;
ns.heat = s.heat;
ns.x = (s.x+(FastRand()&31))&umask;
ns.y = (s.y+(FastRand()&31))&vmask;
ns.args[0] = FastRand();
ns.args[1] = FastRand();
ns.args[2] = s.args[2];
sparks.Push(ns);
}
if ( FastRand()&15 == 15 ) s.x = (s.x+(FastRand()&15)-7)&umask;
if ( FastRand()&15 == 15 ) s.y = (s.y+(FastRand()&15)-7)&vmask;
break;
}
case SPARK_Gametes:
{
if ( (sparks.Size() < sparklimit) && (FastRand() < 20) )
{
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Gamete;
ns.heat = s.heat;
ns.x = (s.x+(FastRand()&31))&umask;
ns.y = (s.y+(FastRand()&31))&vmask;
ns.args[0] = FastRand();
ns.args[2] = s.args[2];
ns.args[3] = FastRand();
sparks.Push(ns);
}
if ( FastRand()&15 == 15 ) s.x = (s.x+(FastRand()&15)-7)&umask;
if ( FastRand()&15 == 15 ) s.y = (s.y+(FastRand()&15)-7)&vmask;
break;
}
case SPARK_CustomCloud:
{
if ( sparks.Size() < sparklimit )
{
FireSpark ns = new("FireSpark");
ns.type = SSPARK_CloudXY;
ns.x = (s.x+(FastRand()&31))&umask;
ns.y = (s.y+(FastRand()&31))&vmask;
ns.args[0] = s.args[0];
ns.args[1] = s.args[1];
ns.args[2] = s.args[3];
sparks.Push(ns);
}
// dunno why the wandering math was done like this here
s.x = (s.x+(FastRand()&7)-(FastRand()&7))&umask;
s.y = (s.y+(FastRand()&7)-(FastRand()&7))&vmask;
break;
}
case SPARK_LocalCloud:
{
if ( sparks.Size() < sparklimit )
{
FireSpark ns = new("FireSpark");
ns.type = SSPARK_CloudXY;
ns.x = (s.x+((FastRand()*s.args[2])>>8))&umask;
ns.y = (s.y+((FastRand()*s.args[2])>>8))&vmask;
ns.args[0] = s.args[0];
ns.args[1] = s.args[1];
ns.args[2] = s.args[3];
sparks.Push(ns);
}
break;
}
case SPARK_Flocks:
{
if ( sparks.Size() < sparklimit )
{
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Twirl;
ns.x = (s.x+(FastRand()&31))&umask;
ns.y = (s.y+(FastRand()&31))&vmask;
ns.args[0] = 0;
ns.args[1] = s.args[0];
ns.args[2] = s.args[1];
ns.args[3] = s.args[3];
ns.heat = s.heat;
s.args[0] = (s.args[0]+s.args[2])&255;
sparks.Push(ns);
}
// dunno why the wandering math was done like this here
s.x = (s.x+(FastRand()&7)-(FastRand()&7))&umask;
s.y = (s.y+(FastRand()&7)-(FastRand()&7))&vmask;
break;
}
case SPARK_Wheel:
{
if ( sparks.Size() >= sparklimit )
break;
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Twirl;
ns.x = s.x;
ns.y = s.y;
ns.args[0] = 0;
ns.args[1] = s.args[0];
ns.args[2] = s.args[1];
ns.args[3] = s.args[3];
ns.heat = s.heat;
s.args[0] = (s.args[0]+s.args[2])&255;
sparks.Push(ns);
break;
}
case SPARK_Sprinkler:
{
if ( sparks.Size() >= sparklimit )
break;
FireSpark ns = new("FireSpark");
ns.type = SSPARK_Sprinkle;
ns.x = s.x;
ns.y = s.y;
ns.heat = s.heat;
ns.args[0] = s.args[0];
ns.args[1] = s.args[1];
ns.args[2] = s.args[2];
ns.args[3] = 2;
ns.heat = s.heat;
s.args[0] = (s.args[0]+s.args[3])&255;
sparks.Push(ns);
break;
}
// stars are their own thing
case SPARK_Stars:
{
PutPixel(s.x,s.y,s.args[1]);
break;
}
// bolt types
case SPARK_LineLightning:
{
if ( !s.heat ) break;
if ( s.args[2] > 0 )
{
s.args[2]--;
DrawBolt(s.x,s.y,s.args[0],s.args[1],s.heat,s.heat);
}
else if ( FastRand() >= uint(s.args[3]) )
s.args[2] = 1+FastRand()&5;
break;
}
case SPARK_RampLightning:
{
if ( !s.heat ) break;
if ( s.args[2] > 0 )
{
s.args[2]--;
DrawBolt(s.x,s.y,s.args[0],s.args[1],s.heat,s.heat>>3);
}
else if ( FastRand() >= uint(s.args[3]) )
s.args[2] = 1+FastRand()&5;
break;
}
case SPARK_SphereLightning:
{
if ( FastRand() < uint(s.args[3]) ) break;
uint ang = FastRand();
uint rad = s.args[2];
uint sdispx = (rad*phasetable[ang])>>8;
uint sdispy = (rad*phasetable[(ang+64)&255])>>8;
DrawBolt(s.x,s.y,sdispx-rad/2,sdispy-rad/2,s.heat,s.heat>>2);
break;
}
// and now the spawned ones
case SSPARK_Ember:
{
if ( (s.heat -= 5) > 0 )
{
PutPixel(s.x,s.y,s.heat);
MoveSpark(s);
}
else sparks.Delete(i--);
break;
}
case SSPARK_EmberCustom:
{
if ( (s.heat -= s.args[3]) > 0 )
{
PutPixel(s.x,s.y,s.heat);
MoveSpark(s);
}
else sparks.Delete(i--);
break;
}
case SSPARK_EmberCustom2: // unused
{
if ( (s.heat -= s.args[2]) > 0 )
{
PutPixel(s.x,s.y,s.heat);
MoveSpark(s);
}
else sparks.Delete(i--);
break;
}
case SSPARK_Gravity:
{
if ( (s.args[2] -= 1) > 0 )
{
PutPixel(s.x,s.y,s.heat);
MoveSpark(s);
s.args[1] = min(s.args[1]+3,127);
}
else sparks.Delete(i--);
break;
}
case SSPARK_GravityFade:
{
if ( (s.heat -= s.args[3]) > 50 )
{
PutPixel(s.x,s.y,s.heat);
MoveSpark(s);
if ( phase&1 ) s.args[1] = min(s.args[1]+3,127);
}
else sparks.Delete(i--);
break;
}
case SSPARK_Rising:
{
if ( (s.args[2] -= 3) > 190 )
{
PutPixel(s.x,s.y,s.args[2]);
MoveSparkTwo(s);
}
else sparks.Delete(i--);
break;
}
case SSPARK_CloudY:
{
if ( (s.args[2] += 4) < 250 )
{
PutPixel(s.x,s.y,s.args[2]);
MoveSparkTwo(s);
}
else sparks.Delete(i--);
break;
}
case SSPARK_CloudXY:
{
if ( (s.args[2] += 4) < 250 )
{
PutPixel(s.x,s.y,s.args[2]);
MoveSpark(s);
}
else sparks.Delete(i--);
break;
}
case SSPARK_Eel:
{
if ( (s.args[2] -= 1) > 0 )
{
PutPixel(s.x,s.y,s.heat);
MoveSpark(s);
}
else sparks.Delete(i--);
break;
}
case SSPARK_Gamete:
{
if ( (s.args[2] -= 1) > 0 )
{
PutPixel(s.x,s.y,s.heat);
uint sawtooth = 127&(s.args[0]+=7);
if ( sawtooth>63 ) sawtooth = 127-sawtooth;
MoveSparkAngle(s,255&(sawtooth+s.args[3]));
}
else sparks.Delete(i--);
break;
}
case SSPARK_Twirl:
{
if ( (s.args[2] -= 1) > 0 )
{
PutPixel(s.x,s.y,s.heat);
int tempspeedx = signedtable[(s.args[1]+64)&255];
int tempspeedy = signedtable[s.args[1]];
// fixed point fuckery happening here
uint wtf = s.args[0]+(s.args[1]<<8)+(s.args[3]<<4);
s.args[0] = wtf&255;
s.args[1] = (wtf>>8)&255;
MoveSparkXY(s,tempspeedx,tempspeedy);
}
else sparks.Delete(i--);
break;
}
case SSPARK_Sprinkle:
{
if ( (s.args[2] -= 1) > 0 )
{
PutPixel(s.x,s.y,s.heat);
int tempspeedx = -128+phasetable[(s.args[1]+64)&255];
int tempspeedy = s.args[1];
s.args[0] = (s.args[0]+s.args[3])&255;
MoveSparkXY(s,tempspeedx,tempspeedy);
}
else sparks.Delete(i--);
break;
}
}
}
}
// handle stars
private void PostDrawSparks()
{
uint sparkdest;
if ( !bHasStars ) return;
bool bFoundStar = false;
for ( int i=0; i<sparks.Size(); i++ )
{
if ( sparks[i].type != SPARK_Stars ) continue;
bFoundStar = true;
uint dimstar = GetPixel(sparks[i].x,sparks[i].y);
sparks[i].args[1] = dimstar;
if ( dimstar < 38 )
PutPixel(sparks[i].x,sparks[i].y,sparks[i].args[0]);
}
if ( !bFoundStar ) bHasStars = false;
}
}

View file

@ -0,0 +1,31 @@
/*
FireTexture reverse engineering project
Lots of disassembly and reverse engineering work all to make some fancy
procedural textures show up in GZDoom. I'm just that insane.
Copyright (c)2023 Marisa the Magician, UnSX Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
// include this file in your projects
#include "./ft_baseclass.zsc"
#include "./ft_math.zsc"
#include "./ft_pixel.zsc"
#include "./ft_spark.zsc"

View file

@ -0,0 +1,19 @@
Copyright (c) 2023 Marisa the Magician, UnSX Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,34 @@
// let's test that this madness works, then
Class FireTextureTestHandler : StaticEventHandler
{
ui FireTexture testme;
ui TextureID testtex;
ui double timers[3];
override void UiTick()
{
if ( testme && testme.Initialized() )
{
timers[0] = MSTimeF();
testme.Tick();
timers[1] = MSTimeF();
testme.Render();
timers[2] = MSTimeF();
}
}
override void RenderOverlay( RenderEvent e )
{
if ( !testme ) testme = FireTexture.Load("Pad_FX.ftx");
if ( !testme.Initialized() ) return;
if ( !testtex.IsValid() ) testtex = TexMan.CheckForTexture("Pad_FX");
Screen.DrawTexture(testtex,false,8,8,
DTA_ScaleX,CleanXFac_1,DTA_ScaleY,CleanYFac_1);
Screen.DrawText(NewSmallFont,Font.CR_RED,16+testme.GetWidth()*CleanXFac_1,16,
"← procedural texture running in real time\n no japes here",
DTA_ScaleX,CleanXFac_1,DTA_ScaleY,CleanYFac_1);
Screen.DrawText(NewSmallFont,Font.CR_RED,16,16+testme.GetHeight()*CleanYFac_1,
"tick: "..(timers[1]-timers[0]).."ms\nrender: "..(timers[2]-timers[1]).."ms",
DTA_ScaleX,CleanXFac_1,DTA_ScaleY,CleanYFac_1);
}
}