numerics 0.1.0
Loading...
Searching...
No Matches
viz.hpp
Go to the documentation of this file.
1/// @file include/viz/viz.hpp
2/// @brief Lightweight real-time visualization layer for numerics apps.
3///
4/// Wraps raylib behind a single draw-callback interface. Every app has the
5/// same shape:
6///
7/// @code
8/// #include "viz.hpp"
9///
10/// num::viz::run("My Sim", 900, 900, [&](num::viz::Frame& f) {
11/// f.step([&]{ sim.step(dt); }); // SPACE pauses, +/- adjusts
12/// substeps for (int i = 0; i < n; ++i)
13/// f.dot(x[i], y[i], heat_color(T[i]));
14/// });
15/// @endcode
16///
17/// This header is NOT included by numerics.hpp -- it depends on raylib which
18/// is an optional app-layer dependency. Include it directly from your app.
19///
20/// ## Controls (built-in)
21/// SPACE pause / unpause
22/// R reset (check f.reset_pressed() in your callback)
23/// +/- increase / decrease substeps
24/// ESC quit
25#pragma once
26
27#include <raylib.h>
28#include <raymath.h>
29#include <algorithm>
30#include <cstdint>
31#include <cstdarg>
32#include <cstdio>
33#include <vector>
34
35namespace num::viz {
36
37// RGBA color (identical byte layout to raylib ::Color -- cast-safe)
38struct Color {
39 uint8_t r = 0, g = 0, b = 0, a = 255;
40};
41
42// Common presets
43inline constexpr Color kBlack = {0, 0, 0, 255};
44inline constexpr Color kWhite = {255, 255, 255, 255};
45inline constexpr Color kRed = {220, 40, 40, 255};
46inline constexpr Color kGreen = {50, 200, 50, 255};
47inline constexpr Color kBlue = {50, 100, 230, 255};
48inline constexpr Color kYellow = {240, 220, 30, 255};
49inline constexpr Color kGray = {120, 120, 120, 255};
50inline constexpr Color kSkyBlue = {100, 180, 240, 255};
51
52// Convert viz::Color <-> raylib ::Color
53inline ::Color to_rl(Color c) {
54 return {c.r, c.g, c.b, c.a};
55}
56inline Color from_rl(::Color c) {
57 return {c.r, c.g, c.b, c.a};
58}
59
60// Unpack a packed ARGB uint32 (e.g. nbody Body::color) into viz::Color
61inline Color unpack(uint32_t c) {
62 return {uint8_t(c >> 24), uint8_t(c >> 16), uint8_t(c >> 8), uint8_t(c)};
63}
64
65// Colormaps (all return viz::Color; inputs are normalized unless noted)
66
67/// t in [0,1]: blue -> cyan -> yellow -> red.
68inline Color heat_color(float t) {
69 t = std::clamp(t, 0.0f, 1.0f);
70 return from_rl(ColorFromHSV((1.0f - t) * 240.0f, 1.0f, 0.95f));
71}
72
73/// t in [-1,1]: deep blue -> black -> deep red.
74inline Color diverging_color(float t) {
75 t = std::clamp(t, -1.0f, 1.0f);
76 if (t >= 0.0f) {
77 auto v = uint8_t(255 * t * t);
78 return {v, 0, uint8_t(v / 10), 255};
79 } else {
80 auto v = uint8_t(255 * t * t);
81 return {uint8_t(v / 10), 0, v, 255};
82 }
83}
84
85/// Quantum wavefunction: hue = phase, brightness = sqrt(|psi|^2 / max).
86inline Color phase_hsv_color(double prob, double phase, double max_prob) {
87 if (max_prob < 1e-20)
88 return kBlack;
89 float amp = std::min(1.0f, float(std::sqrt(prob / max_prob)));
90 float hue = float((phase + 3.14159265) / (2.0 * 3.14159265)) * 360.0f;
91 float h6 = hue / 60.0f;
92 int hi = int(h6) % 6;
93 float f = h6 - int(h6);
94 float s = 0.95f;
95 float p = amp * (1.0f - s);
96 float q = amp * (1.0f - s * f);
97 float u = amp * (1.0f - s * (1.0f - f));
98 float r, g, b;
99 switch (hi) {
100 case 0:
101 r = amp;
102 g = u;
103 b = p;
104 break;
105 case 1:
106 r = q;
107 g = amp;
108 b = p;
109 break;
110 case 2:
111 r = p;
112 g = amp;
113 b = u;
114 break;
115 case 3:
116 r = p;
117 g = q;
118 b = amp;
119 break;
120 case 4:
121 r = u;
122 g = p;
123 b = amp;
124 break;
125 default:
126 r = amp;
127 g = p;
128 b = q;
129 break;
130 }
131 return {uint8_t(r * 255), uint8_t(g * 255), uint8_t(b * 255), 255};
132}
133
134/// Linear blend between two colors.
135inline Color lerp_color(Color a, Color b, float t) {
136 t = std::clamp(t, 0.0f, 1.0f);
137 return {
138 uint8_t(a.r + t * (b.r - a.r)),
139 uint8_t(a.g + t * (b.g - a.g)),
140 uint8_t(a.b + t * (b.b - a.b)),
141 uint8_t(a.a + t * (b.a - a.a)),
142 };
143}
144
145// Internal state managed by run()
146namespace detail {
148 Texture2D tex{};
149 std::vector<::Color> buf;
150 int N = 0;
151 bool valid = false;
152
153 void ensure(int n) {
154 if (n == N && valid)
155 return;
156 if (valid)
157 UnloadTexture(tex);
158 N = n;
159 buf.assign(n * n, ::BLACK);
160 Image img = GenImageColor(n, n, ::BLACK);
161 tex = LoadTextureFromImage(img);
162 UnloadImage(img);
163 valid = true;
164 }
165 void unload() {
166 if (valid) {
167 UnloadTexture(tex);
168 valid = false;
169 N = 0;
170 }
171 }
172};
173
174struct State {
176 bool paused = false;
177 int substeps = 1;
178 int slider_count = 0; // reset each frame
179};
180} // namespace detail
181
182// Frame -- passed to the draw callback on every tick.
183struct Frame {
185 bool& paused;
188
189 // Advance the simulation fn() `substeps` times if not paused.
190 // fn() -> void
191 template<class Fn>
192 void step(Fn fn) {
193 if (!paused)
194 for (int i = 0; i < substeps; ++i)
195 fn();
196 }
197
198 // True on the frame R was pressed.
199 bool reset_pressed() const {
200 return IsKeyPressed(KEY_R);
201 }
202
203 // 2D primitives -- pixel coordinates, (0,0) = top-left.
204 void dot(float x, float y, Color c, float r = 3.0f) {
205 DrawCircleV({x, y}, r, to_rl(c));
206 }
207 void circle(float x, float y, float r, Color c) {
208 DrawCircleLines(int(x), int(y), r, to_rl(c));
209 }
210 void line(float x0,
211 float y0,
212 float x1,
213 float y1,
214 Color c,
215 float thick = 1.0f) {
216 DrawLineEx({x0, y0}, {x1, y1}, thick, to_rl(c));
217 }
218 void rect(float x, float y, float w, float h, Color c) {
219 DrawRectangleV({x, y}, {w, h}, to_rl(c));
220 }
221 void rect_outline(float x,
222 float y,
223 float w,
224 float h,
225 Color c,
226 float thick = 1.0f) {
227 DrawRectangleLinesEx({x, y, w, h}, thick, to_rl(c));
228 }
229 void text(const char* s, float x, float y, int sz, Color c) {
230 DrawText(s, int(x), int(y), sz, to_rl(c));
231 }
232 // printf-style text -- uses a 256-byte internal buffer (safe for HUD
233 // strings)
234 void textf(float x, float y, int sz, Color c, const char* fmt, ...) {
235 char buf[256];
236 va_list args;
237 va_start(args, fmt);
238 vsnprintf(buf, sizeof(buf), fmt, args);
239 va_end(args);
240 DrawText(buf, int(x), int(y), sz, to_rl(c));
241 }
242
243 // Pixel field -- fills the entire window with an N×N colored grid.
244 // color_fn(col, row) -> Color (col = x-axis, row = y-axis, top-left
245 // origin)
246 template<class Fn>
247 void field(int N, Fn color_fn) {
248 auto& cv = _s.canvas;
249 cv.ensure(N);
250 for (int row = 0; row < N; ++row)
251 for (int col = 0; col < N; ++col)
252 cv.buf[row * N + col] = to_rl(color_fn(col, row));
253 UpdateTexture(cv.tex, cv.buf.data());
254 DrawTexturePro(cv.tex,
255 {0, 0, float(N), float(N)},
256 {0, 0, float(width), float(height)},
257 {0, 0},
258 0.0f,
259 ::WHITE);
260 }
261
262 // GUI slider -- stacks vertically in the top-left corner.
263 // Reads mouse drag to update val.
264 void slider(const char* label, double lo, double hi, double& val) {
265 constexpr int PAD = 10;
266 constexpr int SLOT_H = 38;
267 constexpr int BAR_W = 220;
268 constexpr int BAR_H = 8;
269 const int X = PAD;
270 const int Y = PAD + _s.slider_count * SLOT_H;
272
273 DrawRectangle(X - 4, Y - 2, BAR_W + 8, SLOT_H - 4, {0, 0, 0, 170});
274 DrawRectangle(X, Y + 18, BAR_W, BAR_H, {80, 80, 80, 220});
275
276 float t = std::clamp(float((val - lo) / (hi - lo)), 0.0f, 1.0f);
277 int tx = X + int(t * BAR_W);
278
279 Vector2 mp = GetMousePosition();
280 Rectangle hit = {float(X), float(Y), float(BAR_W), float(SLOT_H)};
281 if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)
282 && CheckCollisionPointRec(mp, hit)) {
283 t = std::clamp((mp.x - X) / BAR_W, 0.0f, 1.0f);
284 val = lo + t * (hi - lo);
285 tx = X + int(t * BAR_W);
286 }
287
288 DrawCircle(tx, Y + 18 + BAR_H / 2, 9, {200, 200, 200, 240});
289 DrawText(TextFormat("%s: %.3g", label, val),
290 X,
291 Y + 2,
292 13,
293 {210, 210, 210, 230});
294 }
295
296 // 3D camera descriptor
297 struct Cam3 {
298 float px = 0, py = 0, pz = 0; // camera position
299 float tx = 0, ty = 0, tz = 0; // look-at target
300 float ux = 0, uy = 1, uz = 0; // up vector (default: world-Y)
301 float fovy = 45.0f;
302 };
303
304 // Begin 3D rendering mode. All 3D draw calls must sit between
305 // begin3d/end3d.
306 void begin3d(Cam3 cam) {
307 Camera3D c{};
308 c.position = {cam.px, cam.py, cam.pz};
309 c.target = {cam.tx, cam.ty, cam.tz};
310 c.up = {cam.ux, cam.uy, cam.uz};
311 c.fovy = cam.fovy;
312 c.projection = CAMERA_PERSPECTIVE;
313 BeginMode3D(c);
314 }
315 void end3d() {
316 EndMode3D();
317 }
318
319 void sphere3d(float x, float y, float z, float r, Color c) {
320 DrawSphere({x, y, z}, r, to_rl(c));
321 }
322 void sphere3d_wire(float x, float y, float z, float r, Color c) {
323 DrawSphereWires({x, y, z}, r, 6, 6, to_rl(c));
324 }
325 void line3d(float x0,
326 float y0,
327 float z0,
328 float x1,
329 float y1,
330 float z1,
331 Color c) {
332 DrawLine3D({x0, y0, z0}, {x1, y1, z1}, to_rl(c));
333 }
334 void
335 cube3d(float x, float y, float z, float sx, float sy, float sz, Color c) {
336 DrawCube({x, y, z}, sx, sy, sz, to_rl(c));
337 }
338};
339
340// run() -- open a window and call draw(Frame&) at 60 Hz until ESC / close.
341//
342// Built-in key bindings:
343// SPACE pause / unpause
344// R sets reset flag (check via f.reset_pressed())
345// +/- increase / decrease substeps (capped at 16)
346// ESC quit
347template<class DrawFn>
348void run(const char* title,
349 int w,
350 int h,
351 DrawFn draw,
352 Color bg = {15, 15, 15, 255}) {
353 SetConfigFlags(FLAG_MSAA_4X_HINT);
354 InitWindow(w, h, title);
355 SetTargetFPS(60);
356
357 detail::State state;
358 Frame frame{w, h, state.paused, state.substeps, state};
359
360 while (!WindowShouldClose()) {
361 if (IsKeyPressed(KEY_SPACE))
362 state.paused = !state.paused;
363 if (IsKeyPressed(KEY_EQUAL) || IsKeyPressed(KEY_KP_ADD))
364 state.substeps = std::min(state.substeps + 1, 16);
365 if (IsKeyPressed(KEY_MINUS) || IsKeyPressed(KEY_KP_SUBTRACT))
366 state.substeps = std::max(state.substeps - 1, 1);
367
368 state.slider_count = 0;
369
370 BeginDrawing();
371 ClearBackground(to_rl(bg));
372
373 draw(frame);
374
375 if (state.paused) {
376 DrawRectangle(w / 2 - 70, h / 2 - 18, 140, 36, {0, 0, 0, 160});
377 DrawText("PAUSED", w / 2 - 46, h / 2 - 10, 24, ::YELLOW);
378 }
379
380 EndDrawing();
381 }
382
383 state.canvas.unload();
384 CloseWindow();
385}
386
387} // namespace num::viz
constexpr Color kWhite
Definition viz.hpp:44
Color lerp_color(Color a, Color b, float t)
Linear blend between two colors.
Definition viz.hpp:135
void run(const char *title, int w, int h, DrawFn draw, Color bg={15, 15, 15, 255})
Definition viz.hpp:348
Color unpack(uint32_t c)
Definition viz.hpp:61
Color from_rl(::Color c)
Definition viz.hpp:56
Color phase_hsv_color(double prob, double phase, double max_prob)
Quantum wavefunction: hue = phase, brightness = sqrt(|psi|^2 / max).
Definition viz.hpp:86
constexpr Color kBlue
Definition viz.hpp:47
constexpr Color kBlack
Definition viz.hpp:43
Color heat_color(float t)
t in [0,1]: blue -> cyan -> yellow -> red.
Definition viz.hpp:68
Color diverging_color(float t)
t in [-1,1]: deep blue -> black -> deep red.
Definition viz.hpp:74
constexpr Color kYellow
Definition viz.hpp:48
constexpr Color kGray
Definition viz.hpp:49
constexpr Color kGreen
Definition viz.hpp:46
constexpr Color kSkyBlue
Definition viz.hpp:50
inline ::Color to_rl(Color c)
Definition viz.hpp:53
constexpr Color kRed
Definition viz.hpp:45
constexpr real e
Definition math.hpp:43
uint8_t r
Definition viz.hpp:39
uint8_t a
Definition viz.hpp:39
uint8_t g
Definition viz.hpp:39
uint8_t b
Definition viz.hpp:39
void dot(float x, float y, Color c, float r=3.0f)
Definition viz.hpp:204
void line(float x0, float y0, float x1, float y1, Color c, float thick=1.0f)
Definition viz.hpp:210
bool & paused
Definition viz.hpp:185
void field(int N, Fn color_fn)
Definition viz.hpp:247
void begin3d(Cam3 cam)
Definition viz.hpp:306
void sphere3d_wire(float x, float y, float z, float r, Color c)
Definition viz.hpp:322
void cube3d(float x, float y, float z, float sx, float sy, float sz, Color c)
Definition viz.hpp:335
void rect_outline(float x, float y, float w, float h, Color c, float thick=1.0f)
Definition viz.hpp:221
void slider(const char *label, double lo, double hi, double &val)
Definition viz.hpp:264
void line3d(float x0, float y0, float z0, float x1, float y1, float z1, Color c)
Definition viz.hpp:325
bool reset_pressed() const
Definition viz.hpp:199
void textf(float x, float y, int sz, Color c, const char *fmt,...)
Definition viz.hpp:234
void end3d()
Definition viz.hpp:315
int & substeps
Definition viz.hpp:186
void step(Fn fn)
Definition viz.hpp:192
void sphere3d(float x, float y, float z, float r, Color c)
Definition viz.hpp:319
void text(const char *s, float x, float y, int sz, Color c)
Definition viz.hpp:229
void circle(float x, float y, float r, Color c)
Definition viz.hpp:207
void rect(float x, float y, float w, float h, Color c)
Definition viz.hpp:218
detail::State & _s
Definition viz.hpp:187
std::vector<::Color > buf
Definition viz.hpp:149
FieldCanvas canvas
Definition viz.hpp:175