This follows the classic Stable Fluids paper by Jos Stam. Use your mouse.
A few things that would make this better:
- More optimization
- Higher resolution (if possible)
- monotonic cubic interpolation for advection (likely too expensive)
- MAC grid (NOPE - saves a lot of time to backtrace all quantities from cell centers instead. A MAC grid would mitigate some of the boundary artifacts here, but it's just too slow.)
- vorticity confinement (expensive, but probably the next most important thing)
- better smoke vis - bilinear interpolation? (almost certainly too expensive)
- higher resolution for smoke than for velocity?
- other forces: gravity / heat / etc.
- better code quality (it's kind of a mess, sorry)
V0.1
- Alternating Gauss-Seidel direction
- Control number of pressure iterations with left/right
- Some optimizations thanks to @freds72 (I'm loving the hack for
newgrid
)
V0.2
- @freds72 pointed out a few more optimizations and bugs (thanks!)
- Merged all advection together and integrated density decay - saves tons of CPU.
- Got rid of velocity diffusion - this also saved lots of CPU.
- Lots more micro-optimizations (pre-multiply dt, split up multiple assignments, treat fg/bg colors as a single unit, etc.)
- Increased resolution due to all the optimizations! Formerly, at 30fps, this could do 20x20 at 9 pressure iterations. Now it can do 24x24 at 10 iterations.
- Smoke gets introduced a little slower and fades a little slower.
V0.3
- Sacrificed a little CPU (and a pressure iteration) for some tracer particles.
V0.4
- Fixed weird redeclaration bug that broke this on newer PICO-8 versions
Bonus GIF because it's neat: two vortex pairs approach each other, collide, vortices re-pair and move to the sides.
Very nice, @luchak, but I think you can go full 128x128 instead of blocks. I did a fireball routine years ago for Pico-8 that ran in 128x128 and used a very similar effect to what you are doing here - and it ran quickly requiring no arrays.
@dw817 I took a look at your fireball - looks neat! It's doing something very different though. Yours looks to be spawning pixels near the cursor and somewhat-randomly evolving them over time. This is doing a fluid sim under the hood, treating the smoke as suspended in a box of air that fills most of the screen. Doing things this way gives everything some momentum, keeps the flow inside domain boundaries, allows phenomena like vortex formation, etc. There are doubtless plenty of remaining opportunities for optimization and corner-cutting, but I'd be shocked to see this running above 40x40 at 30fps.
Thanks, @luchak !
I came across that while watching a video of a fireplace. I realized I didn't have the math background to track ever flicker of the flame - so how ?
I thought about it and said well what if I had a data table as big as the screen ? I could use real pixels to store data. And I did and Fireball is the result.
Using the same principle I wrote a variation of it, once again using the display screen as data storage called, "Component Invaders."
Hmm ! Well now that sounds interesting, @luchak. Let me play with your smoke for a bit, watch its motion, and see if I can't develop something similar using only pixels for data - and see if I can get it above 40x40 pixels to a nice 128x128 field.
@dw817 Give it a shot! I'm curious to see what you come up with.
To help illustrate one aspect of what's going on here, I've uploaded a version where you can change the number of pressure solve iterations with the left and right arrows. If you take it down to zero, the fluid is fully compressible, and there's absolutely no billowing or vorticity, just pure linear momentum (and diffusion). It also looks pretty weird. As you start to get to around 4 iterations, the more fluid-like swirling-type behaviors start to return, but there's still a lot of leakage through the walls. At higher numbers of iterations, the vortices are larger, form earlier, and respect the boundaries better.
The thing this solve is really trying to achieve is making sure that the same amount of fluid is entering and exiting each cell (the incompressibility constraint).
You can mimic some of this behavior by introducing noise in the right way - search "curl noise" for one example - but generally, even with noise, you want to respect incompressibility, because that's key to swirling/billowing/vortices/etc.
Also, rough time breakdown at 10 pressure iterations:
- Pressure solve: 37%
- Advection (velocity and smoke): 30%
- Diffusion and smoke decay: 13%
- Drawing: 9%
- Pressure gradient subtraction: 4%
- Divergence computation: 3%
- Other (mouse/keyboard interaction, rounding errors, etc.): 3%
- Idle: 1%
My best pressure solve optimization idea at this point is to pack two cells into the high/low 16 bits of a single number, but I'd need to be very careful about overflow, and needing to mask after shifts could eat some of the possible gains. Might work, might not. I'm even less sure that 3x10 or 4x8 would work at all, that's probably just not enough precision.
If someone else wants to take a crack at this you're welcome. I =AM= working on this though. It's going to be interesting in that I must use a triple-identity per pixel.
I've never done this sort of thing before but I think it can be done.
Interesting! Like I said, I'm very curious where you end up.
Two thoughts
- 4 bits isn't a lot of space for x velocity, y velocity, and smoke density.
- Pressure is a very nonlocal effect, so I'd be surprised if there were a purely local answer here. Part of the reason lots of pressure iterations are necessary is so that faraway cells can "talk" to each other. Take a look at the attached GIF for a slightly-slowed-down version of the pressure solve running on a square of down-pointing velocity introduced into still air (with flow turned off). Conserving mass requires finding ways for the fluid to flow back to where it started, which results in vortex formation! Those circles that you see at the start represent fluid being created or destroyed at each point, which we don't want to allow.
It's gonna be sorta like that @luchak. By triple I mean [][][], not PGET(). The drag the mouse across is gonna be interesting to code I think.
I think this is as good as I can get, @luchak. Does use 128x128 pixel mode. I'm not understanding your code so I'm just basing this off what I am seeing in your display.
... ???
Well that's puzzling. If you run it in immediate mode you get much better results (the smoke follows the trail) whereas online it does not follow the mouse movements at all.
That looks nice! It's a different thing than what I was going for, but it does look neat. I did get velocities from the mouse when running locally but not here on the BBS, yeah.
If you're curious about what my code is doing (or if anyone is, really), it's basically just following https://damassets.autodesk.net/content/dam/autodesk/www/autodesk-reasearch/Publications/pdf/realtime-fluid-dynamics-for.pdf .
Thanks, @luchak.
But yeah that's gotta be an error on @zep's part that it runs differently online than offline. Oh I hated when I ran into this back in GFA.
I ever decided if I get around to writing my own Fantasy Console that I would NEVER use 2-different engines for compilation. Just one, to make an EXE, fire and forget, no chance of it running differently offline or online.
So now I have the lovely task of determining just what the heck runs offline but not online. Wow.
Gold star for you cause your smoke really does filter out better.
@luchak very nice and fluid(!). just a thought, maybe you could try marching squares or just rounded connected sprites to make up for the low resolution?
@dw817 nice particles! for some (maybe good) reason the pointer doesn't lock on the bbs, so you're getting zeroes as mouse deltas. hopefully it can be checked with peek(24365)?? in any case you could calculate the deltas yourself from mouse positions.
@ultrabrite Thanks! The tricky thing here is that I want to continue to display multiple density levels, not a single smoke/not-smoke interface. So that's a lot of states for marching squares! I guess in most cases the smoke is pretty smooth, so I can probably just pick two levels... It's worth thinking about.
The other possible solution I've been considering is running the smoke on a higher-resolution grid, even if I can't really do that with the velocities. That might still be too expensive though.
Looks like this is broken in 0.2.5g
syntax error line 150 (tab 0) local mdx+=mdamp*(mx-mxl-mdx) unexpected symbol near += |
[Please log in to post a comment]