Skip to content

Commit 9a4927d

Browse files
committed
[TEST] cleanup test code and restructure
1 parent 449f656 commit 9a4927d

File tree

4 files changed

+375
-293
lines changed

4 files changed

+375
-293
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ cc -s -O3 %DEF_FLAGS_COMPILER% -o %SOURCE_NAME%.exe %SOURCE_NAME%.c %DEF_FLAGS_L
110110
Afterwards you can generate a video from the ppm frames using FFMPEG or similar ones.
111111

112112
```bat
113-
ffmpeg -y -framerate 30 -i frame_%05d.ppm -c:v libx264 -pix_fmt yuv420p cfd.mp4
113+
ffmpeg -y -framerate 30 -i frame_%%05d.ppm -c:v libx264 -pix_fmt yuv420p cfd.mp4
114114
```
115115

116116
## Run Example: nostdlib, freestsanding

tests/build.bat

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@ set SOURCE_NAME=cfd_test
77
rm *.ppm
88
cc -s -O3 %DEF_FLAGS_COMPILER% -o %SOURCE_NAME%.exe %SOURCE_NAME%.c %DEF_FLAGS_LINKER%
99
%SOURCE_NAME%.exe
10-
ffmpeg -y -framerate 30 -i frame_%05d.ppm -c:v libx264 -pix_fmt yuv420p cfd.mp4

tests/cfd_renderer.h

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
/* cfd.h - v0.2 - public domain data structures - nickscha 2025
2+
3+
A C89 standard compliant, single header, nostdlib (no C Standard Library) renderer for the CFD LBM Grid.
4+
It writes the data in to a pixel buffer which is then written to a PPM image file.
5+
6+
LICENSE
7+
8+
Placed in the public domain and also MIT licensed.
9+
See end of file for detailed license information.
10+
11+
*/
12+
#ifndef CFD_RENDERER_H
13+
#define CFD_RENDERER_H
14+
15+
/* #############################################################################
16+
* # COMPILER SETTINGS
17+
* #############################################################################
18+
*/
19+
/* Check if using C99 or later (inline is supported) */
20+
#if __STDC_VERSION__ >= 199901L
21+
#define CFD_RENDERER_INLINE inline
22+
#define CFD_RENDERER_API extern
23+
#elif defined(__GNUC__) || defined(__clang__)
24+
#define CFD_RENDERER_INLINE __inline__
25+
#define CFD_RENDERER_API static
26+
#elif defined(_MSC_VER)
27+
#define CFD_RENDERER_INLINE __inline
28+
#define CFD_RENDERER_API static
29+
#else
30+
#define CFD_RENDERER_INLINE
31+
#define CFD_RENDERER_API static
32+
#endif
33+
34+
#include "../cfd.h"
35+
#include "cfd_math.h"
36+
37+
/* Structure to hold a single cfd_pixel_color's color data */
38+
typedef struct cfd_pixel_color
39+
{
40+
unsigned char r;
41+
unsigned char g;
42+
unsigned char b;
43+
44+
} cfd_pixel_color;
45+
46+
/* Build once at startup */
47+
static cfd_pixel_color cfd_color_map[401]; /* 0..400; use index -1 for barrier separately */
48+
49+
CFD_RENDERER_API CFD_RENDERER_INLINE void cfd_build_colormap(void)
50+
{
51+
int i;
52+
for (i = 0; i <= 400; ++i)
53+
{
54+
cfd_pixel_color color = {0};
55+
if (i < 50)
56+
{
57+
color.b = (unsigned char)(((float)(255 * (i + 50))) / 100.0f);
58+
}
59+
else if (i < 150)
60+
{
61+
color.g = (unsigned char)(((float)(255 * (i - 50))) / 100.0f);
62+
color.b = 255;
63+
}
64+
else if (i < 250)
65+
{
66+
color.r = (unsigned char)(((float)(255 * (i - 150))) / 100.0f);
67+
color.g = 255;
68+
color.b = 255 - color.r;
69+
}
70+
else if (i < 350)
71+
{
72+
color.r = 255;
73+
color.g = (unsigned char)(((float)(255 * (350 - i))) / 100.0f);
74+
}
75+
else
76+
{
77+
color.r = (unsigned char)(((float)(255 * (450 - i))) / 100.0f);
78+
}
79+
cfd_color_map[i] = color;
80+
}
81+
}
82+
83+
CFD_RENDERER_API CFD_RENDERER_INLINE void cfd_lbm_2d_draw_line(cfd_pixel_color *buffer, int full_width, int y_offset, int plot_height, int x0, int y0, int x1, int y1, cfd_pixel_color color)
84+
{
85+
/* Simple Bresenham's line algorithm */
86+
int dx = cfd_abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
87+
int dy = -cfd_abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
88+
int err = dx + dy, e2;
89+
90+
int y_start = y_offset;
91+
int y_end = y_offset + plot_height;
92+
93+
for (;;)
94+
{
95+
if (x0 >= 0 && x0 < full_width && y0 >= y_start && y0 < y_end)
96+
{
97+
buffer[x0 + y0 * full_width] = color;
98+
}
99+
100+
if (x0 == x1 && y0 == y1)
101+
{
102+
break;
103+
}
104+
105+
e2 = 2 * err;
106+
107+
if (e2 >= dy)
108+
{
109+
err += dy;
110+
x0 += sx;
111+
}
112+
113+
if (e2 <= dx)
114+
{
115+
err += dx;
116+
y0 += sy;
117+
}
118+
}
119+
}
120+
121+
CFD_RENDERER_API CFD_RENDERER_INLINE void cfd_lbm_2d_draw_tracers(cfd_pixel_color *buffer, cfd_lbm_2d_grid *grid, int full_width, int y_offset, int pxPerSquare)
122+
{
123+
cfd_pixel_color color = {150, 150, 150};
124+
int plot_height = grid->ydim * pxPerSquare;
125+
126+
int t;
127+
128+
for (t = 0; t < CFD_LBM_2D_NUMBER_TRACERS; ++t)
129+
{
130+
int canvasX = (int)((grid->tracerX[t] + 0.5f) * (float)pxPerSquare);
131+
int canvasY = y_offset + (plot_height - 1 - (int)((grid->tracerY[t] + 0.5f) * (float)pxPerSquare));
132+
133+
int i;
134+
for (i = -1; i <= 1; ++i)
135+
{
136+
int j;
137+
for (j = -1; j <= 1; ++j)
138+
{
139+
int px = canvasX + i;
140+
int py = canvasY + j;
141+
if (px >= 0 && px < full_width && py >= y_offset && py < y_offset + plot_height)
142+
{
143+
buffer[px + py * full_width] = color;
144+
}
145+
}
146+
}
147+
}
148+
}
149+
150+
CFD_RENDERER_API CFD_RENDERER_INLINE void cfd_lbm_2d_draw_flowlines(cfd_pixel_color *buffer, cfd_lbm_2d_grid *grid, int full_width, int y_offset, int pxPerSquare)
151+
{
152+
int plot_height = grid->ydim * pxPerSquare;
153+
float sitesPerFlowline = 10.0f / (float)pxPerSquare;
154+
float sitesPerFlowlineHalf = sitesPerFlowline * 0.5f;
155+
156+
float y;
157+
float x;
158+
159+
cfd_pixel_color color = {80, 80, 80};
160+
161+
for (y = sitesPerFlowlineHalf; y < (float)grid->ydim; y += sitesPerFlowline)
162+
{
163+
for (x = sitesPerFlowlineHalf; x < (float)grid->xdim; x += sitesPerFlowline)
164+
{
165+
int ix = (int)x;
166+
int iy = (int)y;
167+
168+
float thisUx = grid->ux[ix + iy * grid->xdim];
169+
float thisUy = grid->uy[ix + iy * grid->xdim];
170+
171+
float speed = cfd_sqrtf(thisUx * thisUx + thisUy * thisUy);
172+
173+
if (speed > 0.0001f)
174+
{
175+
int px = (int)((x + 0.5f) * (float)pxPerSquare);
176+
int py = y_offset + (plot_height - 1 - (int)((y + 0.5f) * (float)pxPerSquare));
177+
float scale = 0.25f * (float)pxPerSquare * 10.0f / speed;
178+
int x1 = (int)((float)px - thisUx * scale);
179+
int y1 = (int)((float)py + thisUy * scale);
180+
int x2 = (int)((float)px + thisUx * scale);
181+
int y2 = (int)((float)py - thisUy * scale);
182+
183+
cfd_lbm_2d_draw_line(buffer, full_width, y_offset, plot_height, x1, y1, x2, y2, color);
184+
}
185+
}
186+
}
187+
}
188+
189+
CFD_RENDERER_API CFD_RENDERER_INLINE void cfd_lbm_2d_draw_force_arrow(cfd_pixel_color *buffer, cfd_lbm_2d_grid *grid, int full_width, int y_offset, int pxPerSquare)
190+
{
191+
192+
float x = grid->barrierxSum / (float)grid->barrierCount;
193+
float y = grid->barrierySum / (float)grid->barrierCount;
194+
float Fx = grid->barrierFx;
195+
float Fy = grid->barrierFy;
196+
int plot_height = grid->ydim * pxPerSquare;
197+
198+
int canvasX = (int)((x + 0.5f) * (float)pxPerSquare);
199+
int canvasY = y_offset + (plot_height - 1 - (int)((y + 0.5f) * (float)pxPerSquare));
200+
201+
float magF = cfd_sqrtf(Fx * Fx + Fy * Fy);
202+
203+
float scale = 4.0f * magF * 100.0f;
204+
int x1 = canvasX;
205+
int y1 = canvasY;
206+
int x2 = (int)((float)canvasX + Fx / magF * scale);
207+
int y2 = (int)((float)canvasY - Fy / magF * scale);
208+
209+
cfd_pixel_color color = {0, 0, 0};
210+
211+
/* Draw arrowhead */
212+
float angle = cfd_atan2f(-Fy, Fx);
213+
float arrowAngle = 25.0f * 3.14159f / 180.0f;
214+
float arrowLength = 0.2f * scale;
215+
int xA1 = (int)((float)x2 - arrowLength * cfd_cosf(angle - arrowAngle));
216+
int yA1 = (int)((float)y2 - arrowLength * cfd_sinf(angle - arrowAngle));
217+
int xA2 = (int)((float)x2 - arrowLength * cfd_cosf(angle + arrowAngle));
218+
int yA2 = (int)((float)y2 - arrowLength * cfd_sinf(angle + arrowAngle));
219+
220+
if (grid->barrierCount == 0)
221+
{
222+
return;
223+
}
224+
225+
if (magF < 1e-6f)
226+
{
227+
return;
228+
}
229+
230+
cfd_lbm_2d_draw_line(buffer, full_width, y_offset, plot_height, x1, y1, x2, y2, color);
231+
cfd_lbm_2d_draw_line(buffer, full_width, y_offset, plot_height, x2, y2, xA1, yA1, color);
232+
cfd_lbm_2d_draw_line(buffer, full_width, y_offset, plot_height, x2, y2, xA2, yA2, color);
233+
}
234+
235+
CFD_RENDERER_API CFD_RENDERER_INLINE void cfd_lbm_2d_draw_single_plot(cfd_pixel_color *buffer, cfd_lbm_2d_grid *grid, int width, int y_offset, int plotType, float contrast, int pxPerSquare, int tracerCheck, int flowlineCheck, int forceCheck)
236+
{
237+
float contrastFactor = cfd_powf(1.2f, contrast);
238+
239+
int y;
240+
int x;
241+
242+
/* Step 1: Draw the main fluid plot */
243+
for (y = 0; y < grid->ydim; ++y)
244+
{
245+
for (x = 0; x < grid->xdim; ++x)
246+
{
247+
cfd_pixel_color color = {0};
248+
249+
if (!grid->barrier[x + y * grid->xdim])
250+
{
251+
float value = 0.0f;
252+
int cIndex;
253+
254+
switch (plotType)
255+
{
256+
case 0:
257+
value = cfd_lbm_2d_calculate_density(grid, x, y);
258+
break;
259+
case 1:
260+
value = cfd_lbm_2d_calculate_velocity_x(grid, x, y);
261+
break;
262+
case 2:
263+
value = cfd_lbm_2d_calculate_velocity_y(grid, x, y);
264+
break;
265+
case 3:
266+
value = cfd_lbm_2d_calculate_speed(grid, x, y);
267+
break;
268+
case 4:
269+
value = cfd_lbm_2d_calculate_curl(grid, x, y);
270+
break;
271+
case 5:
272+
value = cfd_lbm_2d_calculate_pressure(grid, x, y);
273+
break;
274+
case 6:
275+
value = cfd_lbm_2d_calculate_wall_shear_stress(grid, x, y);
276+
break;
277+
}
278+
279+
cIndex = (int)(400 * (value * contrastFactor + 0.5f));
280+
cIndex = cIndex < 0 ? 0 : cIndex;
281+
cIndex = cIndex > 400 ? 400 : cIndex;
282+
283+
color = cfd_color_map[cIndex];
284+
}
285+
286+
/* Color the square in the buffer */
287+
{
288+
int flippedy = grid->ydim - y - 1;
289+
int py;
290+
291+
for (py = flippedy * pxPerSquare; py < (flippedy + 1) * pxPerSquare; ++py)
292+
{
293+
int base = (py + y_offset) * width;
294+
int px;
295+
296+
for (px = x * pxPerSquare; px < (x + 1) * pxPerSquare; ++px)
297+
{
298+
buffer[px + base] = color;
299+
}
300+
}
301+
}
302+
}
303+
}
304+
305+
/* Step 2: Draw overlays on top of the buffer */
306+
if (flowlineCheck)
307+
{
308+
cfd_lbm_2d_draw_flowlines(buffer, grid, width, y_offset, pxPerSquare);
309+
}
310+
if (tracerCheck)
311+
{
312+
cfd_lbm_2d_draw_tracers(buffer, grid, width, y_offset, pxPerSquare);
313+
}
314+
if (forceCheck)
315+
{
316+
cfd_lbm_2d_draw_force_arrow(buffer, grid, width, y_offset, pxPerSquare);
317+
}
318+
}
319+
320+
#endif /* CFD_RENDERER_H */
321+
322+
/*
323+
------------------------------------------------------------------------------
324+
This software is available under 2 licenses -- choose whichever you prefer.
325+
------------------------------------------------------------------------------
326+
ALTERNATIVE A - MIT License
327+
Copyright (c) 2025 nickscha
328+
Permission is hereby granted, free of charge, to any person obtaining a copy of
329+
this software and associated documentation files (the "Software"), to deal in
330+
the Software without restriction, including without limitation the rights to
331+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
332+
of the Software, and to permit persons to whom the Software is furnished to do
333+
so, subject to the following conditions:
334+
The above copyright notice and this permission notice shall be included in all
335+
copies or substantial portions of the Software.
336+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
337+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
338+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
339+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
340+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
341+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
342+
SOFTWARE.
343+
------------------------------------------------------------------------------
344+
ALTERNATIVE B - Public Domain (www.unlicense.org)
345+
This is free and unencumbered software released into the public domain.
346+
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
347+
software, either in source code form or as a compiled binary, for any purpose,
348+
commercial or non-commercial, and by any means.
349+
In jurisdictions that recognize copyright laws, the author or authors of this
350+
software dedicate any and all copyright interest in the software to the public
351+
domain. We make this dedication for the benefit of the public at large and to
352+
the detriment of our heirs and successors. We intend this dedication to be an
353+
overt act of relinquishment in perpetuity of all present and future rights to
354+
this software under copyright law.
355+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
356+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
357+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
358+
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
359+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
360+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
361+
------------------------------------------------------------------------------
362+
*/

0 commit comments

Comments
 (0)