physics of explosions (simplified)

Started by RemiD, November 27, 2024, 11:33:19

Previous topic - Next topic

RemiD

hi :)

when we need to code something, it is always a good idea to understand the basics, in this case the physics of explosions.

i am not trying to make realistic explosions, but rather convincing explosions.

my first procedure used an exponential increase formula for the increase of the size / spread of the explosion,
explosion_size = explosion_size * 2

but apparently this is incorrect !

an explosion speed is limited by the speed of waves in the air (gazeous materials), in the water (liquid materials), in the earth (solid materials).

so the first thing we can conclude is that the particles of gazs, the particles of liquids, the particles of solids, will not be spread the same way, but will be spread at a maximum speed depending on the medium.

but to keep it simple, we can consider the spread of small particles in the air (gazs, dusts, droplets) which is maximum 343m per second (from what i have read on the interweb)
therefore, assuming 1pixel is one meter in 2d, or 1unit is one meter in 3d,
at 60fps : 343/1000*60 = 20.58pixels per frame
at 30fps : 343/1000*30 = 10.29pixels per frame

an impressive example of an explosion captured in video :
https://www.youtube.com/watch?v=NFjDq-Rsyjo

however, in practice, i prefer the 'physics' and appearance of an exponential increase for an explosion, so i will keep it this way in my game.
;D

interesting to know nonetheless...

Naughty Alien

..by playing these new 'realistic' games, in terms of visuals and physics, i discovered that they are actually...boring..what i mean is, in game it should be funny (at least thats how it works for me)..for example, in L4D2, that i play often with gang of jokers, there is a custom map (Circus of some kind), where you can have custom weapon (think of it as axe). So, typically, when you use normal weapons and kick zombie out, its more/less 'realistic'..fine..but when i use this custom weapon and hit zombie, it fly 7 days from sunday with ragdoll physics which makes it really funny, especially when hit other dude with zombie body from such distance..if whole thing was physically as real as can be, im confident, it will be as boring as it can be, very very quickly..so,,make it look funny and interesting ;D

Midimaster

#2
This video does not show a typical explosion. This situation had additional a fire, where materials burn "slowly"

I would do explosion with a 3D-vector:

the energy of the explosion move the object in X and Z, but also in Y direction

In all three directions the speed has it's maximum at the beginning
The start speed depends on the weight of the object related to the energy of the explosion.

While X-Movement and Z-Movement are linear (only braked by air resistance), the Y-Movement is a movement into the sky and is from the very first moment braked by the gravity of the object.

In general, you can say, that explosion objects make the same way as a ball thrown into the sky.

A "cheap" way to simulate Y is working with an AddY:Double, which represents the amount of way that is added/subtracted in a certain time (e.g. 16msec)

Each tick this AddY needs to be reduced by Gravity:Double, which represents the Gravity.


Runnable Example 1: One object
Graphics 800,600
Global Y:Double=100, AddY:Double=10.0, Gravity:Double=0.2

Repeat
    Cls
    DrawLine 0,430,800,430
    DrawOval 400, 400-y, 20,20
    Y = Y + AddY
    AddY = AddY -Gravity
    If y<=0 Then y=0    ' crash on the floor
    Flip 1
Until AppTerminate()


And here is the same with 100 chunks:
(the example code is better than the GIF-animation shows)

Explosion.gif

(runnable example)
Graphics 800,600

Global  Gravity:Double=0.2, Start:Int

For Local i:Int=0 To 99
    New TChunk i
Next
TChunk.ResetAll
Print "ready"
Repeat
    Cls
    DrawLine 0,430,800,430
    If Start=False
        DrawText "PRESS E to start Explosion", 100,100
    Else
        DrawText "PRESS R to reset Chunks", 100,100
   
    EndIf
    If KeyHit(KEY_E) Start=True
    If KeyHit(KEY_R) TChunk.ResetAll
    TChunk.DrawAll
    Flip 1
Until AppTerminate()


Type TChunk
    Global List:TList = New TList
    Field Y:Double=100, X:Double, AddY:Double=5.0, AddX:Double, Size:Double
   
    Method New(void:Int)
    Print void
        List.AddLast Self
    End Method 
   
   
    Function ResetAll()
        Start=False
        For Local Chunk:TChunk =EachIn List
            Chunk.Reset
        Next
    End Function
   
   
    Method Reset()
        Local Dir:Double =Rand(360)
        Size = Rnd(5,20)
        AddY = 50.0/Size*Sin(dir)       
        AddX = (Rnd(2)-1) * 50.0/Size*Cos(dir)
        X    = 0
        Y    = 100       
    End Method
   

    Function DrawAll()
        Print list.count()
        For Local Chunk:TChunk =EachIn List
            Chunk.Draw
        Next
    End Function
   
   
    Method Draw()
        'Print x + " " + y + " " + size
        DrawOval 400-x, 400-y, size,size
        If Start=0 Return
        X = X + AddX
        Y = Y + AddY
        AddY = AddY -Gravity
        AddX = AddX* 0.99
        If Y<-size
            Y = -size
            AddX = 0
        EndIf
    End Method
End Type
...back from North Pole.

Baggey

I don't know why but i had to change this line to get it to RUN? I like Demo's Like this

Field Y:Int=100, X:Int, AddY:Double=5.0, AddX:Double, Size:int
Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 32GB ram  2x1TB SSD and NVIDIA Quadro K1200 on 2 x HP Z24's . DID Technology stop! Or have we been assimulated!

Windows10, Parrot OS, Raspberry Pi Black Edition! , ZX Spectrum 48k, C64, Enterprise 128K, The SID chip. Im Misunderstood!

Midimaster

Quote from: Baggey on November 27, 2024, 17:20:43I don't know why but i had to change this line to get it to RUN? I like Demo's Like this

Field Y:Int=100, X:Int, AddY:Double=5.0, AddX:Double, Size:int
Kind Regards Baggey
It is not a  good idea to replace them with INTEGERs... try FLOATs for the coordinates X , Y and Size
...back from North Pole.

RemiD

#5
@Midimaster >>
your example shows more the parts of something which has exploded. (it looks convincing btw)

i was more talking about the 'blast' of an explosion, and how it would spread and how it would damage the nearby entities.

on the game that i am currently working on (alien dropout remake, see worklog), there are 2 kinds of explosions :
-one i would call a circular / spherical explosion with a blast that spreads in all directions, with a small spread distance because it is a weak explosion and there is the air resistance (to simplify).
-one i would call horizontal explosion with a blast that spreads horizontally (on the x axis), and is so powerful that it spreads everywhere along all the ground.

in both cases, we can say that 2 kinds of damages happen :
the damage caused by the impact of the rocket, or by the impact of the bomb, and by the parts which are projected by the explosion.
and
the damage caused by the blast.

so i have tried to consider these 2 types of damages.

and to spread particles after an impact / explosion but also to spread particles after the blast has reached an entity.

my implementation for these 2 kinds of explosions / blasts :
http://rd-stuff.fr/blitz3d/2d-explositions-test-20241127.mp4

of course there is not only one way to do it...

Baggey

As we say there are always more than one way's to skin a cat. This is exactly why Code example's are great! To learn new approaches and Techniques!

@RemiD I think the explosions you've done are 'Formidable!'

Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 32GB ram  2x1TB SSD and NVIDIA Quadro K1200 on 2 x HP Z24's . DID Technology stop! Or have we been assimulated!

Windows10, Parrot OS, Raspberry Pi Black Edition! , ZX Spectrum 48k, C64, Enterprise 128K, The SID chip. Im Misunderstood!

DaiHard

I'd expect the blast wave of an explosion to propagate as a wave (!), and I'd expect the speed of that wave to depend on the properties (esp density) of the thing it's propagating through. I'm not sure how that would slow - maybe it doesn't! Think of waves on the sea, or even ripples on a pond.

The other thing you need to think about is the power/force of the wave: this will dissipate with an inverse square law: Px = Pi / D^2  (the power at point x is the power at the initial point  divided by the square of the distance from i to x). This will mean the destruction will (quite quickly peter out - at which point you can stop drawing the "damage front".

RemiD

Quotethe blast wave of an explosion to propagate as a wave (!), and I'd expect the speed of that wave to depend on the properties (esp density) of the thing it's propagating through.
yes

QuoteI'm not sure how that would slow - maybe it doesn't!
if we want to be precise, an explosion on the ground, near a lake, would propagate in the ground, in the air, in the water, in 3 different ways and at 3 different speeds.

it seems that the more dense (harder) the material is, the faster the wave propagates, and the less dense (softer)  the material is, the slower the wave propagates.

but it is too complex (and unecessary) to consider all of this for a game...

Quotethe power/force of the wave: this will dissipate with an inverse square law: Px = Pi / D^2
ok. ???

Derron

#9
> but it is too complex (and unecessary) to consider all of this for a game...

It is not complicate if you simplify your "environment".
Just imagine you simplify your world into a "cube grid"... you can easily check in which "physical environment" a point currently is ("in a fog, in a stone, in water").

your "explosion waves" are sound waves (or name them sonic waves..).
- https://en.wikipedia.org/wiki/Speed_of_sound - if you want to read up further a bit



> it seems that the more dense (harder) the material is, the faster the wave propagates

It does not just seem to be this way... it _is_ this way. This is because of the "how" sound waves "move" (see wiki page above - but did not read the article itself, just scanned over it).


https://stickmanphysics.com/stickman-physics-home/unit-10-waves/sound-waves/
Has some example velocities ... and also emphasizes to you, that a vacuum does not transport sound.


This is is why explosions in space-fights should be silent ... or maybe you remember the poster tagline of the original Alien movie: In space, no one can hear you scream.
... this is why.

And maybe you also have heard of stuff like "the fog swallowed the noise" ...



Ok, so assume you collect some "speeds" for sound you can assign your "grid cells" to be of either one of these materials. The benefit is then, that eg in a "thief"-game fog or misty weather allows you to move louder or get not heard "so far".
The more "coarse" your grid becomes, the better the performance (but the worse the "realism").


PS: For things like "can a unit see me" people also take into consideration "what is between", for movement stuff like "damping" (different "ground" etc) is incorporated into formulas, ... there people integrate it without much "doubts" - yet for "smelling" (hello dear Fart-Zwart dwarf!) and "hearing" things can surely also be simplified enough without sacrificing it "completely".

bye
Ron

RemiD

Quoteyour "explosion waves" are sound waves (or name them sonic waves..).
i don't think so, a blast can propagates in the air (gaz medium), but also in the water (liquid medium), and also in the earth (solid medium), and these waves would propagates in a different way (with different effects) and at a different speeds depending on the medium.


QuoteThis is is why explosions in space-fights should be silent
exactly, but movies in space would be less captivating without nice sound effects ahah :))


i have to finish the code of the game in 2 days. so i won't bother to implement any of that. but interesting discussion nonetheless.  :)

Derron

So you talk about compression waves - or "blast" ?
If so I have to stand corrected, that my previous post is not much of help :)

Compression waves are for example:
https://en.wikipedia.org/wiki/Longitudinal_wave
and
https://en.wikipedia.org/wiki/Transverse_wave

with the second variant being what I would kinda use for a "god fist slamming into ground" effect


I assume if one knows all these formulas and "simplifies" them, they can use them nicely in some shaders for visual effects (gun blast effect etc). 


bye
Ron

RemiD

i researched this topic just by curiosity, and it is interesting to understand the physics, but at the end of the day, what matters is that it looks convincing and that it does not consume too many ressources (no too many particles created, in this case).

Baggey

Ive been looking into "Mechanics" which has a lot of interesting Math that could be easily adapted to games.

Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 32GB ram  2x1TB SSD and NVIDIA Quadro K1200 on 2 x HP Z24's . DID Technology stop! Or have we been assimulated!

Windows10, Parrot OS, Raspberry Pi Black Edition! , ZX Spectrum 48k, C64, Enterprise 128K, The SID chip. Im Misunderstood!

Matty

Greetings folks....

I saw this thread before breakfast this morning and decided to put something simple together. I have other explosion code which is in Blitz3d that I wrote over a decade ago that produces very good output and the results can be downloaded from my matty77.itch.io page but for a simple explosion generator with fire, sparks and smoke here's a sample in C# code but can easily be rewritten:



Textures:



C#SDL2 Game Engine to use it with: Link

(Explosion code in Engine cs file link: cs file)

/////////////////// Very Simple Explosion Code ////
/// This is the relevant C# code for the explosions
/// It utilises my SDL2 C# engine which isn't included
/// here but it can be downloaded from my matty77.itch.io
/// link....
///////////////////////////////////////////////////
///////////////////Class Definition////////////////
///these should translate fairly easily to any other object
///oriented language
class Fire
{
public static float ESCALE = 1.5f;
public static int maxfire = 2000;
//note - it's been a few years since I've done any real
//c# coding so I might have forgotten how to do this properly.
public static Fire[] fires = new Fire[maxfire];

public float x; //position in space // fire doesn't alter its position once set so no vx,vy,vz
public float y;
public float z;
public float scale; //scale
public float alpha; //alpha fade
public float dalpha;
public float dscale;
public float time; //time from 0 to n of the fire element
//I really should use the same object for all these fire, smoke, spark but
//this will work too.

public Fire() //constructor? Can't remember how this works anymore...
{

}

public static void init() //must call this first or else will crash
{
for(int i=0;i<maxfire;i++)
{
fires[i] = new Fire();
}
}

public static void create(float x, float y, float z)
{
for(int i=0;i<maxfire;i++)
{
if(fires[i].time==0)
{
fires[i].time = 0.01f;
fires[i].x = x * 10f;
fires[i].y = y * 10f;
fires[i].z = z + 100f;
fires[i].scale = 0.3f + (float)Functions.rand(0,100)/300f;
fires[i].scale *= 0.3f;
fires[i].alpha = 0.06f;
fires[i].dalpha = 0.1f + (float) Functions.rand(-100,100) / 300f;
fires[i].dscale = 0.1f*(float)Functions.rand(-50,150)/150f;
break;
}
}
}

public static void draw(Game game)
{
//sort the fires first.....
Fire[] tmpfires = new Fire[maxfire];
int count = 0;
for(int i=0;i<maxfire;i++)
{
tmpfires[i] = new Fire();
if(fires[i].time>0 && fires[i].alpha>0 && fires[i].scale > 0.01 && fires[i].z > 0)
{
tmpfires[i] = fires[i];
count++;
}
}
for(int i=0;i<count;i++)
{
for(int j=0;j<count;j++)
{
if(tmpfires[j].z > tmpfires[i].z)
{
Fire tmpf = tmpfires[i];
tmpfires[i] = tmpfires[j];
tmpfires[j] = tmpf;
}
}
}
for(int i=0;i<count;i++)
{
if(tmpfires[i].time>0 && tmpfires[i].alpha>0 && tmpfires[i].scale > 0.01 && tmpfires[i].z > 0)
{
float sx = tmpfires[i].x / (tmpfires[i].z/Fire.ESCALE);
float sy = tmpfires[i].y / (tmpfires[i].z/Fire.ESCALE);
int screenx = 800 + (int)sx;
int screeny = 450 + (int)sy;
RenderList.drawanimimage(game,game.images[Resources.fire].texture,screenx,screeny,0,Fire.ESCALE*tmpfires[i].scale / tmpfires[i].z,Fire.ESCALE*tmpfires[i].scale / tmpfires[i].z,0,1,false,tmpfires[i].alpha,Resources.fire);
}
}
}

public static void update()
{
for(int i=0;i<maxfire;i++)
{
if(fires[i].time>0)
{
fires[i].time+=1.0f*0.75f;
if(fires[i].time<8)
{
fires[i].scale*=1.4f;
}
if(fires[i].time>16)
{
fires[i].scale*=0.6f;
}
if(fires[i].scale<=0.01)
{
fires[i].time = 0;
}
if(fires[i].time<9)
{
fires[i].alpha = fires[i].time / 6.0f;
}
if(fires[i].time>20)
{
fires[i].alpha = fires[i].alpha * 0.65f;
}
if(fires[i].alpha < 0.05)
{
fires[i].alpha = 0;
}
if(fires[i].scale>0.1){fires[i].scale = fires[i].scale + fires[i].dscale;}
if(fires[i].alpha > 0){fires[i].alpha+=fires[i].dalpha;}
if(fires[i].alpha>1)
{
fires[i].alpha = 1;
}
if(fires[i].time > 100)
{
fires[i].time = 0;
}
}
}
}

}

class Smoke
{
public static int maxsmoke = 2000;
public static Smoke[] smokes = new Smoke[maxsmoke];
public float x; //position in space // fire doesn't alter its position once set so no vx,vy,vz
public float y;
public float z;
public float vx;
public float vy;
public float vz;
public float ay;
public float scale; //scale
public float alpha; //alpha fade
public float time; //time from 0 to n of the fire element
public float dscale;
public float dalpha;
//I really should use the same object for all these fire, smoke, spark but
//this will work too.

public Smoke() //constructor? Can't remember how this works anymore...
{

}

public static void init() //must call this first or else will crash
{
for(int i=0;i<maxsmoke;i++)
{
smokes[i] = new Smoke();
}
}

public static void create(float x, float y, float z)
{
for(int i=0;i<maxsmoke;i++)
{
if(smokes[i].time==0)
{
smokes[i].time = 0.01f;
smokes[i].x = x * 20;
smokes[i].y = y * 20;
smokes[i].z = z + 100f;
smokes[i].vx = (float)Functions.rand(-30,30) / 100.0f;
smokes[i].vy = 500f*(float)Functions.rand(-100,-20) / 500.0f;
smokes[i].vz = (float)Functions.rand(-30,30) / 100.0f;
smokes[i].ay = 0;//0.05f;
smokes[i].scale = 30;
smokes[i].alpha = 0.06f;
smokes[i].dscale = (float)Functions.rand(20,100)/100f;
smokes[i].scale*=smokes[i].dscale*3.5f;
smokes[i].dalpha = (float)Functions.rand(20,100)/100f;

break;
}
}
}

public static void draw(Game game)
{
for(int i=0;i<maxsmoke;i++)
{
if(smokes[i].time>0 && smokes[i].alpha>0 && smokes[i].scale > 0.01f && smokes[i].z > 0)
{
float sx = smokes[i].x / (smokes[i].z/Fire.ESCALE);
float sy = smokes[i].y / (smokes[i].z/Fire.ESCALE);
int screenx = 800 + (int)sx;
int screeny = 450 + (int)sy;
RenderList.drawanimimage(game,game.images[Resources.smoke].texture,screenx,screeny,0,Fire.ESCALE*smokes[i].scale / smokes[i].z,Fire.ESCALE*smokes[i].scale / smokes[i].z,0,0,false,smokes[i].alpha,Resources.smoke);
}
}
}

public static void update()
{
for(int i=0;i<maxsmoke;i++)
{
if(smokes[i].time>0)
{
smokes[i].time+=1.0f;
if(smokes[i].time>15)
{
smokes[i].scale*=1.0125f;
}
if(smokes[i].scale > 13.0)
{
smokes[i].scale = 13.0f;
}
if(smokes[i].scale<=0.01)
{
smokes[i].time = 0;
}
if(smokes[i].time<20)
{
smokes[i].alpha = smokes[i].time / 5.0f;
}
if(smokes[i].time>75)
{
smokes[i].alpha = smokes[i].alpha * 0.65f;
}
if(smokes[i].alpha < 0.05)
{
smokes[i].alpha = 0;
}
if(smokes[i].alpha>0){smokes[i].alpha*=smokes[i].dalpha;}
if(smokes[i].alpha>1)
{
smokes[i].alpha = 1;
}
smokes[i].alpha = 0.06f;
if(smokes[i].time>100)
{
smokes[i].time = 0;
}
smokes[i].x+=smokes[i].vx;
smokes[i].y+=smokes[i].vy;
smokes[i].z+=smokes[i].vz;
smokes[i].vy+=smokes[i].ay;
}
}
}
}

class Spark
{
public static int maxspark = 2000;
public static Spark[] sparks = new Spark[maxspark];
public float x; //position in space // fire doesn't alter its position once set so no vx,vy,vz
public float y;
public float z;
public float vx;
public float vy;
public float vz;
public float ay;
public float scale; //scale
public float alpha; //alpha fade
public float time; //time from 0 to n of the fire element
//I really should use the same object for all these fire, smoke, spark but
//this will work too.

public Spark() //constructor? Can't remember how this works anymore...
{

}

public static void init() //must call this first or else will crash
{
for(int i=0;i<maxspark;i++)
{
sparks[i] = new Spark();
}
}

public static void create(float x, float y, float z)
{
for(int i=0;i<maxspark;i++)
{
if(sparks[i].time==0)
{
sparks[i].time = 0.01f;
sparks[i].x = x;
sparks[i].y = y;
sparks[i].z = z + 100f;
sparks[i].vx = 0.37f*(float)Functions.rand(-150,150) / 5.0f;
sparks[i].vy = 0.37f*(float)Functions.rand(-260,-5) / 5.0f;
sparks[i].vz = 0.37f*(float)Functions.rand(-20,20) / 5.0f;
sparks[i].ay = 0.2f;
sparks[i].scale = 1;
sparks[i].alpha = 0.06f;
break;
}
}
}

public static void draw(Game game)
{
for(int i=0;i<maxspark;i++)
{
if(sparks[i].time>0 && sparks[i].alpha>0 && sparks[i].scale > 0.01f && sparks[i].z > 0)
{
float sx = sparks[i].x / (sparks[i].z/Fire.ESCALE);
float sy = sparks[i].y / (sparks[i].z/Fire.ESCALE);
int screenx = 800 + (int)sx;
int screeny = 450 + (int)sy;
RenderList.drawanimimage(game,game.images[Resources.spark].texture,screenx,screeny,0,Fire.ESCALE*sparks[i].scale / sparks[i].z,Fire.ESCALE*sparks[i].scale / sparks[i].z,0,1,false,sparks[i].alpha,Resources.spark);
}
}
}
//(as I said..I'm a bit rusty...)
public static void update()
{
for(int i=0;i<maxspark;i++)
{
if(sparks[i].time>0)
{
sparks[i].time+=1.0f;
/*
if(sparks[i].time>20)
{
sparks[i].scale*=0.9f;
}
if(sparks[i].scale<=0.01)
{
sparks[i].time = 0;
}
if(sparks[i].time<10)
{
sparks[i].alpha = sparks[i].time / 8.0f;
}
if(sparks[i].time>35)
{
sparks[i].alpha = sparks[i].alpha * 0.9f;
}
*/
sparks[i].alpha = 1;
sparks[i].scale = 1;
if(sparks[i].time > 100)
{
sparks[i].time = 0;
}
if(sparks[i].alpha < 0.05)
{
sparks[i].alpha = 0;
}
if(sparks[i].alpha>1)
{
sparks[i].alpha = 1;
}
if(sparks[i].time > 70)
{
sparks[i].alpha = 1f - (float)(sparks[i].time - 70)/40.0f;
}
sparks[i].x+=sparks[i].vx;
sparks[i].y+=sparks[i].vy;
sparks[i].z+=sparks[i].vz;
sparks[i].vy+=sparks[i].ay;
if(sparks[i].y > 5)
{
sparks[i].time = 0;
}
if(sparks[i].z<10)
{
sparks[i].time = 0;
}
}
}
}
}


////////////////////
///inside your main game loop
///include the following
///and it will draw and animate a simple explosion
///that lasts for 100 frames
///requires 3 images, one for fire, one for smoke and one for sparks....
///
//update smoke, fire, flame stuff in here and draw it....
Fire.update();
Smoke.update();
Spark.update();
Fire.draw(game);
Spark.draw(game);
Smoke.draw(game);
gametime++;
gametime%=100;
if(gametime > 20 && gametime<55 && gametime % 5 == 0) //fire
{
for(int i=0;i<4;i++)
{
float fx = (float) Functions.rand(-500,500) / 4f;
float fy = (float) Functions.rand(-500,500) / 4f;
float fz = (float) Functions.rand(10,100) / 2f;
Fire.create(fx,fy,fz);
}
}
if(gametime>2 && gametime < 6) //sparks
{
for(int i=0;i<50;i++)
{
float fx = (float) Functions.rand(-40,40) / 4f;
float fy = (float) Functions.rand(-40,40) / 4f;
float fz = (float) Functions.rand(10,15) / 2f;
Spark.create(fx,fy,fz);
}
}
if(gametime>30 && gametime<60) //smoke
{
for(int i=0;i<5;i++)
{
float fx = (float) Functions.rand(-350,350) / 4f;
float fy = (float) Functions.rand(-300,300) / 4f;
float fz = (float) Functions.rand(10,75) / 2f;
Smoke.create(fx,fy,fz);
}
}