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);
70 const ns::idx N =
static_cast<ns::idx
>(N_arg);
71 const ns::real dt = 0.5 /
static_cast<ns::real
>(N);
76 const int NPART = 3000;
77 const int MAX_AGE = 300;
78 std::vector<Particle> particles(NPART);
79 for (
auto& p : particles) respawn(p);
82 SetConfigFlags(FLAG_MSAA_4X_HINT);
83 InitWindow(WIN, WIN,
"NS Demo -- 2D Incompressible Flow [SPACE pause R reset +/- speed [/] scale]");
86 std::vector<Color> pixels(N * N);
89 static_cast<int>(N),
static_cast<int>(N),
90 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
92 Texture2D tex = LoadTextureFromImage(img);
96 float sim_time = 0.0f;
97 float omega_scale = 20.0f;
99 while (!WindowShouldClose()) {
101 if (IsKeyPressed(KEY_SPACE)) paused = !paused;
102 if (IsKeyPressed(KEY_R)) {
105 for (
auto& p : particles) respawn(p);
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))
113 if (IsKeyPressed(KEY_RIGHT_BRACKET))
117 for (
int s = 0; s < substeps; ++s) {
119 sim_time +=
static_cast<float>(dt);
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);
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))));
138 omega_scale = 0.98f * omega_scale + 0.02f * cur_max;
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);
148 UpdateTexture(tex, pixels.data());
151 ClearBackground(BLACK);
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);
159 for (
const auto& p : particles) {
160 float px = p.x * WIN;
161 float py = (1.0f - p.y) * WIN;
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});
170 const auto& st = solver.
stats;
172 DrawFPS(WIN - 90, 8);
174 DrawText(TextFormat(
"Grid %d x %d (%.1fM cells)",
175 N_arg, N_arg, (
float)(N * N) / 1e6f), 8, y, FS, RAYWHITE);
178 DrawText(TextFormat(
"Time %.3f substeps/frame %d [+/-]",
179 sim_time, substeps), 8, y, FS, RAYWHITE);
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});
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});
192 DrawText(TextFormat(
"omega scale %.1f [[ ]]", omega_scale),
196 DrawText(
"PAUSED", WIN / 2 - 50, WIN / 2, 32, YELLOW);
198 DrawText(
"SPACE pause R reset +/- speed [/] omega scale ESC quit",
199 8, WIN - 22, 14, {140, 140, 140, 200});