Advertisement

Navier-Stokes fluid dynamics

Started by March 01, 2025 01:27 AM
20 comments, last by taby 18 hours, 4 minutes ago

I basically started studying Unreal Engine 5 because it has a really nice Navier-Stokes solver. However, it was not clear to me whether or not they allowed you to report collision data for things other than rendering purposes. That was a big disappointment for me, because I would have had to pay some major cash to get official support on the subject.

So I decided to see how far I could get using Claude AI. After some alterations, the code that I got from Claude is at:

https://github.com/sjhalayka/navier-stokes

It collects data on the collisions with obstacles, and reports it on the CPU end of things.

Below is a grayscale image, with orange highlights where the density field interacts with the obstacle field. Not bad for an AI (under some selective guidance).

 

There is one problem. The simulation is apparently square, and when the window is widescreen, it is squashed/stretched.

Any ideas on how to go about fixing that?

Advertisement

I fixed it, but now the mouse position is messed up on the y axis… looks good on the x axis.

Right on. I got it working. I'm going to submit it to Code Review Stack Exchange.

I added in code and data for a colour texture, which advects and diffuses like the density texture. This way I can visually differentiate between enemy fire and friendly fire. Claude helped.

At one point i got Zebra shadows by accident. Looked fluidy in motion. : )

 

Advertisement

I have a bug, and I've noticed it in another OpenGL app that I wrote that I had the same problem. Basically, the sprites (e.g. stamps) are drawn too small, and not 64x64 pixels like they should be.

I am 95% sure that the following code is where the problem lies:

const char* stampObstacleFragmentShader = R"(
#version 330 core
uniform sampler2D obstacleTexture;
uniform sampler2D stampTexture;
uniform vec2 position;
uniform vec2 stampSize;
uniform float threshold;
out float FragColor;

in vec2 TexCoord;

void main() 
{
    // Get current obstacle value
    float obstacle = texture(obstacleTexture, TexCoord).r;
    
    // Calculate coordinates in stamp texture
	vec2 stamp_size = textureSize(stampTexture, 0)/2.0;

    vec2 stampCoord = (TexCoord - position) * textureSize(obstacleTexture, 0) / stamp_size + vec2(0.5);

    vec2 adjustedCoord = stampCoord;

	ivec2 obstacle_tex_size = textureSize(obstacleTexture, 0);    

	float aspect_ratio = float(obstacle_tex_size.x) / float(obstacle_tex_size.y);

    // For non-square textures, adjust sampling to prevent stretching
    if (aspect_ratio > 1.0) {
        adjustedCoord.x = (adjustedCoord.x - 0.5) / aspect_ratio + 0.5;
    } else if (aspect_ratio < 1.0) {
        adjustedCoord.y = (adjustedCoord.y - 0.5) * aspect_ratio + 0.5;
    }    

	stampCoord = adjustedCoord;

	
	// Keep the stamps square
	if(aspect_ratio > 1.0)
		stampCoord *= aspect_ratio;
	else
		stampCoord /= aspect_ratio;



	// Why do I need to perform a scale?
	// stampCoord /= 1.5;




    // Check if we're within stamp bounds
    if (stampCoord.x >= 0.0 && stampCoord.x <= 1.0 && 
        stampCoord.y >= 0.0 && stampCoord.y <= 1.0) {
        
        // Sample stamp texture (use first channel for grayscale images)
        //float stampValue = length(texture(stampTexture, stampCoord).rgb) / 1.732; // Normalize RGB length
        
		float stampValue = texture(stampTexture, stampCoord).a;

        // Apply threshold to make it binary
        stampValue = stampValue > threshold ? 1.0 : 0.0;
        
        // Combine with existing obstacle (using max for union)
        obstacle = max(obstacle, stampValue);
    }
    
    FragColor = obstacle;
}
)";

Can you see why it doesn't render the sprite as 64x64?

The whole code is at:

https://github.com/sjhalayka/navier-stokes/blob/main/main.cpp

Thanks for any ideas.

I've got fluid-sprite collision detection working, along with sprite-sprite collision detection.

The next step is to add force and colour to the fields based on the location and velocity of each bullet.

taby said:
The next step is to add force and colour to the fields based on the location and velocity of each bullet.

How do you represent color, and how does it change by what?

And, seems you have an idea to make a fluid game. I guess those stars should bounce off each other, and the fluid makes them flow and rotate. That's intersting. I have not seen such game yet.

But there is a problem, and i can tell what things i have tried related to that.
The problem is the boundary of the simualtion. It kills everything intersting, and damps all the fluidy swirls of our imagination away, leaving us with boring nothingness.
We have two options here. A static boundary, like the edge of a swimming pool. Ther are no tornados or river flows in those pools because of that. Boring.
Much better is a ‘periodic’ baundary. (There are proper terms for those boundary conditions, but i have forgot them : ) I mean, what flows out left, comes back from the right. So you can make a stream actually.
But the stream can only have one global direction, which is still boring.
So i tried to add external force, actually using a procedural vector field. It's easy to create such field from sines, or tiling noise function, etc.
You can see the low frequency sines in those images, now that you know:

 

 

There is the intersting high frequency details from the fluid simualtion, but there is also artistic control of the flow on the low frequencies.
That's pretty promising for my goal of procedural generation, but i guess you could do similar things to design fluidy levels for a game, eventually, beside obvious ideas such as sources and sinks, explosions, etc. 



 

Advertisement