numerics
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/// @file main.cpp
2/// @brief Real-time raylib visualisation of the 2-D Navier-Stokes demo.
3///
4/// Render layers (back -> front):
5/// 1. Vorticity texture (NxN) -- red = +omega (CCW), blue = -omega (CW), black = 0
6/// 2. Particle tracers -- 3 000 massless particles, white with trail fade
7/// 3. HUD -- solver stats, fps, controls
8///
9/// Keyboard controls:
10/// SPACE pause / resume
11/// R reset simulation
12/// + / - increase / decrease substeps per frame
13/// [ / ] halve / double visualisation omega scale
14/// ESC / Q quit
15///
16/// Command-line:
17/// ./ns_demo [N] e.g. ./ns_demo 512 (default 256)
18
19#include "raylib.h"
20#include "ns_solver.hpp"
21
22#include <vector>
23#include <cmath>
24#include <cstdlib>
25#include <algorithm>
26
27// Colour map: diverging blue-black-red
28
29static Color vorticity_color(float t) {
30 // t in [-1, 1]: -1 -> deep blue, 0 -> black, +1 -> deep red
31 t = std::max(-1.0f, std::min(1.0f, t));
32 if (t >= 0.0f) {
33 auto v = static_cast<unsigned char>(255 * t * t);
34 return {v, 0, static_cast<unsigned char>(v * 0.1f), 255};
35 } else {
36 auto v = static_cast<unsigned char>(255 * t * t);
37 return {static_cast<unsigned char>(v * 0.1f), 0, v, 255};
38 }
39}
40
41// Particle tracer
42
43struct Particle {
44 float x = 0, y = 0; // physical coords in [0, 1]
45 int age = 0;
46};
47
48static void advect_particle(Particle& p, const ns::NSSolver& s, float dt_f) {
49 float ux = static_cast<float>(s.interp_u(p.x, p.y));
50 float vy = static_cast<float>(s.interp_v(p.x, p.y));
51 p.x = std::fmod(p.x + ux * dt_f + 10.0f, 1.0f);
52 p.y = std::fmod(p.y + vy * dt_f + 10.0f, 1.0f);
53 ++p.age;
54}
55
56static void respawn(Particle& p) {
57 p.x = static_cast<float>(rand()) / RAND_MAX;
58 p.y = static_cast<float>(rand()) / RAND_MAX;
59 p.age = 0;
60}
61
62// main
63
64int main(int argc, char* argv[]) {
65 int N_arg = (argc > 1) ? std::atoi(argv[1]) : 256;
66 if (N_arg < 32 || N_arg > 2048) {
67 fprintf(stderr, "N must be in [32, 2048]; got %d\n", N_arg);
68 return 1;
69 }
70 const ns::idx N = static_cast<ns::idx>(N_arg);
71 const ns::real dt = 0.5 / static_cast<ns::real>(N); // CFL ~= 0.5
72
73 ns::NSSolver solver(N, dt, /*nu=*/0.0);
74 solver.init_shear_layer();
75
76 const int NPART = 3000;
77 const int MAX_AGE = 300;
78 std::vector<Particle> particles(NPART);
79 for (auto& p : particles) respawn(p);
80
81 const int WIN = 900;
82 SetConfigFlags(FLAG_MSAA_4X_HINT);
83 InitWindow(WIN, WIN, "NS Demo -- 2D Incompressible Flow [SPACE pause R reset +/- speed [/] scale]");
84 SetTargetFPS(60);
85
86 std::vector<Color> pixels(N * N);
87 Image img = {
88 pixels.data(),
89 static_cast<int>(N), static_cast<int>(N),
90 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
91 };
92 Texture2D tex = LoadTextureFromImage(img);
93
94 bool paused = false;
95 int substeps = 1;
96 float sim_time = 0.0f;
97 float omega_scale = 20.0f; // vorticity display range; auto-adjusts
98
99 while (!WindowShouldClose()) {
100
101 if (IsKeyPressed(KEY_SPACE)) paused = !paused;
102 if (IsKeyPressed(KEY_R)) {
103 solver.init_shear_layer();
104 sim_time = 0.0f;
105 for (auto& p : particles) respawn(p);
106 }
107 if (IsKeyPressed(KEY_EQUAL) || IsKeyPressed(KEY_KP_ADD))
108 substeps = std::min(substeps + 1, 16);
109 if (IsKeyPressed(KEY_MINUS) || IsKeyPressed(KEY_KP_SUBTRACT))
110 substeps = std::max(substeps - 1, 1);
111 if (IsKeyPressed(KEY_LEFT_BRACKET))
112 omega_scale *= 0.5f;
113 if (IsKeyPressed(KEY_RIGHT_BRACKET))
114 omega_scale *= 2.0f;
115
116 if (!paused) {
117 for (int s = 0; s < substeps; ++s) {
118 solver.step();
119 sim_time += static_cast<float>(dt);
120 }
121
122 // Advect particles (use total elapsed sim dt for this frame)
123 float frame_dt = static_cast<float>(dt) * substeps;
124 for (auto& p : particles) {
125 advect_particle(p, solver, frame_dt);
126 if (p.age > MAX_AGE) respawn(p);
127 }
128 }
129
130 // Compute max vorticity this frame for auto-range (lightweight, O(N^2))
131 float cur_max = 1.0f;
132 for (ns::idx i = 0; i < N; ++i)
133 for (ns::idx j = 0; j < N; ++j)
134 cur_max = std::max(cur_max, std::abs(
135 static_cast<float>(solver.vorticity(i, j))));
136
137 // Exponential moving average to prevent scale jitter
138 omega_scale = 0.98f * omega_scale + 0.02f * cur_max;
139
140 // Fill pixel buffer
141 // Texture origin is top-left; grid j=0 is bottom -> flip j
142 for (ns::idx i = 0; i < N; ++i) {
143 for (ns::idx j = 0; j < N; ++j) {
144 float w = static_cast<float>(solver.vorticity(i, j)) / omega_scale;
145 pixels[i + (N - 1 - j) * N] = vorticity_color(w);
146 }
147 }
148 UpdateTexture(tex, pixels.data());
149
150 BeginDrawing();
151 ClearBackground(BLACK);
152
153 // Vorticity field (stretched to full window)
154 Rectangle src = {0, 0, static_cast<float>(N), static_cast<float>(N)};
155 Rectangle dst = {0, 0, static_cast<float>(WIN), static_cast<float>(WIN)};
156 DrawTexturePro(tex, src, dst, {0, 0}, 0.0f, WHITE);
157
158 // Particles
159 for (const auto& p : particles) {
160 float px = p.x * WIN;
161 float py = (1.0f - p.y) * WIN; // flip y for screen coords
162 float alpha = static_cast<float>(MAX_AGE - p.age) / MAX_AGE;
163 unsigned char a = static_cast<unsigned char>(180 * alpha);
164 DrawCircleV({px, py}, 1.5f, {255, 255, 255, a});
165 }
166
167 const int FS = 18;
168 const int FS2 = 16;
169 int y = 8;
170 const auto& st = solver.stats;
171
172 DrawFPS(WIN - 90, 8);
173
174 DrawText(TextFormat("Grid %d x %d (%.1fM cells)",
175 N_arg, N_arg, (float)(N * N) / 1e6f), 8, y, FS, RAYWHITE);
176 y += FS + 4;
177
178 DrawText(TextFormat("Time %.3f substeps/frame %d [+/-]",
179 sim_time, substeps), 8, y, FS, RAYWHITE);
180 y += FS + 4;
181
182 DrawText(TextFormat("CG %zu iters res %.2e %5.1f ms",
183 st.cg_iters, st.cg_residual, st.pressure_ms),
184 8, y, FS, {100, 220, 255, 255});
185 y += FS + 4;
186
187 DrawText(TextFormat("Advect %4.1f ms Project %4.1f ms Total %5.1f ms",
188 st.advect_ms, st.project_ms, st.total_ms),
189 8, y, FS, {180, 255, 180, 255});
190 y += FS + 4;
191
192 DrawText(TextFormat("omega scale %.1f [[ ]]", omega_scale),
193 8, y, FS2, GRAY);
194
195 if (paused)
196 DrawText("PAUSED", WIN / 2 - 50, WIN / 2, 32, YELLOW);
197
198 DrawText("SPACE pause R reset +/- speed [/] omega scale ESC quit",
199 8, WIN - 22, 14, {140, 140, 140, 200});
200
201 EndDrawing();
202 }
203
204 UnloadTexture(tex);
205 CloseWindow();
206 return 0;
207}
real vorticity(idx i, idx j) const
Vorticity omega = d_v/d_x - d_u/d_y at grid corner (i*h, j*h).
real interp_u(real px, real py) const
Interpolate x-velocity at physical point (px, py).
real interp_v(real px, real py) const
Interpolate y-velocity at physical point (px, py).
void init_shear_layer(real rho=0.05, real delta=0.05)
Definition ns_solver.cpp:27
void step()
Advance one time step (advect -> pressure -> project).
Definition ns_solver.cpp:50
int main()
Definition main.cpp:84
2-D incompressible Navier-Stokes, periodic MAC grid
float y
Definition main.cpp:44
int age
Definition main.cpp:45
float x
Definition main.cpp:44