numerics
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/// @file main.cpp
2/// @brief SPH fluid simulation with raylib visualization
3///
4/// Dam-break scenario: fluid falls onto a hot sphere (90 degC) and a cold sphere
5/// (0 degC). Heat diffuses between particles and exchanges with rigid bodies.
6///
7/// Controls:
8/// SPACE -- pause / resume
9/// R -- reset to initial state
10/// LMB drag -- spawn cool water particles (20 degC)
11/// RMB drag -- spawn hot water particles (85 degC)
12/// +/- -- increase / decrease simulation speed (substeps per frame)
13/// ESC -- quit
14
15#include "raylib.h"
16#include "fluid.hpp"
17#include <cmath>
18#include <cstdlib>
19#include <algorithm>
20#include <string>
21
22// Screen / domain constants
23static constexpr int SCR_W = 1000;
24static constexpr int SCR_H = 700;
25static constexpr float DOM_W = 1.0f;
26static constexpr float DOM_H = 0.7f;
27static constexpr float SCALE = SCR_W / DOM_W; // 1000 px/m
28static constexpr float PRAD_PX = 4.5f; // visual particle radius
29
30// Coordinate helpers
31inline Vector2 sim_to_screen(float sx, float sy) {
32 return { sx * SCALE, (DOM_H - sy) * SCALE };
33}
34
35inline void sim_to_screen(float sx, float sy, float& px, float& py) {
36 px = sx * SCALE;
37 py = (DOM_H - sy) * SCALE;
38}
39
40inline void screen_to_sim(float px, float py, float& sx, float& sy) {
41 sx = px / SCALE;
42 sy = DOM_H - py / SCALE;
43}
44
45// Temperature -> colour (blue=cold -> cyan -> green -> yellow -> red=hot)
46static Color temp_color(float T, float T_min, float T_max) {
47 float t = (T - T_min) / (T_max - T_min + 1e-4f);
48 t = std::clamp(t, 0.0f, 1.0f);
49 // HSV: hue sweeps 240 deg (blue) -> 0 deg (red)
50 return ColorFromHSV((1.0f - t) * 240.0f, 1.0f, 0.95f);
51}
52
53// Initial scene setup
54static void setup(physics::FluidSolver& solver) {
55 solver.clear();
56
57 const float h = solver.params().h;
58 const float dx = 0.8f * h; // particle spacing ~= 0.02 m
59
60 // Fluid block (dam-break): left column, ambient temperature
61 for (float x = 0.03f; x < 0.53f; x += dx) {
62 for (float y = 0.03f; y < 0.43f; y += dx) {
63 // Tiny deterministic jitter to break symmetry
64 float jx = 0.001f * ((static_cast<int>(x * 1000) % 7) - 3);
65 float jy = 0.001f * ((static_cast<int>(y * 1000) % 5) - 2);
66 solver.add_particle(x + jx, y + jy, 0.0f, 0.0f, 20.0f);
67 }
68 }
69
70 // Hot sphere -- right side, lower
72 hot.x = 0.74f; hot.y = 0.22f;
73 hot.radius = 0.10f;
74 hot.temperature = 90.0f;
75 hot.mass = 5000.0f;
76 hot.fixed = true;
77 hot.is_heat_source = true;
78 solver.add_body(hot);
79
80 // Cold sphere -- upper middle
81 physics::RigidBody cold{};
82 cold.x = 0.52f; cold.y = 0.57f;
83 cold.radius = 0.08f;
84 cold.temperature = 0.0f;
85 cold.mass = 5000.0f;
86 cold.fixed = true;
87 cold.is_heat_source = true;
88 solver.add_body(cold);
89}
90
91// Draw the temperature legend bar (bottom-right corner)
92static void draw_legend(float T_min, float T_max) {
93 const int BAR_X = SCR_W - 130;
94 const int BAR_Y = 30;
95 const int BAR_W = 20;
96 const int BAR_H = 200;
97 const int steps = 50;
98
99 for (int i = 0; i < steps; ++i) {
100 float t = static_cast<float>(i) / steps;
101 float T = T_min + t * (T_max - T_min);
102 Color c = temp_color(T, T_min, T_max);
103 int y = BAR_Y + BAR_H - static_cast<int>(t * BAR_H);
104 DrawRectangle(BAR_X, y, BAR_W, BAR_H / steps + 1, c);
105 }
106 DrawRectangleLines(BAR_X, BAR_Y, BAR_W, BAR_H, WHITE);
107
108 // Labels
109 DrawText(TextFormat("%.0f degC", T_max), BAR_X + BAR_W + 4, BAR_Y, 16, WHITE);
110 DrawText(TextFormat("%.0f degC", (T_min + T_max) * 0.5f),
111 BAR_X + BAR_W + 4, BAR_Y + BAR_H / 2, 16, WHITE);
112 DrawText(TextFormat("%.0f degC", T_min), BAR_X + BAR_W + 4, BAR_Y + BAR_H - 16, 16, WHITE);
113 DrawText("Temp", BAR_X - 2, BAR_Y + BAR_H + 4, 14, GRAY);
114}
115
116// Main
117int main() {
118 InitWindow(SCR_W, SCR_H, "SPH Fluid + Heat Transfer");
119 SetTargetFPS(60);
120
122 params.h = 0.025f;
123 params.rho0 = 1000.0f;
124 params.gamma= 7;
125 params.c0 = 10.0f;
126 params.mu = 8.0f;
127 params.mass = params.rho0 * (0.8f * params.h) * (0.8f * params.h);
128 params.gx = 0.0f;
129 params.gy = -9.81f;
130 params.dt = 0.001f;
131 params.xmin = 0.0f; params.xmax = DOM_W;
132 params.ymin = 0.0f; params.ymax = DOM_H;
133 params.restitution = 0.01f;
134 params.alpha_T = 0.005f;
135 params.h_conv = 8.0f;
136
137 physics::FluidSolver solver(params);
138 setup(solver);
139
140 bool paused = false;
141 int substeps = 6; // simulation substeps per rendered frame
142 bool add_hot = false;
143
144 while (!WindowShouldClose()) {
145 if (IsKeyPressed(KEY_SPACE)) paused = !paused;
146 if (IsKeyPressed(KEY_R)) setup(solver);
147 if (IsKeyPressed(KEY_EQUAL) || IsKeyPressed(KEY_KP_ADD))
148 substeps = std::min(substeps + 1, 20);
149 if (IsKeyPressed(KEY_MINUS) || IsKeyPressed(KEY_KP_SUBTRACT))
150 substeps = std::max(substeps - 1, 1);
151 if (IsKeyPressed(KEY_G)) {
152 for (physics::RigidBody& b : solver.bodies())
153 b.fixed = false;
154 }
155
156 // Spawn particles at mouse position
157 if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) || IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) {
158 add_hot = IsMouseButtonDown(MOUSE_BUTTON_RIGHT);
159 Vector2 mp = GetMousePosition();
160 float sx, sy;
161 screen_to_sim(mp.x, mp.y, sx, sy);
162 const float T = add_hot ? 85.0f : 15.0f;
163 // Spawn a small cluster
164 for (int k = 0; k < 4; ++k) {
165 float ox = 0.01f * ((k % 3) - 1);
166 float oy = 0.01f * ((k / 3) - 0);
167 float nx = sx + ox, ny = sy + oy;
168 if (nx > params.xmin && nx < params.xmax &&
169 ny > params.ymin && ny < params.ymax) {
170 solver.add_particle(nx, ny, 0.0f, 0.0f, T);
171 }
172 }
173 }
174
175 if (!paused) {
176 for (int s = 0; s < substeps; ++s) {
177 solver.step();
178 // If heat source: maintain rigid body temperatures
179 for (physics::RigidBody& b : solver.bodies()) {
180 if (b.is_heat_source) { /* temperature held fixed already */ }
181 }
182 }
183 }
184
185 BeginDrawing();
186 ClearBackground(Color{18, 18, 28, 255});
187
188 // Draw domain boundary
189 DrawRectangleLines(0, 0, SCR_W, SCR_H, Color{60, 60, 80, 255});
190
191 const float T_min = solver.min_temp();
192 const float T_max = solver.max_temp();
193
194 // Draw rigid bodies (filled + outline)
195 for (const physics::RigidBody& body : solver.bodies()) {
196 Vector2 pos = sim_to_screen(body.x, body.y);
197 float r = body.radius * SCALE;
198 Color fc = temp_color(body.temperature, T_min, T_max);
199 fc.a = 180;
200 DrawCircleV(pos, r, fc);
201 DrawCircleLines(static_cast<int>(pos.x), static_cast<int>(pos.y),
202 r, Color{220, 220, 220, 230});
203
204 // Label
205 const char* label = (body.temperature > 50.0f) ? "HOT" : "COLD";
206 DrawText(label,
207 static_cast<int>(pos.x) - 12,
208 static_cast<int>(pos.y) - 8, 14,
209 (body.temperature > 50.0f) ? RED : SKYBLUE);
210 }
211
212 // Draw particles
213 for (const physics::Particle& p : solver.particles()) {
214 float px, py;
215 sim_to_screen(p.x, p.y, px, py);
216 Color c = temp_color(p.temperature, T_min, T_max);
217 DrawCircleV({px, py}, PRAD_PX, c);
218 }
219
220 // HUD
221 DrawFPS(10, 10);
222 DrawText(TextFormat("Particles: %d", (int)solver.particles().size()),
223 10, 36, 20, WHITE);
224 DrawText(TextFormat("Substeps/frame: %d (sim rate: %.2fx)",
225 substeps, substeps * params.dt * 60.0f),
226 10, 60, 18, LIGHTGRAY);
227 DrawText(TextFormat("T range: %.1f degC - %.1f degC", T_min, T_max),
228 10, 84, 18, LIGHTGRAY);
229
230 // Controls bar
231 DrawRectangle(0, SCR_H - 30, SCR_W, 30, Color{0,0,0,180});
232 DrawText("SPACE=pause R=reset G=drop bodies LMB=cold water RMB=hot water +/-=speed ESC=quit",
233 8, SCR_H - 22, 14, Color{180, 180, 180, 255});
234
235 // Pause overlay
236 if (paused) {
237 DrawRectangle(0, 0, SCR_W, SCR_H, Color{0, 0, 0, 100});
238 DrawText("PAUSED", SCR_W / 2 - 60, SCR_H / 2 - 20, 40, YELLOW);
239 }
240
241 // Temperature legend
242 draw_legend(T_min, T_max);
243
244 EndDrawing();
245 }
246
247 CloseWindow();
248 return 0;
249}
float max_temp() const
Definition fluid.hpp:83
const FluidParams & params() const
Definition fluid.hpp:80
void add_particle(float x, float y, float vx, float vy, float temperature)
Definition fluid.cpp:28
const std::vector< RigidBody > & bodies() const
Definition fluid.hpp:78
void add_body(const RigidBody &body)
Definition fluid.cpp:39
float min_temp() const
Definition fluid.hpp:82
const std::vector< Particle > & particles() const
Definition fluid.hpp:77
int main()
Definition main.cpp:84
Weakly Compressible SPH (WCSPH) fluid solver – public interface.
Vector2 sim_to_screen(float sx, float sy)
Definition main.cpp:31
void screen_to_sim(float px, float py, float &sx, float &sy)
Definition main.cpp:40
float rho0
Rest density [kg/m^3].
Definition fluid.hpp:37
float alpha_T
Thermal diffusivity [m^2/s].
Definition fluid.hpp:58
float mu
Dynamic viscosity [Pa*s].
Definition fluid.hpp:40
int gamma
Tait EOS exponent.
Definition fluid.hpp:38
float h
Smoothing length [m].
Definition fluid.hpp:34
float c0
Speed of sound [m/s] -> B = rho_0c_0^2/gamma.
Definition fluid.hpp:39
float gy
Gravity y [m/s^2].
Definition fluid.hpp:45
float restitution
Velocity restitution at walls.
Definition fluid.hpp:55
float gx
Gravity x [m/s^2].
Definition fluid.hpp:44
float h_conv
Convective coefficient with rigid bodies [1/s].
Definition fluid.hpp:59
float dt
Timestep [s] (must satisfy CFL: dt < h/c0)
Definition fluid.hpp:48
float mass
Particle mass [kg] (~= rho_0*dx^2, dx=0.8h)
Definition fluid.hpp:41
A single SPH fluid particle (float precision for performance)
Definition particle.hpp:8
A rigid spherical body that interacts with fluid particles.
Definition rigid_body.hpp:8