├── .gitignore ├── .travis.yml ├── README.md ├── par_bluenoise.h ├── par_bubbles.h ├── par_camera_control.h ├── par_easings.h ├── par_easycurl.h ├── par_filecache.h ├── par_msquares.h ├── par_octasphere.h ├── par_shapes.h ├── par_sprune.h ├── par_streamlines.h ├── par_string_blocks.h ├── test ├── CMakeLists.txt ├── assertion-macros.h ├── boxes.py ├── cgltf.h ├── cgltf_write.h ├── console-colors.c ├── console-colors.h ├── describe.h ├── flare.json ├── gen_rgb_png.py ├── getcolors.py ├── hierarchy.py ├── lodepng.c ├── lodepng.h ├── lz4.c ├── lz4.cpp ├── lz4.h ├── msquares.html ├── msquares.svg ├── msquares_diagram.jpg ├── octasphere_color.png ├── octasphere_normal.png ├── octasphere_orm.png ├── packd3.html ├── par_asset.h ├── rgb.png ├── rgba.png ├── sds.c ├── sds.h ├── sdsalloc.h ├── test_bluenoise.c ├── test_bubbles.c ├── test_c.c ├── test_cpp.cpp ├── test_filecache.c ├── test_filecache_lz4.c ├── test_linkage.c ├── test_linkage.cpp ├── test_msquares.c ├── test_octasphere.cpp ├── test_shapes.c ├── test_sprune.c ├── test_strings.cpp ├── tverts.png ├── whereami.c └── whereami.h └── tools ├── .uncrustify ├── aliases.sh └── format.py /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.obj 3 | uncrustify 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | script: 3 | # uncrustify 4 | # - curl -L https://github.com/uncrustify/uncrustify/archive/uncrustify-0.70.0.tar.gz | tar -xz 5 | # - cd uncrustify-uncrustify-0.70.0 6 | # - ls 7 | # - cmake . -Bbuild 8 | # - cmake --build build 9 | # - cp build/uncrustify .. && cd .. 10 | # - ./uncrustify --version 11 | # - python tools/format.py --check 12 | # gcc 13 | - export CC=gcc CXX=g++ 14 | - cmake test -Bbuild 15 | - cmake --build build 16 | - ./build/test_shapes 17 | - rm -rf build 18 | - echo Switching from gcc to clang... 19 | # clang 20 | - export CC=clang CXX=clang++ 21 | - cmake test -Bbuild 22 | - cmake --build build 23 | # smoke tests 24 | - ./build/test_bubbles 25 | - ./build/test_shapes 26 | - ./build/test_filecache 27 | - ./build/test_filecache_lz4 28 | - ./build/test_sprune 29 | - ./build/test_strings 30 | - ./build/test_octasphere 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/prideout/par.svg?branch=master)](https://travis-ci.org/prideout/par) 2 | 3 | ## par 4 | 5 | Single-file C libraries under the MIT license, mostly graphics related. 6 | Documentation can be found at the top of each header file, but some libraries have an accompanying blog post. 7 | The most useful ones are listed in the following table. 8 | 9 | library | description | link 10 | ------------------- | ---- | --- 11 | **par_camera_control.h** | orbit controller, or pan-and-zoom like Google Maps | [demo project](https://github.com/prideout/camera_demo) 12 | **par_octasphere.h** | malloc-free mesh gen for spheres and rounded cuboids | [blog post](https://prideout.net/blog/octasphere) 13 | **par_streamlines.h** | triangulate wide lines and curves | [blog post](https://prideout.net/blog/par_streamlines/) 14 | **par_string_blocks.h** | string manager for snippets of Lua or GLSL | 15 | **par_shapes.h** | generate parametric surfaces and other simple shapes | [blog post](https://prideout.net/shapes) 16 | 17 | There are more libraries too but they're probably less useful; scroll to the bottom of this README. 18 | 19 | ## tests 20 | 21 | To run tests, you need CMake and pkg-config. On macOS, these can be installed with homebrew: 22 | 23 | ```bash 24 | $ brew install cmake pkg-config 25 | ``` 26 | 27 | Here's how you can tell CMake to use the CMakeLists in the `test` folder, placing all the messy stuff in a new folder called `build`. 28 | 29 | ```bash 30 | $ cmake test -Bbuild # Create makefiles 31 | $ cmake --build build # Invoke the build 32 | ``` 33 | 34 | The tests are executed by simply running the programs: 35 | ```bash 36 | $ build/test_bubbles 37 | $ build/test_shapes 38 | $ build/test_octasphere 39 | ``` 40 | 41 | ## code formatting 42 | 43 | This library's code style is strictly enforced to be vertically dense (no consecutive newlines) and 100 columns or less. 44 | 45 | The `tools/format.py` script invokes a two-step code formatting process: 46 | 47 | 1. Runs `uncrustify` with our custom configuration. This auto-formats all code in the root folder, up to a point. 48 | 1. Checks for violations that are not otherwise enforced with uncrustify. 49 | 50 | The aforementioned Python script is also invoked from Travis, but using the `--check` option, which checks for conformance without editing the code. 51 | 52 | Beyond what our uncrustify configuration enforces, the Python script does the following: 53 | 54 | - Checks that no lines are more than 100 chars. 55 | - Checks for extra newlines before an end brace. 56 | 57 | ## other libraries 58 | 59 | library | description | link 60 | ------------------- | ---- | --- 61 | **par_bluenoise.h** | generate progressive 2D point sequences | [blog post](https://prideout.net/recursive-wang-tiles) 62 | **par_bubbles.h** | pack circles into hierarchical diagrams | [blog post](https://prideout.net/bubbles) 63 | **par_easings.h** | Robert Penner's easing functions | 64 | **par_easycurl.h** | simple HTTP requests using libcurl | 65 | **par_filecache.h** | LRU caching on your device's filesystem | 66 | **par_sprune.h** | efficient broad-phase collision detection in 2D | [web demo](https://prideout.net/d3cpp/) 67 | **par_msquares.h** | unmaintained marching squares library (do not use) | [blog post](https://prideout.net/marching-squares) 68 | 69 | -------------------------------------------------------------------------------- /par_easings.h: -------------------------------------------------------------------------------- 1 | // EASINGS :: https://github.com/prideout/par 2 | // Robert Penner's easing functions. 3 | // 4 | // Distributed under the MIT License, see bottom of file. 5 | 6 | // ----------------------------------------------------------------------------- 7 | // BEGIN PUBLIC API 8 | // ----------------------------------------------------------------------------- 9 | 10 | #ifndef PAR_EASINGS_H 11 | #define PAR_EASINGS_H 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | #ifndef PAR_EASINGS_FLOAT 18 | #define PAR_EASINGS_FLOAT float 19 | #endif 20 | 21 | PAR_EASINGS_FLOAT par_easings_linear(PAR_EASINGS_FLOAT t); 22 | PAR_EASINGS_FLOAT par_easings_in_cubic(PAR_EASINGS_FLOAT t); 23 | PAR_EASINGS_FLOAT par_easings_out_cubic(PAR_EASINGS_FLOAT t); 24 | PAR_EASINGS_FLOAT par_easings_in_out_cubic(PAR_EASINGS_FLOAT t); 25 | PAR_EASINGS_FLOAT par_easings_in_quad(PAR_EASINGS_FLOAT t); 26 | PAR_EASINGS_FLOAT par_easings_out_quad(PAR_EASINGS_FLOAT t); 27 | PAR_EASINGS_FLOAT par_easings_in_out_quad(PAR_EASINGS_FLOAT t); 28 | PAR_EASINGS_FLOAT par_easings_in_elastic(PAR_EASINGS_FLOAT t); 29 | PAR_EASINGS_FLOAT par_easings_out_elastic(PAR_EASINGS_FLOAT t); 30 | PAR_EASINGS_FLOAT par_easings_in_out_elastic(PAR_EASINGS_FLOAT t); 31 | PAR_EASINGS_FLOAT par_easings_in_bounce(PAR_EASINGS_FLOAT t); 32 | PAR_EASINGS_FLOAT par_easings_out_bounce(PAR_EASINGS_FLOAT t); 33 | PAR_EASINGS_FLOAT par_easings_in_out_bounce(PAR_EASINGS_FLOAT t); 34 | PAR_EASINGS_FLOAT par_easings_in_back(PAR_EASINGS_FLOAT t); 35 | PAR_EASINGS_FLOAT par_easings_out_back(PAR_EASINGS_FLOAT t); 36 | PAR_EASINGS_FLOAT par_easings_in_out_back(PAR_EASINGS_FLOAT t); 37 | 38 | #ifndef PAR_PI 39 | #define PAR_PI (3.14159265359) 40 | #define PAR_MIN(a, b) (a > b ? b : a) 41 | #define PAR_MAX(a, b) (a > b ? a : b) 42 | #define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) 43 | #define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } 44 | #define PAR_SQR(a) ((a) * (a)) 45 | #endif 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | // ----------------------------------------------------------------------------- 52 | // END PUBLIC API 53 | // ----------------------------------------------------------------------------- 54 | 55 | #ifdef PAR_EASINGS_IMPLEMENTATION 56 | 57 | #define PARFLT PAR_EASINGS_FLOAT 58 | 59 | PARFLT par_easings__linear(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 60 | { 61 | return c * t / d + b; 62 | } 63 | 64 | PARFLT par_easings__in_cubic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 65 | { 66 | t /= d; 67 | return c * t * t * t + b; 68 | } 69 | 70 | PARFLT par_easings__out_cubic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 71 | { 72 | t = t / d - 1; 73 | return c * (t * t * t + 1) + b; 74 | } 75 | 76 | PARFLT par_easings__in_out_cubic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 77 | { 78 | t /= d / 2; 79 | if (t < 1) { 80 | return c / 2 * t * t * t + b; 81 | } 82 | t -= 2; 83 | return c / 2 * (t * t * t + 2) + b; 84 | } 85 | 86 | PARFLT par_easings__in_quad(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 87 | { 88 | t /= d; 89 | return c * t * t + b; 90 | } 91 | 92 | PARFLT par_easings__out_quad(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 93 | { 94 | t /= d; 95 | return -c * t * (t - 2) + b; 96 | } 97 | 98 | PARFLT par_easings__in_out_quad(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 99 | { 100 | t /= d / 2; 101 | if (t < 1) { 102 | return c / 2 * t * t + b; 103 | } 104 | --t; 105 | return -c / 2 * (t * (t - 2) - 1) + b; 106 | } 107 | 108 | PARFLT par_easings__in_quart(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 109 | { 110 | t /= d; 111 | return c * t * t * t * t + b; 112 | } 113 | 114 | PARFLT par_easings__out_quart(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 115 | { 116 | t = t / d - 1; 117 | return -c * (t * t * t * t - 1) + b; 118 | } 119 | 120 | PARFLT par_easings__in_out_quart(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 121 | { 122 | t /= d / 2; 123 | if (t < 1) { 124 | return c / 2 * t * t * t * t + b; 125 | } 126 | t -= 2; 127 | return -c / 2 * (t * t * t * t - 2) + b; 128 | } 129 | 130 | PARFLT par_easings__in_quint(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 131 | { 132 | t /= d; 133 | return c * t * t * t * t * t + b; 134 | } 135 | 136 | PARFLT par_easings__out_quint(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 137 | { 138 | t = t / d - 1; 139 | return c * (t * t * t * t * t + 1) + b; 140 | } 141 | 142 | PARFLT par_easings__in_out_quint(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 143 | { 144 | t /= d / 2; 145 | if (t < 1) { 146 | return c / 2 * t * t * t * t * t + b; 147 | } 148 | t -= 2; 149 | return c / 2 * (t * t * t * t * t + 2) + b; 150 | } 151 | 152 | PARFLT par_easings__in_sine(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 153 | { 154 | return -c * cos(t / d * (PAR_PI / 2)) + c + b; 155 | } 156 | 157 | PARFLT par_easings__out_sine(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 158 | { 159 | return c * sin(t / d * (PAR_PI / 2)) + b; 160 | } 161 | 162 | PARFLT par_easings__in_out_sine(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 163 | { 164 | return -c / 2 * (cos(PAR_PI * t / d) - 1) + b; 165 | } 166 | 167 | PARFLT par_easings__in_out_expo(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 168 | { 169 | t /= d / 2; 170 | if (t < 1) { 171 | return c / 2 * pow(2, 10 * (t - 1)) + b; 172 | } 173 | return c / 2 * (-pow(2, -10 * --t) + 2) + b; 174 | } 175 | 176 | PARFLT par_easings__in_circ(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 177 | { 178 | t /= d; 179 | return -c * (sqrt(1 - t * t) - 1) + b; 180 | } 181 | 182 | PARFLT par_easings__out_circ(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 183 | { 184 | t = t / d - 1; 185 | return c * sqrt(1 - t * t) + b; 186 | } 187 | 188 | PARFLT par_easings__in_out_circ(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 189 | { 190 | t /= d / 2; 191 | if (t < 1) { 192 | return -c / 2 * (sqrt(1 - t * t) - 1) + b; 193 | } 194 | t -= 2; 195 | return c / 2 * (sqrt(1 - t * t) + 1) + b; 196 | } 197 | 198 | PARFLT par_easings__in_elastic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 199 | { 200 | PARFLT a, p, s; 201 | s = 1.70158; 202 | p = 0; 203 | a = c; 204 | if (!p) { 205 | p = d * 0.3; 206 | } 207 | if (a < fabsf(c)) { 208 | a = c; 209 | s = p / 4; 210 | } else { 211 | s = p / (2 * PAR_PI) * asin(c / a); 212 | } 213 | t -= 1; 214 | return -(a * pow(2, 10 * t) * 215 | sin((t * d - s) * (2 * PAR_PI) / p)) + b; 216 | } 217 | 218 | PARFLT par_easings__out_elastic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 219 | { 220 | PARFLT a, p, s; 221 | s = 1.70158; 222 | p = 0; 223 | a = c; 224 | if (!p) { 225 | p = d * 0.3; 226 | } 227 | if (a < fabsf(c)) { 228 | a = c; 229 | s = p / 4; 230 | } else { 231 | s = p / (2 * PAR_PI) * asin(c / a); 232 | } 233 | return a * pow(2, -10 * t) * 234 | sin((t * d - s) * (2 * PAR_PI) / p) + c + b; 235 | } 236 | 237 | PARFLT par_easings__in_out_elastic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 238 | { 239 | PARFLT a, p, s; 240 | s = 1.70158; 241 | p = 0; 242 | a = c; 243 | if (!p) { 244 | p = d * (0.3 * 1.5); 245 | } 246 | if (a < fabsf(c)) { 247 | a = c; 248 | s = p / 4; 249 | } else { 250 | s = p / (2 * PAR_PI) * asin(c / a); 251 | } 252 | if (t < 1) { 253 | t -= 1; 254 | return -0.5 * (a * pow(2, 10 * t) * 255 | sin((t * d - s) * (2 * PAR_PI) / p)) + b; 256 | } 257 | t -= 1; 258 | return a * pow(2, -10 * t) * 259 | sin((t * d - s) * (2 * PAR_PI) / p) * 0.5 + c + b; 260 | } 261 | 262 | PARFLT par_easings__in_back(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 263 | { 264 | PARFLT s = 1.70158; 265 | t/=d; 266 | return c*t*t*((s+1)*t - s) + b; 267 | } 268 | 269 | PARFLT par_easings__out_back(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 270 | { 271 | PARFLT s = 1.70158; 272 | t=t/d-1; 273 | return c*(t*t*((s+1)*t + s) + 1) + b; 274 | } 275 | 276 | PARFLT par_easings__in_out_back(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 277 | { 278 | PARFLT s = 1.70158; 279 | t/=d/2; 280 | s*=1.525; 281 | if (t < 1) { return c/2*(t*t*((s+1)*t - s)) + b; } 282 | s*=1.525; 283 | t-=2; 284 | return (c/2*(t*t*(s+1)*t + s) + 2) + b; 285 | } 286 | 287 | PARFLT par_easings__out_bounce(PARFLT t, PARFLT b, PARFLT c, PARFLT d); 288 | 289 | PARFLT par_easings__in_bounce(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 290 | { 291 | PARFLT v = par_easings__out_bounce(d - t, 0, c, d); 292 | return c - v + b; 293 | } 294 | 295 | PARFLT par_easings__out_bounce(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 296 | { 297 | t /= d; 298 | if (t < 1.0 / 2.75) { 299 | return c * (7.5625 * t * t) + b; 300 | } 301 | if (t < 2.0 / 2.75) { 302 | t -= 1.5 / 2.75; 303 | return c * (7.5625 * t * t + 0.75) + b; 304 | } 305 | if (t < 2.5 / 2.75) { 306 | t -= 2.25 / 2.75; 307 | return c * (7.5625 * t * t + 0.9375) + b; 308 | } 309 | t -= 2.625 / 2.75; 310 | return c * (7.5625 * t * t + 0.984375) + b; 311 | } 312 | 313 | PARFLT par_easings__in_out_bounce(PARFLT t, PARFLT b, PARFLT c, PARFLT d) 314 | { 315 | PARFLT v; 316 | if (t < d / 2) { 317 | v = par_easings__in_bounce(t * 2, 0, c, d); 318 | return v * 0.5 + b; 319 | } 320 | v = par_easings__out_bounce(t * 2 - d, 0, c, d); 321 | return v * 0.5 + c * 0.5 + b; 322 | } 323 | 324 | PARFLT par_easings_linear(PARFLT t) 325 | { 326 | return par_easings__linear(t, 0, 1, 1); 327 | } 328 | 329 | PARFLT par_easings_in_cubic(PARFLT t) 330 | { 331 | return par_easings__in_cubic(t, 0, 1, 1); 332 | } 333 | 334 | PARFLT par_easings_out_cubic(PARFLT t) 335 | { 336 | return par_easings__out_cubic(t, 0, 1, 1); 337 | } 338 | 339 | PARFLT par_easings_in_out_cubic(PARFLT t) 340 | { 341 | return par_easings__in_out_cubic(t, 0, 1, 1); 342 | } 343 | 344 | PARFLT par_easings_in_quad(PARFLT t) 345 | { 346 | return par_easings__in_quad(t, 0, 1, 1); 347 | } 348 | 349 | PARFLT par_easings_out_quad(PARFLT t) 350 | { 351 | return par_easings__out_quad(t, 0, 1, 1); 352 | } 353 | 354 | PARFLT par_easings_in_out_quad(PARFLT t) 355 | { 356 | return par_easings__in_out_quad(t, 0, 1, 1); 357 | } 358 | 359 | PARFLT par_easings_in_elastic(PARFLT t) 360 | { 361 | return par_easings__in_elastic(t, 0, 1, 1); 362 | } 363 | 364 | PARFLT par_easings_out_elastic(PARFLT t) 365 | { 366 | return par_easings__out_elastic(t, 0, 1, 1); 367 | } 368 | 369 | PARFLT par_easings_in_out_elastic(PARFLT t) 370 | { 371 | return par_easings__in_out_elastic(t, 0, 1, 1); 372 | } 373 | 374 | PARFLT par_easings_in_bounce(PARFLT t) 375 | { 376 | return par_easings__in_bounce(t, 0, 1, 1); 377 | } 378 | 379 | PARFLT par_easings_out_bounce(PARFLT t) 380 | { 381 | return par_easings__out_bounce(t, 0, 1, 1); 382 | } 383 | 384 | PARFLT par_easings_in_out_bounce(PARFLT t) 385 | { 386 | return par_easings__in_out_bounce(t, 0, 1, 1); 387 | } 388 | 389 | PARFLT par_easings_in_back(PARFLT t) 390 | { 391 | return par_easings__in_back(t, 0, 1, 1); 392 | } 393 | 394 | PARFLT par_easings_out_back(PARFLT t) 395 | { 396 | return par_easings__out_back(t, 0, 1, 1); 397 | } 398 | 399 | PARFLT par_easings_in_out_back(PARFLT t) 400 | { 401 | return par_easings__in_out_back(t, 0, 1, 1); 402 | } 403 | 404 | #undef PARFLT 405 | 406 | #endif // PAR_EASINGS_IMPLEMENTATION 407 | #endif // PAR_EASINGS_H 408 | 409 | // par_easings is distributed under the MIT license: 410 | // 411 | // Copyright (c) 2019 Philip Rideout 412 | // 413 | // Permission is hereby granted, free of charge, to any person obtaining a copy 414 | // of this software and associated documentation files (the "Software"), to deal 415 | // in the Software without restriction, including without limitation the rights 416 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 417 | // copies of the Software, and to permit persons to whom the Software is 418 | // furnished to do so, subject to the following conditions: 419 | // 420 | // The above copyright notice and this permission notice shall be included in 421 | // all copies or substantial portions of the Software. 422 | // 423 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 424 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 425 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 426 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 427 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 428 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 429 | // SOFTWARE. 430 | -------------------------------------------------------------------------------- /par_easycurl.h: -------------------------------------------------------------------------------- 1 | // EASYCURL :: https://github.com/prideout/par 2 | // Wrapper around libcurl for performing simple synchronous HTTP requests. 3 | // 4 | // Distributed under the MIT License, see bottom of file. 5 | 6 | // ----------------------------------------------------------------------------- 7 | // BEGIN PUBLIC API 8 | // ----------------------------------------------------------------------------- 9 | 10 | #ifndef PAR_EASYCURL_H 11 | #define PAR_EASYCURL_H 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | typedef unsigned char par_byte; 18 | 19 | // Call this before calling any other easycurl function. The flags are 20 | // currently unused, so you can just pass 0. 21 | void par_easycurl_init(unsigned int flags); 22 | 23 | // Allocates a memory buffer and downloads a data blob into it. 24 | // Returns 1 for success and 0 otherwise. The byte count should be 25 | // pre-allocated. The caller is responsible for freeing the returned data. 26 | // This does not do any caching! 27 | int par_easycurl_to_memory(char const* url, par_byte** data, int* nbytes); 28 | 29 | // Downloads a file from the given URL and saves it to disk. Returns 1 for 30 | // success and 0 otherwise. 31 | int par_easycurl_to_file(char const* srcurl, char const* dstpath); 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif 36 | 37 | // ----------------------------------------------------------------------------- 38 | // END PUBLIC API 39 | // ----------------------------------------------------------------------------- 40 | 41 | #ifdef PAR_EASYCURL_IMPLEMENTATION 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #ifdef _MSC_VER 49 | #define strncasecmp _strnicmp 50 | #define strcasecmp _stricmp 51 | #else 52 | #include 53 | #endif 54 | 55 | static int _ready = 0; 56 | 57 | void par_easycurl_init(unsigned int flags) 58 | { 59 | if (!_ready) { 60 | curl_global_init(CURL_GLOBAL_DEFAULT); 61 | _ready = 1; 62 | } 63 | } 64 | 65 | void par_easycurl_shutdown() 66 | { 67 | if (_ready) { 68 | curl_global_cleanup(); 69 | } 70 | } 71 | 72 | static size_t onheader(void* v, size_t size, size_t nmemb) 73 | { 74 | size_t n = size * nmemb; 75 | char* h = (char*) v; 76 | if (n > 14 && !strncasecmp("Last-Modified:", h, 14)) { 77 | char const* s = h + 14; 78 | time_t r = curl_getdate(s, 0); 79 | if (r != -1) { 80 | // TODO handle last-modified 81 | } 82 | } else if (n > 5 && !strncasecmp("ETag:", h, 5)) { 83 | // TODO handle etag 84 | } 85 | return n; 86 | } 87 | 88 | typedef struct { 89 | par_byte* data; 90 | int nbytes; 91 | } par_easycurl_buffer; 92 | 93 | static size_t onwrite(char* contents, size_t size, size_t nmemb, void* udata) 94 | { 95 | size_t realsize = size * nmemb; 96 | par_easycurl_buffer* mem = (par_easycurl_buffer*) udata; 97 | mem->data = (par_byte*) realloc(mem->data, mem->nbytes + realsize + 1); 98 | if (!mem->data) { 99 | return 0; 100 | } 101 | memcpy(mem->data + mem->nbytes, contents, realsize); 102 | mem->nbytes += realsize; 103 | mem->data[mem->nbytes] = 0; 104 | return realsize; 105 | } 106 | 107 | #if IOS_EXAMPLE 108 | bool curlToMemory(char const* url, uint8_t** data, int* nbytes) 109 | { 110 | NSString* nsurl = 111 | [NSString stringWithCString:url encoding:NSASCIIStringEncoding]; 112 | NSMutableURLRequest* request = 113 | [NSMutableURLRequest requestWithURL:[NSURL URLWithString:nsurl]]; 114 | [request setTimeoutInterval: TIMEOUT_SECONDS]; 115 | NSURLResponse* response = nil; 116 | NSError* error = nil; 117 | // Use the simple non-async API because we're in a secondary thread anyway. 118 | NSData* nsdata = [NSURLConnection sendSynchronousRequest:request 119 | returningResponse:&response 120 | error:&error]; 121 | if (error == nil) { 122 | *nbytes = (int) [nsdata length]; 123 | *data = (uint8_t*) malloc([nsdata length]); 124 | memcpy(*data, [nsdata bytes], [nsdata length]); 125 | return true; 126 | } 127 | BLAZE_ERROR("%s\n", [[error localizedDescription] UTF8String]); 128 | return false; 129 | } 130 | 131 | #endif 132 | 133 | int par_easycurl_to_memory(char const* url, par_byte** data, int* nbytes) 134 | { 135 | char errbuf[CURL_ERROR_SIZE] = {0}; 136 | par_easycurl_buffer buffer = {(par_byte*) malloc(1), 0}; 137 | long code = 0; 138 | long status = 0; 139 | CURL* handle = curl_easy_init(); 140 | curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1); 141 | curl_easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate"); 142 | curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1); 143 | curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 8); 144 | curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1); 145 | curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, onwrite); 146 | curl_easy_setopt(handle, CURLOPT_WRITEDATA, &buffer); 147 | curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, onheader); 148 | curl_easy_setopt(handle, CURLOPT_URL, url); 149 | curl_easy_setopt(handle, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); 150 | curl_easy_setopt(handle, CURLOPT_TIMEVALUE, 0); 151 | curl_easy_setopt(handle, CURLOPT_HTTPHEADER, 0); 152 | curl_easy_setopt(handle, CURLOPT_TIMEOUT, 60); 153 | curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errbuf); 154 | curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0); 155 | CURLcode res = curl_easy_perform(handle); 156 | if (res != CURLE_OK) { 157 | printf("CURL Error: %s\n", errbuf); 158 | return 0; 159 | } 160 | curl_easy_getinfo(handle, CURLINFO_CONDITION_UNMET, &code); 161 | curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status); 162 | if (status == 304 || status >= 400) { 163 | return 0; 164 | } 165 | *data = buffer.data; 166 | *nbytes = buffer.nbytes; 167 | curl_easy_cleanup(handle); 168 | return 1; 169 | } 170 | 171 | int par_easycurl_to_file(char const* srcurl, char const* dstpath) 172 | { 173 | long code = 0; 174 | long status = 0; 175 | FILE* filehandle = fopen(dstpath, "wb"); 176 | if (!filehandle) { 177 | printf("Unable to open %s for writing.\n", dstpath); 178 | return 0; 179 | } 180 | CURL* handle = curl_easy_init(); 181 | curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1); 182 | curl_easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate"); 183 | curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1); 184 | curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 8); 185 | curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1); 186 | curl_easy_setopt(handle, CURLOPT_WRITEDATA, filehandle); 187 | curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, onheader); 188 | curl_easy_setopt(handle, CURLOPT_URL, srcurl); 189 | curl_easy_setopt(handle, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); 190 | curl_easy_setopt(handle, CURLOPT_TIMEVALUE, 0); 191 | curl_easy_setopt(handle, CURLOPT_HTTPHEADER, 0); 192 | curl_easy_setopt(handle, CURLOPT_TIMEOUT, 60); 193 | curl_easy_perform(handle); 194 | curl_easy_getinfo(handle, CURLINFO_CONDITION_UNMET, &code); 195 | curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status); 196 | fclose(filehandle); 197 | if (status == 304 || status >= 400) { 198 | remove(dstpath); 199 | return 0; 200 | } 201 | curl_easy_cleanup(handle); 202 | return 1; 203 | } 204 | 205 | #endif // PAR_EASYCURL_IMPLEMENTATION 206 | #endif // PAR_EASYCURL_H 207 | 208 | // par_easycurl is distributed under the MIT license: 209 | // 210 | // Copyright (c) 2019 Philip Rideout 211 | // 212 | // Permission is hereby granted, free of charge, to any person obtaining a copy 213 | // of this software and associated documentation files (the "Software"), to deal 214 | // in the Software without restriction, including without limitation the rights 215 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 216 | // copies of the Software, and to permit persons to whom the Software is 217 | // furnished to do so, subject to the following conditions: 218 | // 219 | // The above copyright notice and this permission notice shall be included in 220 | // all copies or substantial portions of the Software. 221 | // 222 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 223 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 224 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 225 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 226 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 227 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 228 | // SOFTWARE. 229 | -------------------------------------------------------------------------------- /par_filecache.h: -------------------------------------------------------------------------------- 1 | // FILECACHE :: https://github.com/prideout/par 2 | // Simple file-based LRU cache for blobs with content-addressable names. 3 | // 4 | // Each cached item is stored on disk as "{PREFIX}{NAME}", where {PREFIX} 5 | // is passed in when initializing the cache. You'll probably want to specify 6 | // a folder path for your prefix, including the trailing slash. 7 | // 8 | // Each item is divided into a payload (arbitrary size) and an optional header 9 | // (fixed size). The structure of the payload and header are completely up to 10 | // you. The list of items is stored in a text file at "{PREFIX}table", which 11 | // contains a list of names, timestamps, and byte counts. This table is loaded 12 | // only once, but is saved every time the client fetches a blob from the cache, 13 | // so that the most-recently-accessed timestamps are always up to date, even 14 | // when your application doesn't close gracefully. 15 | // 16 | // Distributed under the MIT License, see bottom of file. 17 | 18 | // ----------------------------------------------------------------------------- 19 | // BEGIN PUBLIC API 20 | // ----------------------------------------------------------------------------- 21 | 22 | #ifndef PAR_FILECACHE_H 23 | #define PAR_FILECACHE_H 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | #include 30 | #include 31 | 32 | // Initialize the filecache using the given prefix (usually a folder path with 33 | // a trailing slash) and the given maximum byte count. If items already exist 34 | // in the cache when this is called, they are not evicted. Cached items are 35 | // meant to persist from run to run. 36 | void par_filecache_init(char const* prefix, int maxsize); 37 | 38 | // Save a blob to the cache using the given unique name. If adding the blob 39 | // would cause the cache to exceed maxsize, the least-recently-used item is 40 | // evicted at this time. 41 | void par_filecache_save(char const* name, uint8_t const* payload, 42 | int payloadsize, uint8_t const* header, int headersize); 43 | 44 | // Check if the given blob is in the cache; if not, return 0. If so, return 1 45 | // and allocate new memory for payload. The caller should free the payload. 46 | // The header is preallocated so the caller needs to know its size beforehand. 47 | bool par_filecache_load(char const* name, uint8_t** payload, int* payloadsize, 48 | uint8_t* header, int headersize); 49 | 50 | // Remove all items from the cache. 51 | void par_filecache_evict_all(); 52 | 53 | // Set this to zero if you wish to avoid LZ4 compression. I recommend using 54 | // it though, because it's very fast and it's a two-file library. 55 | #ifndef ENABLE_LZ4 56 | #define ENABLE_LZ4 0 57 | #endif 58 | 59 | #ifndef PAR_FILECACHE_VERBOSE 60 | #define PAR_FILECACHE_VERBOSE 0 61 | #endif 62 | 63 | #ifndef PAR_PI 64 | #define PAR_PI (3.14159265359) 65 | #define PAR_MIN(a, b) (a > b ? b : a) 66 | #define PAR_MAX(a, b) (a > b ? a : b) 67 | #define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) 68 | #define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } 69 | #define PAR_SQR(a) ((a) * (a)) 70 | #endif 71 | 72 | #ifndef PAR_MALLOC 73 | #define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) 74 | #define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) 75 | #define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N))) 76 | #define PAR_FREE(BUF) free(BUF) 77 | #endif 78 | 79 | #ifdef __cplusplus 80 | } 81 | #endif 82 | 83 | // ----------------------------------------------------------------------------- 84 | // END PUBLIC API 85 | // ----------------------------------------------------------------------------- 86 | 87 | #ifdef PAR_FILECACHE_IMPLEMENTATION 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | #include 99 | 100 | #ifndef PATH_MAX 101 | #define PATH_MAX 4096 102 | #endif 103 | 104 | #if ENABLE_LZ4 105 | #include "lz4.h" 106 | #endif 107 | 108 | static char * _par_strdup(char const* s) 109 | { 110 | if (s) { 111 | size_t l = strlen(s); 112 | char *s2 = (char*) malloc(l + 1); 113 | if (s2) { 114 | strcpy(s2, s); 115 | } 116 | return s2; 117 | } 118 | return 0; 119 | } 120 | 121 | #define PAR_MAX_ENTRIES 64 122 | 123 | typedef struct { 124 | time_t last_used_timestamp; 125 | uint64_t hashed_name; 126 | char const* name; 127 | int nbytes; 128 | } filecache_entry_t; 129 | 130 | typedef struct { 131 | filecache_entry_t entries[PAR_MAX_ENTRIES]; 132 | int nentries; 133 | int totalbytes; 134 | } filecache_table_t; 135 | 136 | static void _update_table(char const* item_name, int item_size); 137 | static void _append_table(char const* item_name, int item_size); 138 | static void _read_or_create_tablefile(); 139 | static void _save_tablefile(); 140 | static void _evict_lru(); 141 | static uint64_t _hash(char const* name); 142 | 143 | static char _fileprefix[PATH_MAX] = "./_cache."; 144 | static char _tablepath[PATH_MAX] = "./_cache.table"; 145 | static int _maxtotalbytes = 1024 * 1024 * 16; 146 | static filecache_table_t* _table = 0; 147 | 148 | void par_filecache_init(char const* prefix, int maxsize) 149 | { 150 | size_t len = strlen(prefix); 151 | assert(len + 1 < PATH_MAX && "Cache prefix is too long"); 152 | strncpy(_fileprefix, prefix, len + 1); 153 | strcpy(_tablepath, _fileprefix); 154 | strcat(_tablepath, "table"); 155 | _maxtotalbytes = maxsize; 156 | } 157 | 158 | #if IOS_EXAMPLE 159 | NSString* getPrefix() 160 | { 161 | NSString* cachesFolder = [NSSearchPathForDirectoriesInDomains( 162 | NSCachesDirectory, NSUserDomainMask, YES) firstObject]; 163 | NSError* error = nil; 164 | if (![[NSFileManager defaultManager] createDirectoryAtPath : cachesFolder 165 | withIntermediateDirectories : YES 166 | attributes : nil 167 | error : &error]) { 168 | NSLog(@"MGMPlatformGetCachesFolder error: %@", error); 169 | return nil; 170 | } 171 | return [cachesFolder stringByAppendingString : @"/_cache."]; 172 | } 173 | 174 | #endif 175 | 176 | static bool par_filecache__read(void* dest, int nbytes, FILE* file) 177 | { 178 | int consumed = (int) fread(dest, nbytes, 1, file); 179 | return consumed == 1; 180 | } 181 | 182 | bool par_filecache_load(char const* name, uint8_t** payload, int* payloadsize, 183 | uint8_t* header, int headersize) 184 | { 185 | char qualified[PATH_MAX]; 186 | size_t len = strlen(name); 187 | if (len == 0) { 188 | return false; 189 | } 190 | assert(len + strlen(_fileprefix) < PATH_MAX); 191 | strcpy(qualified, _fileprefix); 192 | strcat(qualified, name); 193 | if (access(qualified, F_OK) == -1) { 194 | return false; 195 | } 196 | FILE* cachefile = fopen(qualified, "rb"); 197 | assert(cachefile && "Unable to open cache file for reading"); 198 | fseek(cachefile, 0, SEEK_END); 199 | long fsize = ftell(cachefile); 200 | fseek(cachefile, 0, SEEK_SET); 201 | if (headersize > 0 && !par_filecache__read(header, headersize, cachefile)) { 202 | return false; 203 | } 204 | int32_t dnbytes; 205 | #if ENABLE_LZ4 206 | long cnbytes = fsize - headersize - sizeof(dnbytes); 207 | if (!par_filecache__read(&dnbytes, sizeof(dnbytes), cachefile)) { 208 | return false; 209 | } 210 | #else 211 | long cnbytes = fsize - headersize; 212 | dnbytes = (int32_t) cnbytes; 213 | #endif 214 | char* cbuff = (char*) malloc(cnbytes); 215 | if (!par_filecache__read(cbuff, (int) cnbytes, cachefile)) { 216 | return false; 217 | } 218 | #if ENABLE_LZ4 219 | char* dbuff = (char*) malloc(dnbytes); 220 | LZ4_decompress_safe(cbuff, dbuff, (int) cnbytes, dnbytes); 221 | free(cbuff); 222 | #else 223 | char* dbuff = cbuff; 224 | #endif 225 | fclose(cachefile); 226 | *payload = (uint8_t*) dbuff; 227 | *payloadsize = dnbytes; 228 | _update_table(name, (int) cnbytes); 229 | return true; 230 | } 231 | 232 | void par_filecache_save(char const* name, uint8_t const* payload, 233 | int payloadsize, uint8_t const* header, int headersize) 234 | { 235 | char qualified[PATH_MAX]; 236 | size_t len = strlen(name); 237 | if (len == 0) { 238 | return; 239 | } 240 | assert(len + strlen(_fileprefix) < PATH_MAX); 241 | strcpy(qualified, _fileprefix); 242 | strcat(qualified, name); 243 | FILE* cachefile = fopen(qualified, "wb"); 244 | assert(cachefile && "Unable to open cache file for writing"); 245 | if (headersize > 0) { 246 | fwrite(header, 1, headersize, cachefile); 247 | } 248 | int csize = 0; 249 | if (payloadsize > 0) { 250 | #if ENABLE_LZ4 251 | int32_t nbytes = payloadsize; 252 | fwrite(&nbytes, 1, sizeof(nbytes), cachefile); 253 | int maxsize = LZ4_compressBound(nbytes); 254 | char* dst = (char*) malloc(maxsize); 255 | char const* src = (char const*) payload; 256 | assert(nbytes < LZ4_MAX_INPUT_SIZE); 257 | csize = LZ4_compress_default(src, dst, nbytes, maxsize); 258 | fwrite(dst, 1, csize, cachefile); 259 | free(dst); 260 | #else 261 | csize = payloadsize; 262 | int actual = (int) fwrite(payload, 1, csize, cachefile); 263 | if (actual < csize) { 264 | fclose(cachefile); 265 | remove(qualified); 266 | printf("Unable to save %s to cache (%d bytes)\n", name, csize); 267 | return; 268 | } 269 | #endif 270 | } 271 | fclose(cachefile); 272 | _update_table(name, csize + headersize); 273 | } 274 | 275 | void par_filecache_evict_all() 276 | { 277 | #if PAR_FILECACHE_VERBOSE 278 | printf("Evicting all.\n"); 279 | #endif 280 | char qualified[PATH_MAX]; 281 | if (!_table) { 282 | _read_or_create_tablefile(); 283 | } 284 | filecache_entry_t* entry = _table->entries; 285 | for (int i = 0; i < _table->nentries; i++, entry++) { 286 | strcpy(qualified, _fileprefix); 287 | strcat(qualified, entry->name); 288 | #if PAR_FILECACHE_VERBOSE 289 | printf("Evicting %s\n", qualified); 290 | #endif 291 | remove(qualified); 292 | } 293 | _table->nentries = 0; 294 | _table->totalbytes = 0; 295 | remove(_tablepath); 296 | } 297 | 298 | // Adds the given item to the table and evicts the LRU items if the total cache 299 | // size exceeds the specified maxsize. 300 | static void _append_table(char const* item_name, int item_size) 301 | { 302 | time_t now = time(0); 303 | if (!_table) { 304 | _read_or_create_tablefile(); 305 | } 306 | uint64_t hashed_name = _hash(item_name); 307 | int total = _table->totalbytes + item_size; 308 | while (_table->nentries >= PAR_MAX_ENTRIES || total > _maxtotalbytes) { 309 | assert(_table->nentries > 0 && "Cache size is too small."); 310 | _evict_lru(); 311 | total = _table->totalbytes + item_size; 312 | } 313 | _table->totalbytes = total; 314 | filecache_entry_t* entry = &_table->entries[_table->nentries++]; 315 | entry->last_used_timestamp = now; 316 | entry->hashed_name = hashed_name; 317 | entry->name = _par_strdup(item_name); 318 | entry->nbytes = item_size; 319 | _save_tablefile(); 320 | } 321 | 322 | // Updates the timestamp associated with the given item. 323 | static void _update_table(char const* item_name, int item_size) 324 | { 325 | time_t now = time(0); 326 | if (!_table) { 327 | _read_or_create_tablefile(); 328 | } 329 | uint64_t hashed_name = _hash(item_name); 330 | filecache_entry_t* entry = _table->entries; 331 | int i; 332 | for (i = 0; i < _table->nentries; i++, entry++) { 333 | if (entry->hashed_name == hashed_name) { 334 | break; 335 | } 336 | } 337 | if (i >= _table->nentries) { 338 | _append_table(item_name, item_size); 339 | return; 340 | } 341 | entry->last_used_timestamp = now; 342 | _save_tablefile(); 343 | } 344 | 345 | static void _read_or_create_tablefile() 346 | { 347 | _table = (filecache_table_t*) calloc(sizeof(filecache_table_t), 1); 348 | FILE* fhandle = fopen(_tablepath, "r"); 349 | if (!fhandle) { 350 | fhandle = fopen(_tablepath, "w"); 351 | if (!fhandle) { 352 | mkdir(dirname(_tablepath), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 353 | fhandle = fopen(_tablepath, "w"); 354 | } 355 | assert(fhandle && "Unable to create filecache info file."); 356 | } else { 357 | filecache_entry_t entry; 358 | char name[PATH_MAX]; 359 | while (1) { 360 | int nargs = fscanf(fhandle, "%ld %d %s", &entry.last_used_timestamp, 361 | &entry.nbytes, name); 362 | if (nargs != 3) { 363 | break; 364 | } 365 | entry.name = _par_strdup(name); 366 | entry.hashed_name = _hash(entry.name); 367 | _table->entries[_table->nentries++] = entry; 368 | _table->totalbytes += entry.nbytes; 369 | } 370 | } 371 | fclose(fhandle); 372 | } 373 | 374 | static void _save_tablefile() 375 | { 376 | FILE* fhandle = fopen(_tablepath, "w"); 377 | assert(fhandle && "Unable to create filecache info file."); 378 | filecache_entry_t* entry = _table->entries; 379 | for (int i = 0; i < _table->nentries; i++, entry++) { 380 | fprintf(fhandle, "%ld %d %s\n", entry->last_used_timestamp, 381 | entry->nbytes, entry->name); 382 | } 383 | fclose(fhandle); 384 | } 385 | 386 | static void _evict_lru() 387 | { 388 | const uint64_t never_evict = _hash("version"); 389 | int oldest_index = -1; 390 | time_t oldest_time = LONG_MAX; 391 | filecache_entry_t* entry = _table->entries; 392 | for (int i = 0; i < _table->nentries; i++, entry++) { 393 | if (entry->hashed_name == never_evict) { 394 | continue; 395 | } 396 | if (entry->last_used_timestamp < oldest_time) { 397 | oldest_time = entry->last_used_timestamp; 398 | oldest_index = i; 399 | } 400 | } 401 | if (oldest_index > -1) { 402 | entry = _table->entries + oldest_index; 403 | char qualified[PATH_MAX]; 404 | size_t len = strlen(entry->name); 405 | assert(len + strlen(_fileprefix) < PATH_MAX); 406 | strcpy(qualified, _fileprefix); 407 | strcat(qualified, entry->name); 408 | #if PAR_FILECACHE_VERBOSE 409 | printf("Evicting %s\n", entry->name); 410 | #endif 411 | remove(qualified); 412 | _table->totalbytes -= entry->nbytes; 413 | if (_table->nentries-- > 1) { 414 | *entry = _table->entries[_table->nentries]; 415 | } 416 | } 417 | } 418 | 419 | // https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function 420 | 421 | static uint64_t _hash(char const* name) 422 | { 423 | const uint64_t OFFSET = 14695981039346656037ull; 424 | const uint64_t PRIME = 1099511628211ull; 425 | const unsigned char* str = (const unsigned char*) name; 426 | uint64_t hval = OFFSET; 427 | while (*str) { 428 | hval *= PRIME; 429 | hval ^= (uint64_t) *(str++); 430 | } 431 | return hval; 432 | } 433 | 434 | #undef PAR_MAX_ENTRIES 435 | #endif // PAR_FILECACHE_IMPLEMENTATION 436 | #endif // PAR_FILECACHE_H 437 | 438 | // par_filecache is distributed under the MIT license: 439 | // 440 | // Copyright (c) 2019 Philip Rideout 441 | // 442 | // Permission is hereby granted, free of charge, to any person obtaining a copy 443 | // of this software and associated documentation files (the "Software"), to deal 444 | // in the Software without restriction, including without limitation the rights 445 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 446 | // copies of the Software, and to permit persons to whom the Software is 447 | // furnished to do so, subject to the following conditions: 448 | // 449 | // The above copyright notice and this permission notice shall be included in 450 | // all copies or substantial portions of the Software. 451 | // 452 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 453 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 454 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 455 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 456 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 457 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 458 | // SOFTWARE. 459 | -------------------------------------------------------------------------------- /par_sprune.h: -------------------------------------------------------------------------------- 1 | // SPRUNE :: https://github.com/prideout/par 2 | // Sweep and Prune library for detecting axis-aligned box collisions in 2D. 3 | // 4 | // For an emscripten demo of this library, take a look at the following link. 5 | // 6 | // https://prideout.net/d3cpp 7 | // 8 | // The axis-aligned bounding boxes are specified by (minx, miny, maxx, maxy). 9 | // Simple usage example: 10 | // 11 | // float boxes[] = { 12 | // 0.10, 0.10, 0.30, 0.30, // box 0 13 | // 0.20, 0.20, 0.40, 0.40, // box 1 14 | // 0.60, 0.15, 0.70, 0.25, // box 2 15 | // }; 16 | // int nboxes = 3; 17 | // par_sprune_context* ctx = par_sprune_overlap(boxes, nboxes, 0); 18 | // int const* pairs = ctx->collision_pairs; 19 | // for (int i = 0; i < ctx->ncollision_pairs * 2; i += 2) { 20 | // printf("box %d overlaps box %d\n", pairs[i], pairs[i + 1]); 21 | // } 22 | // par_sprune_free_context(ctx); 23 | // 24 | // Distributed under the MIT License, see bottom of file. 25 | 26 | #ifndef PAR_SPRUNE_H 27 | #define PAR_SPRUNE_H 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | #include 34 | #include 35 | 36 | #ifndef PAR_SPRUNE_INT 37 | #define PAR_SPRUNE_INT int32_t 38 | #endif 39 | 40 | #ifndef PAR_SPRUNE_FLT 41 | #define PAR_SPRUNE_FLT float 42 | #endif 43 | 44 | // ----------------------------------------------------------------------------- 45 | // BEGIN PUBLIC API 46 | // ----------------------------------------------------------------------------- 47 | 48 | typedef struct { 49 | PAR_SPRUNE_INT const* const collision_pairs; // list of two-tuples 50 | PAR_SPRUNE_INT const ncollision_pairs; // number of two-tuples 51 | PAR_SPRUNE_INT const* const culled; // filled by par_sprune_cull 52 | PAR_SPRUNE_INT const nculled; // set by par_sprune_cull 53 | } par_sprune_context; 54 | 55 | void par_sprune_free_context(par_sprune_context* context); 56 | 57 | // Takes an array of 4-tuples (minx miny maxx maxy) and performs SaP. Populates 58 | // "collision_pairs" and "ncollision_pairs". Optionally takes an existing 59 | // context to avoid memory churn; pass NULL for initial construction. 60 | par_sprune_context* par_sprune_overlap(PAR_SPRUNE_FLT const* aabbs, 61 | PAR_SPRUNE_INT naabbs, par_sprune_context* previous); 62 | 63 | // Reads new aabb data from the same pointer that was passed to the overlap 64 | // function and refreshes the two relevant fields. This function should 65 | // only be used when the number of aabbs remains constant. If this returns 66 | // false, no changes to the collision set were detected. 67 | bool par_sprune_update(par_sprune_context* ctx); 68 | 69 | // Examines all collision groups and creates a culling set such that no boxes 70 | // would overlap if the culled boxes are removed. When two boxes collide, the 71 | // box that occurs earlier in the list is more likely to be culled. Populates 72 | // the "culled" and "nculled" fields in par_sprune_context. This is useful for 73 | // hiding labels in GIS applications. 74 | void par_sprune_cull(par_sprune_context* context); 75 | 76 | // ----------------------------------------------------------------------------- 77 | // END PUBLIC API 78 | // ----------------------------------------------------------------------------- 79 | 80 | #ifdef __cplusplus 81 | } 82 | #endif 83 | 84 | #ifdef PAR_SPRUNE_IMPLEMENTATION 85 | #define PARINT PAR_SPRUNE_INT 86 | #define PARFLT PAR_SPRUNE_FLT 87 | 88 | #include 89 | #include 90 | 91 | #ifndef PAR_PI 92 | #define PAR_PI (3.14159265359) 93 | #define PAR_MIN(a, b) (a > b ? b : a) 94 | #define PAR_MAX(a, b) (a > b ? a : b) 95 | #define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) 96 | #define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } 97 | #define PAR_SQR(a) ((a) * (a)) 98 | #endif 99 | 100 | #ifndef PAR_MALLOC 101 | #define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) 102 | #define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) 103 | #define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N))) 104 | #define PAR_FREE(BUF) free(BUF) 105 | #endif 106 | 107 | #ifndef PAR_ARRAY 108 | #define PAR_ARRAY 109 | #define pa_free(a) ((a) ? PAR_FREE(pa___raw(a)), 0 : 0) 110 | #define pa_push(a, v) (pa___maybegrow(a, (int) 1), (a)[pa___n(a)++] = (v)) 111 | #define pa_count(a) ((a) ? pa___n(a) : 0) 112 | #define pa_add(a, n) (pa___maybegrow(a, (int) n), pa___n(a) += (n)) 113 | #define pa_last(a) ((a)[pa___n(a) - 1]) 114 | #define pa_end(a) (a + pa_count(a)) 115 | #define pa_clear(arr) if (arr) pa___n(arr) = 0 116 | #define pa___raw(a) ((int*) (a) -2) 117 | #define pa___m(a) pa___raw(a)[0] 118 | #define pa___n(a) pa___raw(a)[1] 119 | #define pa___needgrow(a, n) ((a) == 0 || pa___n(a) + ((int) n) >= pa___m(a)) 120 | #define pa___maybegrow(a, n) (pa___needgrow(a, (n)) ? pa___grow(a, n) : 0) 121 | #define pa___grow(a, n) (*((void**)& (a)) = pa___growf((void*) (a), (n), \ 122 | sizeof(*(a)))) 123 | 124 | // ptr[-2] is capacity, ptr[-1] is size. 125 | static void* pa___growf(void* arr, int increment, int itemsize) 126 | { 127 | int dbl_cur = arr ? 2 * pa___m(arr) : 0; 128 | int min_needed = pa_count(arr) + increment; 129 | int m = dbl_cur > min_needed ? dbl_cur : min_needed; 130 | int* p = (int *) PAR_REALLOC(uint8_t, arr ? pa___raw(arr) : 0, 131 | itemsize * m + sizeof(int) * 2); 132 | if (p) { 133 | if (!arr) { 134 | p[1] = 0; 135 | } 136 | p[0] = m; 137 | return p + 2; 138 | } 139 | return (void*) (2 * sizeof(int)); 140 | } 141 | 142 | #endif 143 | 144 | typedef struct { 145 | 146 | // Public: 147 | PARINT* collision_pairs; 148 | PARINT ncollision_pairs; 149 | PARINT* culled; 150 | PARINT nculled; 151 | 152 | // Private: 153 | PARFLT const* aabbs; 154 | PARINT naabbs; 155 | PARINT* sorted_indices[2]; 156 | PARINT* pairs[2]; 157 | 158 | } par_sprune__context; 159 | 160 | static inline int par_qsort_cmpswap(char *__restrict a, char *__restrict b, 161 | size_t w, 162 | int (*compar)(const void *_a, const void *_b, 163 | void *_arg), 164 | void *arg) 165 | { 166 | char tmp, *end = a+w; 167 | if (compar(a, b, arg) > 0) { 168 | for(; a < end; a++, b++) { tmp = *a; *a = *b; *b = tmp; } 169 | return 1; 170 | } 171 | return 0; 172 | } 173 | 174 | // qsort doesn't take a context, so we have our own portable implementation. 175 | // Parameters: 176 | // base is the array to be sorted 177 | // nel is the number of elements in the array 178 | // w is the size in bytes of each element of the array 179 | // compar is the comparison function 180 | // arg is a pointer to be passed to the comparison function 181 | // 182 | static inline void par_qsort( 183 | void *base, 184 | size_t nel, 185 | size_t w, 186 | int (*compar)(const void *_a, const void *_b, void *_arg), 187 | void *arg) 188 | { 189 | char *b = (char*) base, *end = (char*) (b + nel * w); 190 | if (nel < 7) { 191 | char *pi, *pj; 192 | for (pi = b+w; pi < end; pi += w) { 193 | for (pj = pi; pj > b && par_qsort_cmpswap(pj-w, pj, w, compar, arg); 194 | pj -= w) {} 195 | } 196 | return; 197 | } 198 | char *x, *y, *xend, ch; 199 | char *pl, *pr; 200 | char *last = b+w*(nel-1), *tmp; 201 | char *l[3]; 202 | l[0] = b; 203 | l[1] = b+w*(nel/2); 204 | l[2] = last; 205 | if (compar(l[0],l[1],arg) > 0) { 206 | tmp=l[0]; l[0]=l[1]; l[1]=tmp; 207 | } 208 | if (compar(l[1],l[2],arg) > 0) { 209 | tmp=l[1]; l[1]=l[2]; l[2]=tmp; 210 | if (compar(l[0],l[1],arg) > 0) { 211 | tmp=l[0]; l[0]=l[1]; l[1]=tmp; 212 | } 213 | } 214 | for(x = l[1], y = last, xend = x+w; xsorted_indices[0]); 241 | pa_free(ctx->sorted_indices[1]); 242 | pa_free(ctx->pairs[0]); 243 | pa_free(ctx->pairs[1]); 244 | pa_free(ctx->collision_pairs); 245 | PAR_FREE(ctx); 246 | } 247 | 248 | static void par_sprune__remove(PARINT* arr, PARINT val) 249 | { 250 | int i = pa_count(arr) - 1; 251 | for (; i >= 0; i--) { 252 | if (arr[i] == val) { 253 | break; 254 | } 255 | } 256 | assert(i >= 0); 257 | for (++i; i < pa_count(arr); i++) { 258 | PAR_SWAP(PARINT, arr[i - 1], arr[i]); 259 | } 260 | pa___n(arr)--; 261 | } 262 | 263 | typedef struct { 264 | PARFLT const* aabbs; 265 | } par__sprune_sorter; 266 | 267 | static int par__cmpinds(const void* pa, const void* pb, void* psorter) 268 | { 269 | PARINT a = *((const PARINT*) pa); 270 | PARINT b = *((const PARINT*) pb); 271 | par__sprune_sorter* sorter = (par__sprune_sorter*) psorter; 272 | PARFLT const* aabbs = sorter->aabbs; 273 | PARFLT vala = aabbs[a]; 274 | PARFLT valb = aabbs[b]; 275 | if (vala > valb) return 1; 276 | if (vala < valb) return -1; 277 | if (a > b) return 1; 278 | if (a < b) return -1; 279 | return 0; 280 | } 281 | 282 | static int par__cmppairs(const void* pa, const void* pb, void* unused) 283 | { 284 | PARINT a = *((const PARINT*) pa); 285 | PARINT b = *((const PARINT*) pb); 286 | if (a > b) return 1; 287 | if (a < b) return -1; 288 | a = *(1 + (const PARINT*) pa); 289 | b = *(1 + (const PARINT*) pb); 290 | if (a > b) return 1; 291 | if (a < b) return -1; 292 | return 0; 293 | } 294 | 295 | static int par__cmpfind(const void* pa, const void* pb) 296 | { 297 | PARINT a = *((const PARINT*) pa); 298 | PARINT b = *((const PARINT*) pb); 299 | if (a > b) return 1; 300 | if (a < b) return -1; 301 | a = *(1 + (const PARINT*) pa); 302 | b = *(1 + (const PARINT*) pb); 303 | if (a > b) return 1; 304 | if (a < b) return -1; 305 | return 0; 306 | } 307 | 308 | par_sprune_context* par_sprune_overlap(PARFLT const* aabbs, PARINT naabbs, 309 | par_sprune_context* previous) 310 | { 311 | par_sprune__context* ctx = (par_sprune__context*) previous; 312 | if (!ctx) { 313 | ctx = PAR_CALLOC(par_sprune__context, 1); 314 | } 315 | ctx->aabbs = aabbs; 316 | ctx->naabbs = naabbs; 317 | for (int axis = 0; axis < 2; axis++) { 318 | pa_clear(ctx->sorted_indices[axis]); 319 | pa_add(ctx->sorted_indices[axis], naabbs * 2); 320 | pa_clear(ctx->pairs[axis]); 321 | } 322 | for (PARINT i = 0; i < naabbs; i++) { 323 | ctx->sorted_indices[0][i * 2 + 0] = i * 4 + 0; 324 | ctx->sorted_indices[1][i * 2 + 0] = i * 4 + 1; 325 | ctx->sorted_indices[0][i * 2 + 1] = i * 4 + 2; 326 | ctx->sorted_indices[1][i * 2 + 1] = i * 4 + 3; 327 | } 328 | par__sprune_sorter sorter; 329 | sorter.aabbs = ctx->aabbs; 330 | PARINT* active = 0; 331 | 332 | // Sweep a plane first across the X-axis, then down through the Y-axis. 333 | 334 | for (int axis = 0; axis < 2; axis++) { 335 | PARINT** pairs = &ctx->pairs[axis]; 336 | PARINT* indices = ctx->sorted_indices[axis]; 337 | par_qsort(indices, naabbs * 2, sizeof(PARINT), par__cmpinds, &sorter); 338 | pa_clear(active); 339 | for (PARINT i = 0; i < naabbs * 2; i++) { 340 | PARINT fltindex = indices[i]; 341 | PARINT boxindex = fltindex / 4; 342 | bool ismin = ((fltindex - axis) % 4) == 0; 343 | if (ismin) { 344 | for (int j = 0; j < pa_count(active); j++) { 345 | pa_push(*pairs, active[j]); 346 | pa_push(*pairs, boxindex); 347 | pa_push(*pairs, boxindex); 348 | pa_push(*pairs, active[j]); 349 | } 350 | pa_push(active, boxindex); 351 | } else { 352 | par_sprune__remove(active, boxindex); 353 | } 354 | } 355 | } 356 | 357 | // Sort the Y-axis collision pairs to make it easier to intersect it 358 | // with the set of X-axis collision pairs. We also sort the X-axis 359 | // pairs because it's required for subsequent calls to par_sprune_update. 360 | 361 | PARINT* xpairs = ctx->pairs[0]; 362 | PARINT* ypairs = ctx->pairs[1]; 363 | int nxpairs = pa_count(xpairs) / 2; 364 | int nypairs = pa_count(ypairs) / 2; 365 | int pairsize = 2 * sizeof(PARINT); 366 | pa_free(active); 367 | par_qsort(xpairs, nxpairs, pairsize, par__cmppairs, 0); 368 | par_qsort(ypairs, nypairs, pairsize, par__cmppairs, 0); 369 | pa_clear(ctx->collision_pairs); 370 | 371 | // Find the intersection of X-axis overlaps and Y-axis overlaps. 372 | 373 | for (int i = 0; i < pa_count(xpairs); i += 2) { 374 | PARINT* key = xpairs + i; 375 | if (key[1] < key[0]) { 376 | continue; 377 | } 378 | void* found = bsearch(key, ypairs, nypairs, pairsize, par__cmpfind); 379 | if (found) { 380 | pa_push(ctx->collision_pairs, key[0]); 381 | pa_push(ctx->collision_pairs, key[1]); 382 | } 383 | } 384 | ctx->ncollision_pairs = pa_count(ctx->collision_pairs) / 2; 385 | return (par_sprune_context*) ctx; 386 | } 387 | 388 | bool par_sprune_update(par_sprune_context* context) 389 | { 390 | par_sprune__context* ctx = (par_sprune__context*) context; 391 | PARINT* collision_pairs = ctx->collision_pairs; 392 | PARINT ncollision_pairs = ctx->ncollision_pairs; 393 | ctx->collision_pairs = 0; 394 | par_sprune_overlap(ctx->aabbs, ctx->naabbs, context); 395 | bool dirty = ncollision_pairs != ctx->ncollision_pairs; 396 | if (!dirty) { 397 | int pairsize = 2 * sizeof(PARINT); 398 | for (int i = 0; i < ctx->ncollision_pairs; i += 2) { 399 | PARINT* key = ctx->collision_pairs + i; 400 | if (!bsearch(key, collision_pairs, ncollision_pairs, 401 | pairsize, par__cmpfind)) { 402 | dirty = true; 403 | break; 404 | } 405 | } 406 | } 407 | pa_free(collision_pairs); 408 | return dirty; 409 | } 410 | 411 | bool par_sprune__is_culled(par_sprune__context* ctx, PARINT key) 412 | { 413 | for (int i = 0; i < pa_count(ctx->culled); i++) { 414 | if (key == ctx->culled[i]) { 415 | return true; 416 | } 417 | } 418 | return false; 419 | } 420 | 421 | static int par__cmpfindsingle(const void* pa, const void* pb) 422 | { 423 | PARINT a = *((const PARINT*) pa); 424 | PARINT b = *((const PARINT*) pb); 425 | if (a > b) return 1; 426 | if (a < b) return -1; 427 | return 0; 428 | } 429 | 430 | void par_sprune_cull(par_sprune_context* context) 431 | { 432 | par_sprune__context* ctx = (par_sprune__context*) context; 433 | pa_clear(ctx->culled); 434 | PARINT* collision_pairs = ctx->collision_pairs; 435 | PARINT ncollision_pairs = ctx->ncollision_pairs; 436 | int pairsize = 2 * sizeof(PARINT); 437 | for (int i = 0; i < ctx->naabbs; i++) { 438 | PARINT* found = (PARINT*) bsearch(&i, collision_pairs, ncollision_pairs, 439 | pairsize, par__cmpfindsingle); 440 | if (!found) { 441 | continue; 442 | } 443 | if (!par_sprune__is_culled(ctx, found[0]) && 444 | !par_sprune__is_culled(ctx, found[1])) { 445 | pa_push(ctx->culled, found[0]); 446 | } 447 | } 448 | ctx->nculled = pa_count(ctx->culled); 449 | } 450 | 451 | #undef PARINT 452 | #undef PARFLT 453 | #endif // PAR_SPRUNE_IMPLEMENTATION 454 | #endif // PAR_SPRUNE_H 455 | 456 | // par_sprune is distributed under the MIT license: 457 | // 458 | // Copyright (c) 2019 Philip Rideout 459 | // 460 | // Permission is hereby granted, free of charge, to any person obtaining a copy 461 | // of this software and associated documentation files (the "Software"), to deal 462 | // in the Software without restriction, including without limitation the rights 463 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 464 | // copies of the Software, and to permit persons to whom the Software is 465 | // furnished to do so, subject to the following conditions: 466 | // 467 | // The above copyright notice and this permission notice shall be included in 468 | // all copies or substantial portions of the Software. 469 | // 470 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 471 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 472 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 473 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 474 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 475 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 476 | // SOFTWARE. 477 | -------------------------------------------------------------------------------- /par_string_blocks.h: -------------------------------------------------------------------------------- 1 | // STRING_BLOCKS :: https://github.com/prideout/par 2 | // String extraction and concatenation, especially useful for snippets of GLSL or Lua. 3 | // 4 | // This little library extracts blocks of text from a memory blob or file, then lets you retrieve 5 | // them by name. It also makes it easy to glue together a sequence of blocks. 6 | // 7 | // Each block of text is assigned a name using a prefix line that starts with three dash characters, 8 | // such as "--- the_name" or "--- my.block". 9 | // 10 | // For example, suppose you have a file called "shaders.glsl" that looks like this: 11 | // 12 | // --- my_shader 13 | // void main() { ... } 14 | // --- common 15 | // uniform vec4 resolution; 16 | // uniform vec4 color; 17 | // 18 | // You can use this library to read in the file and extract one of the blocks: 19 | // 20 | // parsb_context* blocks = parsb_create_context((parsb_options){}); 21 | // parsb_add_blocks_from_file(blocks, "shaders.glsl"); 22 | // const char* single = parsb_get_blocks(blocks, "my_shader"); 23 | // 24 | // You can also concatenate blocks using a space-delimited list of names: 25 | // 26 | // const char* concatenated = parsb_get_blocks(blocks, "common my_shader"); 27 | // 28 | // You can also add or replace blocks on the fly: 29 | // 30 | // parsb_add_block(blocks, "prefix", "#version 330\n"); 31 | // const char* concatenated = parsb_get_blocks(blocks, "prefix common my_shader"); 32 | // 33 | // The "blocks" context in the above examples holds a cache of generated strings, so be sure to 34 | // destroy it when you're done: 35 | // 36 | // parsb_destroy_context(blocks); 37 | // 38 | // Distributed under the MIT License, see bottom of file. 39 | 40 | #ifndef PAR_STRING_BLOCKS_H 41 | #define PAR_STRING_BLOCKS_H 42 | 43 | #ifdef __cplusplus 44 | extern "C" { 45 | #endif 46 | 47 | #include 48 | 49 | // OPTIONS 50 | // ------- 51 | // line_directives ... adds #line annotations into concatenated strings for better error messages. 52 | typedef struct parsb_options { 53 | bool line_directives; 54 | } parsb_options; 55 | 56 | // CONTEXT CREATION AND DESTRUCTION 57 | // -------------------------------- 58 | // A context is an opaque handle to a memory arena. All generated strings are owned by the context 59 | // and freed when the context is destroyed. 60 | typedef struct parsb_context_s parsb_context; 61 | parsb_context* parsb_create_context(parsb_options); 62 | void parsb_destroy_context(parsb_context*); 63 | 64 | // ADDING AND REPLACING BLOCKS 65 | // --------------------------- 66 | // When using the plural form (add_blocks), the submitted buffer may contain multiple blocks, each 67 | // with a name defined by its closest preceding triple-dash line. If a block with the specified name 68 | // already exists, it gets replaced. 69 | // 70 | // The singular form (add_block) adds a single block whose name is explicitly specified as an 71 | // argument. Again, if a block with the given name already exists, it gets replaced. 72 | // 73 | // These functions do not retain the passed-in strings so clients can free them after pushing them. 74 | void parsb_add_blocks(parsb_context*, const char* buffer, int buffer_size); 75 | void parsb_add_block(parsb_context*, const char* name, const char* body); 76 | #ifndef PARSB_NO_STDIO 77 | void parsb_add_blocks_from_file(parsb_context* context, const char* filename); 78 | #endif 79 | 80 | // EXTRACTING AND CONCATENATING BLOCKS 81 | // ----------------------------------- 82 | // The block_names string is a space-separated list of block names that are being requested. The 83 | // returned string is owned by the context, so please make a copy if you need it to outlive the 84 | // context. If the returned string is null, then one or more of the block names could not be found. 85 | const char* parsb_get_blocks(parsb_context*, const char* block_names); 86 | 87 | // GETTING BLOCKS BY INDEX 88 | // ----------------------- 89 | int parsb_get_num_blocks(const parsb_context*); 90 | void parsb_get_block(const parsb_context*, int index, const char** name, const char** body); 91 | 92 | // SAVING THE BLOCK LIST 93 | // --------------------- 94 | // These functions export the entire "database" of atomic blocks. 95 | typedef void (*parsb_write_line)(const char* line, void* userdata); 96 | void parsb_write_blocks(parsb_context*, parsb_write_line writefn, void* user); 97 | #ifndef PARSB_NO_STDIO 98 | void parsb_write_blocks_to_file(parsb_context*, const char* filename); 99 | #endif 100 | 101 | #ifndef PARSB_MAX_NUM_BLOCKS 102 | #define PARSB_MAX_NUM_BLOCKS 128 103 | #endif 104 | 105 | #ifndef PARSB_MAX_NAME_LENGTH 106 | #define PARSB_MAX_NAME_LENGTH 256 107 | #endif 108 | 109 | #ifndef PARSB_MAX_LINE_LENGTH 110 | #define PARSB_MAX_LINE_LENGTH 256 111 | #endif 112 | 113 | #ifdef __cplusplus 114 | } 115 | #endif 116 | 117 | // ----------------------------------------------------------------------------- 118 | // END PUBLIC API 119 | // ----------------------------------------------------------------------------- 120 | 121 | #ifdef PAR_STRING_BLOCKS_IMPLEMENTATION 122 | 123 | #include 124 | #include 125 | #include 126 | #include 127 | 128 | #ifndef PARSB_NO_STDIO 129 | #include 130 | #endif 131 | 132 | #define PARSB_MIN(a, b) (a > b ? b : a) 133 | 134 | typedef struct { 135 | int count; 136 | char* values[PARSB_MAX_NUM_BLOCKS]; 137 | char* names[PARSB_MAX_NUM_BLOCKS]; 138 | } parsb__list; 139 | 140 | struct parsb_context_s { 141 | parsb_options options; 142 | parsb__list blocks; 143 | parsb__list results; 144 | }; 145 | 146 | static char* parsb__add_or_replace(parsb_context*, const char* id, const char* value, 147 | int value_size, int line_number); 148 | static char* parsb__list_add(parsb__list*, const char* id, const char* value, int value_size, 149 | int line_number); 150 | static char* parsb__list_get(parsb__list*, const char* id, int idlen); 151 | static void parsb__list_free(parsb__list* ); 152 | 153 | parsb_context* parsb_create_context(parsb_options options) { 154 | parsb_context* context = (parsb_context*) calloc(1, sizeof(parsb_context)); 155 | context->options = options; 156 | return context; 157 | } 158 | 159 | void parsb_destroy_context(parsb_context* context) { 160 | parsb__list_free(&context->blocks); 161 | parsb__list_free(&context->results); 162 | free(context); 163 | } 164 | 165 | void parsb_add_blocks(parsb_context* context, const char* blob, int buffer_size) { 166 | const char* previous_block = 0; 167 | char previous_name[PARSB_MAX_NAME_LENGTH]; 168 | int line_number = 0; 169 | int block_line_number = 0; 170 | for (int i = 0; i < buffer_size - 4; i++) { 171 | if (blob[i] != '-' || blob[i + 1] != '-' || blob[i + 2] != '-' || blob[i + 3] != ' ') { 172 | if (blob[i] == '\n') { 173 | line_number++; 174 | } 175 | continue; 176 | } 177 | if (previous_block) { 178 | parsb__add_or_replace(context, previous_name, previous_block, 179 | i - (previous_block - blob), block_line_number); 180 | } 181 | i += 4; 182 | const char* name = blob + i; 183 | const char* block_start = 0; 184 | for (; i < buffer_size; i++) { 185 | if (blob[i] == '\n') { 186 | line_number++; 187 | int name_length = i - (name - blob); 188 | memcpy(previous_name, name, name_length); 189 | block_line_number = line_number + 2; 190 | previous_name[name_length] = 0; 191 | block_start = blob + i + 1; 192 | break; 193 | } 194 | if (isspace(blob[i])) { 195 | int name_length = i - (name - blob); 196 | memcpy(previous_name, name, name_length); 197 | block_line_number = line_number + 2; 198 | previous_name[name_length] = 0; 199 | for (i++; i < buffer_size; i++) { 200 | if (blob[i] == '\n') { 201 | line_number++; 202 | block_start = blob + i + 1; 203 | break; 204 | } 205 | } 206 | break; 207 | } 208 | } 209 | if (block_start == 0) { 210 | return; 211 | } 212 | previous_block = block_start; 213 | } 214 | if (previous_block) { 215 | parsb__add_or_replace(context, previous_name, previous_block, 216 | buffer_size - (previous_block - blob), block_line_number); 217 | } 218 | } 219 | 220 | void parsb_add_block(parsb_context* context, const char* name, const char* body) { 221 | char* dup = strdup(body); 222 | parsb__add_or_replace(context, name, dup, 1 + strlen(body), 0); 223 | } 224 | 225 | const char* parsb_get_blocks(parsb_context* context, const char* block_names) { 226 | int len = strlen(block_names); 227 | const char* name = block_names; 228 | int name_length = 0; 229 | int result_length = 0; 230 | 231 | // First pass determines the amount of required memory. 232 | int num_names = 0; 233 | for (int i = 0; i < len; i++) { 234 | char c = block_names[i]; 235 | if (isspace(c) || !c) { 236 | const char* block = parsb__list_get(&context->blocks, name, name_length); 237 | if (block) { 238 | result_length += strlen(block); 239 | num_names++; 240 | } else { 241 | return NULL; 242 | } 243 | name_length = 0; 244 | name = block_names + i + 1; 245 | } else { 246 | name_length++; 247 | } 248 | } 249 | const char* block = parsb__list_get(&context->blocks, name, name_length); 250 | if (block) { 251 | result_length += strlen(block); 252 | num_names++; 253 | } 254 | 255 | // If no concatenation is required, return early. 256 | if (num_names == 1) { 257 | return parsb__list_get(&context->blocks, name, name_length); 258 | } 259 | 260 | // Allocate storage for the result. 261 | char* result = parsb__list_add(&context->results, 0, 0, result_length, 0); 262 | char* cursor = result; 263 | 264 | // Second pass populates the result. 265 | name = block_names; 266 | name_length = 0; 267 | for (int i = 0; i < len; i++) { 268 | char c = block_names[i]; 269 | if (isspace(c) || !c) { 270 | const char* block = parsb__list_get(&context->blocks, name, name_length); 271 | if (block) { 272 | memcpy(cursor, block, strlen(block)); 273 | cursor += strlen(block); 274 | } 275 | name_length = 0; 276 | name = block_names + i + 1; 277 | } else { 278 | name_length++; 279 | } 280 | } 281 | block = parsb__list_get(&context->blocks, name, name_length); 282 | if (block) { 283 | memcpy(cursor, block, strlen(block)); 284 | cursor += strlen(block); 285 | } 286 | return result; 287 | } 288 | 289 | int parsb_get_num_blocks(const parsb_context* context) { 290 | return context->blocks.count; 291 | } 292 | 293 | void parsb_get_block(const parsb_context* context, int index, const char** name, 294 | const char** body) { 295 | if (index < 0 || index >= context->blocks.count) { 296 | return; 297 | } 298 | *name = context->blocks.names[index]; 299 | *body = context->blocks.values[index]; 300 | } 301 | 302 | void parsb_write_blocks(parsb_context* context, parsb_write_line writefn, void* userdata) { 303 | char line[PARSB_MAX_LINE_LENGTH + 1] = {0}; 304 | for (int i = 0; i < context->blocks.count; i++) { 305 | 306 | sprintf(line, "--- %s", context->blocks.names[i]); 307 | writefn(line, userdata); 308 | 309 | const char* cursor = context->blocks.values[i]; 310 | const int blocklen = strlen(cursor); 311 | int previous = 0; 312 | for (int i = 0; i < blocklen; i++) { 313 | if (cursor[i] == '\n') { 314 | int line_length = PARSB_MIN(i - previous, PARSB_MAX_LINE_LENGTH); 315 | memcpy(line, cursor + previous, line_length); 316 | line[line_length] = 0; 317 | writefn(line, userdata); 318 | previous = i + 1; 319 | } else if (i == blocklen - 1) { 320 | int line_length = PARSB_MIN(1 + i - previous, PARSB_MAX_LINE_LENGTH); 321 | memcpy(line, cursor + previous, line_length); 322 | line[line_length] = 0; 323 | writefn(line, userdata); 324 | previous = i + 1; 325 | } 326 | } 327 | } 328 | } 329 | 330 | static char* parsb__add_or_replace(parsb_context* context, const char* id, const char* value, 331 | int value_size, int line_number) { 332 | line_number = context->options.line_directives ? line_number : 0; 333 | const size_t idlen = strlen(id); 334 | for (int i = 0; i < context->blocks.count; i++) { 335 | if (strncmp(id, context->blocks.names[i], idlen) == 0) { 336 | free(context->blocks.values[i]); 337 | context->blocks.values[i] = strndup(value, value_size); 338 | return context->blocks.values[i]; 339 | } 340 | } 341 | return parsb__list_add(&context->blocks, id, value, value_size, line_number); 342 | } 343 | 344 | static char* parsb__list_add(parsb__list* list, const char* name, 345 | const char* value, int value_size, int line_number) { 346 | if (value_size == 0) { 347 | return NULL; 348 | } 349 | 350 | if (list->count == PARSB_MAX_NUM_BLOCKS) { 351 | assert(false && "Please increase PARSB_MAX_NUM_BLOCKS."); 352 | return NULL; 353 | } 354 | 355 | char* storage; 356 | char* cursor; 357 | 358 | if (line_number > 0) { 359 | char line_directive[16] = {0}; 360 | int prefix_length = snprintf(line_directive, 16, "\n#line %d\n", line_number); 361 | storage = (char*) calloc(1, prefix_length + value_size + 1); 362 | memcpy(storage, line_directive, prefix_length); 363 | cursor = storage + prefix_length; 364 | } else { 365 | storage = cursor = (char*) calloc(1, value_size + 1); 366 | } 367 | 368 | if (value) { 369 | memcpy(cursor, value, value_size); 370 | } 371 | 372 | #if PARSB_ENABLE_TRIM 373 | value_size--; 374 | while (isspace(cursor[value_size])) { 375 | cursor[value_size] = 0; 376 | value_size--; 377 | if (value_size == 0) { 378 | break; 379 | } 380 | } 381 | #endif 382 | 383 | if (name) { 384 | list->names[list->count] = strdup(name); 385 | } else { 386 | list->names[list->count] = 0; 387 | } 388 | 389 | list->values[list->count] = storage; 390 | list->count++; 391 | return storage; 392 | } 393 | 394 | static char* parsb__list_get(parsb__list* list, const char* name, int idlen) { 395 | for (int i = 0; i < list->count; i++) { 396 | if (strncmp(name, list->names[i], idlen) == 0) { 397 | return list->values[i]; 398 | } 399 | } 400 | return NULL; 401 | } 402 | 403 | static void parsb__list_free(parsb__list* list) { 404 | for (int i = 0; i < list->count; i++) { 405 | free(list->names[i]); 406 | free(list->values[i]); 407 | } 408 | list->count = 0; 409 | } 410 | 411 | #ifndef PARSB_NO_STDIO 412 | 413 | void parsb_add_blocks_from_file(parsb_context* context, const char* filename) { 414 | FILE* f = fopen(filename, "r"); 415 | if (!f) { 416 | fprintf(stderr, "Unable to open %s\n", filename); 417 | return; 418 | } 419 | fseek(f, 0, SEEK_END); 420 | int length = ftell(f); 421 | fseek(f, 0, SEEK_SET); 422 | char* buffer = (char*) malloc(length); 423 | fread(buffer, 1, length, f); 424 | fclose(f); 425 | parsb_add_blocks(context, buffer, length); 426 | free(buffer); 427 | } 428 | 429 | static void writefn(const char* line, void* userdata) { 430 | fprintf((FILE*) userdata, "%s\n", line); 431 | } 432 | 433 | void parsb_write_blocks_to_file(parsb_context* context, const char* filename) { 434 | FILE* f = fopen(filename, "w"); 435 | if (!f) { 436 | fprintf(stderr, "Unable to open %s\n", filename); 437 | return; 438 | } 439 | parsb_write_blocks(context, writefn, f); 440 | fclose(f); 441 | } 442 | 443 | #endif 444 | 445 | #endif // PAR_STRING_BLOCKS_IMPLEMENTATION 446 | #endif // PAR_STRING_BLOCKS_H 447 | 448 | // par_string_blocks is distributed under the MIT license: 449 | // 450 | // Copyright (c) 2020 Philip Rideout 451 | // 452 | // Permission is hereby granted, free of charge, to any person obtaining a copy 453 | // of this software and associated documentation files (the "Software"), to deal 454 | // in the Software without restriction, including without limitation the rights 455 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 456 | // copies of the Software, and to permit persons to whom the Software is 457 | // furnished to do so, subject to the following conditions: 458 | // 459 | // The above copyright notice and this permission notice shall be included in 460 | // all copies or substantial portions of the Software. 461 | // 462 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 463 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 464 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 465 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 466 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 467 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 468 | // SOFTWARE. 469 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project(par) 3 | find_package(PkgConfig REQUIRED) 4 | pkg_search_module(CURL REQUIRED libcurl) 5 | 6 | set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} "-std=c11 -Wall") 7 | set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-std=c++17 -Wvla -Wall") 8 | 9 | include_directories(.. . ${CURL_INCLUDE_DIRS}) 10 | 11 | add_executable( 12 | test_strings 13 | console-colors.c 14 | test_strings.cpp) 15 | target_link_libraries(test_strings) 16 | 17 | add_executable( 18 | test_msquares 19 | test_msquares.c 20 | lz4.cpp 21 | lodepng.c 22 | sds.c 23 | whereami.c) 24 | target_link_libraries(test_msquares ${CURL_LIBRARIES}) 25 | 26 | add_executable( 27 | test_bluenoise 28 | test_bluenoise.c 29 | lz4.cpp 30 | lodepng.c 31 | sds.c 32 | whereami.c) 33 | target_link_libraries(test_bluenoise ${CURL_LIBRARIES}) 34 | 35 | add_executable( 36 | test_cpp 37 | test_cpp.cpp 38 | lz4.cpp 39 | lodepng.c 40 | sds.c 41 | whereami.c) 42 | target_link_libraries(test_cpp ${CURL_LIBRARIES}) 43 | 44 | add_executable( 45 | test_c 46 | test_c.c 47 | lz4.cpp 48 | lodepng.c 49 | sds.c 50 | whereami.c) 51 | target_link_libraries(test_c ${CURL_LIBRARIES}) 52 | 53 | add_executable( 54 | test_linkage 55 | test_linkage.cpp 56 | test_linkage.c 57 | lz4.cpp 58 | lodepng.c 59 | sds.c 60 | whereami.c) 61 | target_link_libraries(test_linkage ${CURL_LIBRARIES}) 62 | 63 | add_executable( 64 | test_shapes 65 | test_shapes.c 66 | console-colors.c) 67 | target_link_libraries(test_shapes m) 68 | 69 | add_executable( 70 | test_bubbles 71 | test_bubbles.c 72 | console-colors.c) 73 | target_link_libraries(test_bubbles m) 74 | 75 | add_executable( 76 | test_filecache 77 | test_filecache.c 78 | console-colors.c) 79 | 80 | add_executable( 81 | test_filecache_lz4 82 | test_filecache_lz4.c 83 | console-colors.c) 84 | 85 | add_executable( 86 | test_sprune 87 | test_sprune.c 88 | console-colors.c) 89 | 90 | add_executable( 91 | test_octasphere 92 | test_octasphere.cpp 93 | console-colors.c) 94 | -------------------------------------------------------------------------------- /test/assertion-macros.h: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // assertion-macros.h 4 | // 5 | // Copyright (c) 2014 Stephen Mathieson 6 | // MIT licensed 7 | // 8 | 9 | 10 | #ifndef ASSERTION_MACROS_H 11 | #define ASSERTION_MACROS_H 1 12 | 13 | #define ASSERTIONS_VERSION "0.2.0" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | static int __assert_bail = 0; 20 | static int __assert_failures = 0; 21 | 22 | /* 23 | * Bail at first failing assertion 24 | */ 25 | 26 | #define assert_bail() __assert_bail = !__assert_bail; 27 | 28 | /* 29 | * Get the number of failed assertions 30 | */ 31 | 32 | #define assert_failures() __assert_failures 33 | 34 | /* 35 | * Reset the number of failed assertions 36 | */ 37 | 38 | #define assert_reset() ({ \ 39 | __assert_failures = 0; \ 40 | }) 41 | 42 | // don't clobber assert 43 | #ifndef assert 44 | # define assert assert_ok 45 | #endif 46 | 47 | /* 48 | * Assert that `expr` evaluates to something truthy 49 | */ 50 | 51 | #define assert_ok(expr) ({ \ 52 | if (!(expr)) {\ 53 | __assert_failures++; \ 54 | fprintf(stderr, \ 55 | "Assertion error: %s (%s:%d)\n", \ 56 | #expr, __FILE__, __LINE__); \ 57 | if (__assert_bail) abort(); \ 58 | } \ 59 | }) 60 | 61 | /* 62 | * Assert that `expr` is NULL 63 | */ 64 | 65 | #define assert_null(expr) ({ \ 66 | if ((expr) != NULL) {\ 67 | __assert_failures++; \ 68 | fprintf(stderr, \ 69 | "Assertion error: %s is NULL (%s:%d)\n", \ 70 | #expr, __FILE__, __LINE__); \ 71 | if (__assert_bail) abort(); \ 72 | } \ 73 | }) 74 | 75 | /* 76 | * Assert that `expr` is not NULL 77 | */ 78 | 79 | #define assert_not_null(expr) ({ \ 80 | if ((expr) == NULL) {\ 81 | __assert_failures++; \ 82 | fprintf(stderr, \ 83 | "Assertion error: %s is not NULL (%s:%d)\n", \ 84 | #expr, __FILE__, __LINE__); \ 85 | if (__assert_bail) abort(); \ 86 | } \ 87 | }) 88 | 89 | /* 90 | * Assert that `a` is equal to `b` 91 | */ 92 | 93 | #define assert_equal(a, b) ({ \ 94 | if (a != b) {\ 95 | __assert_failures++; \ 96 | fprintf(stderr, \ 97 | "Assertion error: %d == %d (%s:%d)\n", \ 98 | a, b, __FILE__, __LINE__); \ 99 | if (__assert_bail) abort(); \ 100 | } \ 101 | }) 102 | 103 | /* 104 | * Assert that `a` is not equal to `b` 105 | */ 106 | 107 | #define assert_not_equal(a, b) ({ \ 108 | if (a == b) {\ 109 | __assert_failures++; \ 110 | fprintf(stderr, \ 111 | "Assertion error: %d != %d (%s:%d)\n", \ 112 | a, b, __FILE__, __LINE__); \ 113 | if (__assert_bail) abort(); \ 114 | } \ 115 | }) 116 | 117 | /* 118 | * Assert that `a` is equal to `b` 119 | */ 120 | 121 | #define assert_str_equal(a, b) ({ \ 122 | if (0 != strcmp(a, b)) {\ 123 | __assert_failures++; \ 124 | fprintf(stderr, \ 125 | "Assertion error: \"%s\" == \"%s\" (%s:%d)\n", \ 126 | a, b, __FILE__, __LINE__); \ 127 | if (__assert_bail) abort(); \ 128 | } \ 129 | }) 130 | 131 | /* 132 | * Assert that `a` is not equal to `b` 133 | */ 134 | 135 | #define assert_str_not_equal(a, b) ({ \ 136 | if (0 == strcmp(a, b)) {\ 137 | __assert_failures++; \ 138 | fprintf(stderr, \ 139 | "Assertion error: \"%s\" != \"%s\" (%s:%d)\n", \ 140 | a, b, __FILE__, __LINE__); \ 141 | if (__assert_bail) abort(); \ 142 | } \ 143 | }) 144 | 145 | #endif 146 | -------------------------------------------------------------------------------- /test/boxes.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | r = random.Random(1) 4 | 5 | def generate_box(): 6 | a = r.random() 7 | b = r.random() 8 | c = r.random() 9 | d = r.random() 10 | minx = min(a, b) 11 | maxx = max(a, b) 12 | miny = min(c, d) 13 | maxy = max(c, d) 14 | cx = 0.5 * (minx + maxx) 15 | cy = 0.5 * (miny + maxy) 16 | w = maxx - minx 17 | h = maxy - miny 18 | sz = r.random() * r.random() 19 | w = w * sz 20 | h = h * sz 21 | minx = cx - w * 0.5 22 | maxx = cx + w * 0.5 23 | miny = cy - h * 0.5 24 | maxy = cy + h * 0.5 25 | return minx, miny, maxx, maxy 26 | 27 | for i in xrange(20): 28 | box = generate_box() 29 | print '{:.2}, {:.2}, {:.2}, {:.2},'.format(*box) 30 | print 31 | for i in xrange(10): 32 | box = generate_box() 33 | print '{:.2}, {:.2}, {:.2}, {:.2},'.format(*box) 34 | -------------------------------------------------------------------------------- /test/console-colors.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 Yusuke Suzuki 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | #include 25 | #include 26 | #include 27 | 28 | #ifdef _WIN32 29 | #include 30 | #endif 31 | 32 | #include "console-colors.h" 33 | 34 | static int Write(FILE *stream, const char *format, va_list ap) { 35 | #if defined(_WIN32) && !defined(__MINGW32__) 36 | return vfprintf_s(stream, format, ap); 37 | #else 38 | return vfprintf(stream, format, ap); 39 | #endif 40 | } 41 | 42 | #ifdef _WIN32 43 | static const WORD FG[] = { 44 | /* NONE */ 0, 45 | /* BLACK */ 0, 46 | /* DARK_RED */ FOREGROUND_RED, 47 | /* DARK_GREEN */ FOREGROUND_GREEN, 48 | /* DARK_YELLOW */ FOREGROUND_RED | FOREGROUND_GREEN, 49 | /* DARK_BLUE */ FOREGROUND_BLUE, 50 | /* DARK_MAGENTA */ FOREGROUND_RED | FOREGROUND_BLUE, 51 | /* DARK_CYAN */ FOREGROUND_GREEN | FOREGROUND_BLUE, 52 | /* GRAY */ FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED, 53 | /* DARK_GRAY */ FOREGROUND_INTENSITY, 54 | /* RED */ FOREGROUND_INTENSITY | FOREGROUND_RED, 55 | /* GREEN */ FOREGROUND_INTENSITY | FOREGROUND_GREEN, 56 | /* YELLOW */ FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN, 57 | /* BLUE */ FOREGROUND_INTENSITY | FOREGROUND_BLUE, 58 | /* MAGENTA */ FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE, 59 | /* CYAN */ FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE, 60 | /* WHITE */ FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED, 61 | }; 62 | 63 | static const WORD BG[] = { 64 | /* NONE */ 0, 65 | /* BLACK */ 0, 66 | /* DARK_RED */ BACKGROUND_RED, 67 | /* DARK_GREEN */ BACKGROUND_GREEN, 68 | /* DARK_YELLOW */ BACKGROUND_RED | BACKGROUND_GREEN, 69 | /* DARK_BLUE */ BACKGROUND_BLUE, 70 | /* DARK_MAGENTA */ BACKGROUND_RED | BACKGROUND_BLUE, 71 | /* DARK_CYAN */ BACKGROUND_GREEN | BACKGROUND_BLUE, 72 | /* GRAY */ BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED, 73 | /* DARK_GRAY */ BACKGROUND_INTENSITY, 74 | /* RED */ BACKGROUND_INTENSITY | BACKGROUND_RED, 75 | /* GREEN */ BACKGROUND_INTENSITY | BACKGROUND_GREEN, 76 | /* YELLOW */ BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN, 77 | /* BLUE */ BACKGROUND_INTENSITY | BACKGROUND_BLUE, 78 | /* MAGENTA */ BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE, 79 | /* CYAN */ BACKGROUND_INTENSITY | BACKGROUND_GREEN | BACKGROUND_BLUE, 80 | /* WHITE */ BACKGROUND_INTENSITY | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_RED, 81 | }; 82 | 83 | static WORD ExtractForeground(WORD attributes) { 84 | const WORD mask = 85 | FOREGROUND_INTENSITY | 86 | FOREGROUND_GREEN | 87 | FOREGROUND_BLUE | 88 | FOREGROUND_RED; 89 | return attributes & mask; 90 | } 91 | 92 | static WORD ExtractBackground(WORD attributes) { 93 | const WORD mask = 94 | BACKGROUND_INTENSITY | 95 | BACKGROUND_GREEN | 96 | BACKGROUND_BLUE | 97 | BACKGROUND_RED; 98 | return attributes & mask; 99 | } 100 | 101 | static WORD ExtractOthers(WORD attributes) { 102 | const WORD mask = 103 | COMMON_LVB_LEADING_BYTE | 104 | COMMON_LVB_TRAILING_BYTE | 105 | COMMON_LVB_GRID_HORIZONTAL | 106 | COMMON_LVB_GRID_LVERTICAL | 107 | COMMON_LVB_GRID_RVERTICAL | 108 | COMMON_LVB_REVERSE_VIDEO | 109 | COMMON_LVB_UNDERSCORE; 110 | return attributes & mask; 111 | } 112 | 113 | static WORD Generate(unsigned int fg, unsigned int bg, WORD attributes) { 114 | WORD result = ExtractOthers(attributes); 115 | result |= ((fg == 0) ? ExtractForeground(attributes) : FG[fg]); 116 | result |= ((bg == 0) ? ExtractBackground(attributes) : BG[bg]); 117 | return result; 118 | } 119 | 120 | #else 121 | 122 | static inline unsigned int Shift( 123 | unsigned int val, unsigned int normal, unsigned int bright) { 124 | if (val == 0) { 125 | return 9 + normal; 126 | } 127 | val -= 1; 128 | /* background */ 129 | if (val >= 8) { 130 | return (val - 8) + bright; 131 | } 132 | return val + normal; 133 | } 134 | 135 | static void UnixTerminalColorize( 136 | FILE* stream, unsigned int fg, unsigned int bg) { 137 | fprintf(stream, "\x1B[39;49;%u;%um", Shift(fg, 30, 90), Shift(bg, 40, 100)); 138 | } 139 | 140 | static void UnixTerminalRestore(FILE* stream) { 141 | fputs("\x1B[39;49m", stream); 142 | } 143 | #endif /* _WIN32 */ 144 | 145 | int cc_fprintf(cc_color_t color, FILE* stream, const char* format, ...) { 146 | va_list ap; 147 | va_start(ap, format); 148 | int result = -EINVAL; 149 | 150 | if (stream != stdout && stream != stderr) { 151 | result = Write(stream, format, ap); 152 | goto finish; 153 | } 154 | 155 | const unsigned int fg = 156 | color & ((1 << CC_COLOR_BITS) - 1); 157 | const unsigned int bg = 158 | (color >> CC_COLOR_BITS) & ((1 << CC_COLOR_BITS) - 1); 159 | 160 | #ifdef _WIN32 161 | const HANDLE console = GetStdHandle( 162 | stream == stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); 163 | if (console == INVALID_HANDLE_VALUE) { 164 | result = Write(stream, format, ap); 165 | goto finish; 166 | } 167 | CONSOLE_SCREEN_BUFFER_INFO csbi; 168 | if (!GetConsoleScreenBufferInfo(console, &csbi)) { 169 | result = Write(stream, format, ap); 170 | goto finish; 171 | } 172 | SetConsoleTextAttribute(console, Generate(fg, bg, csbi.wAttributes)); 173 | result = Write(stream, format, ap); 174 | SetConsoleTextAttribute(console, csbi.wAttributes); 175 | #else 176 | UnixTerminalColorize(stream, fg, bg); 177 | Write(stream, format, ap); 178 | UnixTerminalRestore(stream); 179 | #endif /* _WIN32 */ 180 | 181 | finish: 182 | va_end(ap); 183 | return result; 184 | } 185 | 186 | /* vim: set sw=4 ts=4 et tw=80 : */ 187 | -------------------------------------------------------------------------------- /test/console-colors.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 Yusuke Suzuki 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | #ifndef CONSOLE_COLORS_CONSOLE_COLORS_H_ 25 | #define CONSOLE_COLORS_CONSOLE_COLORS_H_ 26 | #include 27 | 28 | #define CC_COLOR_BITS 5 29 | 30 | typedef enum { 31 | CC_FG_NONE = 0 << 0, 32 | CC_FG_BLACK = 1 << 0, 33 | CC_FG_DARK_RED = 2 << 0, 34 | CC_FG_DARK_GREEN = 3 << 0, 35 | CC_FG_DARK_YELLOW = 4 << 0, 36 | CC_FG_DARK_BLUE = 5 << 0, 37 | CC_FG_DARK_MAGENTA = 6 << 0, 38 | CC_FG_DARK_CYAN = 7 << 0, 39 | CC_FG_GRAY = 8 << 0, 40 | CC_FG_DARK_GRAY = 9 << 0, 41 | CC_FG_RED = 10 << 0, 42 | CC_FG_GREEN = 11 << 0, 43 | CC_FG_YELLOW = 12 << 0, 44 | CC_FG_BLUE = 13 << 0, 45 | CC_FG_MAGENTA = 14 << 0, 46 | CC_FG_CYAN = 15 << 0, 47 | CC_FG_WHITE = 16 << 0, 48 | 49 | CC_BG_NONE = 0 << CC_COLOR_BITS, 50 | CC_BG_BLACK = 1 << CC_COLOR_BITS, 51 | CC_BG_DARK_RED = 2 << CC_COLOR_BITS, 52 | CC_BG_DARK_GREEN = 3 << CC_COLOR_BITS, 53 | CC_BG_DARK_YELLOW = 4 << CC_COLOR_BITS, 54 | CC_BG_DARK_BLUE = 5 << CC_COLOR_BITS, 55 | CC_BG_DARK_MAGENTA = 6 << CC_COLOR_BITS, 56 | CC_BG_DARK_CYAN = 7 << CC_COLOR_BITS, 57 | CC_BG_GRAY = 8 << CC_COLOR_BITS, 58 | CC_BG_DARK_GRAY = 9 << CC_COLOR_BITS, 59 | CC_BG_RED = 10 << CC_COLOR_BITS, 60 | CC_BG_GREEN = 11 << CC_COLOR_BITS, 61 | CC_BG_YELLOW = 12 << CC_COLOR_BITS, 62 | CC_BG_BLUE = 13 << CC_COLOR_BITS, 63 | CC_BG_MAGENTA = 14 << CC_COLOR_BITS, 64 | CC_BG_CYAN = 15 << CC_COLOR_BITS, 65 | CC_BG_WHITE = 16 << CC_COLOR_BITS 66 | } cc_color_t; 67 | 68 | #ifndef COMMON_LVB_LEADING_BYTE 69 | #define COMMON_LVB_LEADING_BYTE 0x0100 70 | #endif 71 | 72 | #ifndef COMMON_LVB_TRAILING_BYTE 73 | #define COMMON_LVB_TRAILING_BYTE 0x0200 74 | #endif 75 | 76 | #ifndef COMMON_LVB_GRID_HORIZONTAL 77 | #define COMMON_LVB_GRID_HORIZONTAL 0x0400 78 | #endif 79 | 80 | #ifndef COMMON_LVB_GRID_LVERTICAL 81 | #define COMMON_LVB_GRID_LVERTICAL 0x0800 82 | #endif 83 | 84 | #ifndef COMMON_LVB_GRID_RVERTICAL 85 | #define COMMON_LVB_GRID_RVERTICAL 0x1000 86 | #endif 87 | 88 | #ifndef COMMON_LVB_REVERSE_VIDEO 89 | #define COMMON_LVB_REVERSE_VIDEO 0x4000 90 | #endif 91 | 92 | #ifndef COMMON_LVB_UNDERSCORE 93 | #define COMMON_LVB_UNDERSCORE 0x8000 94 | #endif 95 | 96 | /** 97 | * @param color {console_color_t} Console color. We can pass (FG | BG) as color. 98 | * @param stream {FILE*} `stdout` or `stderr`. Others will be passed to fprintf 99 | * without colors. 100 | * @param format {const char*} Format string fprintf will take. 101 | * @return {int} fprintf returned value. 102 | * 103 | * CAUTION(Yusuke Suzuki): bright FG & dark BG combination doesn't works 104 | * correctly on some terminals, but this is an well-known issue. 105 | */ 106 | int cc_fprintf(cc_color_t color, FILE* stream, const char* format, ...); 107 | 108 | #endif /* CONSOLE_COLORS_CONSOLE_COLORS_H_ */ 109 | /* vim: set sw=4 ts=4 et tw=80 : */ 110 | -------------------------------------------------------------------------------- /test/describe.h: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // describe.h 4 | // 5 | // Copyright (c) 2013 Stephen Mathieson 6 | // Copyright (c) 2015 Michael Phan-Ba 7 | // MIT licensed 8 | // 9 | 10 | 11 | #ifndef DESCRIBE_H 12 | #define DESCRIBE_H 1 13 | 14 | #include "console-colors.h" 15 | #include "assertion-macros.h" 16 | 17 | #define DESCRIBE_VERSION "1.1.0" 18 | #define DESCRIBE_OK "✓" 19 | #define DESCRIBE_FAIL "✖" 20 | 21 | void __describe_after_spec(const char *specification, int before) { 22 | if (assert_failures() == before) { 23 | cc_fprintf( 24 | CC_FG_DARK_GREEN 25 | , stdout 26 | , " %s" 27 | , DESCRIBE_OK 28 | ); 29 | } else { 30 | cc_fprintf( 31 | CC_FG_DARK_RED 32 | , stdout 33 | , " %s" 34 | , DESCRIBE_FAIL 35 | ); 36 | } 37 | cc_fprintf( 38 | CC_FG_GRAY 39 | , stdout 40 | , " %s\n" 41 | , specification 42 | ); 43 | } 44 | 45 | /* 46 | * Describe `suite` with `title` 47 | */ 48 | 49 | #define describe(title) for ( \ 50 | int __run = 0; \ 51 | __run++ == 0 && printf("\n %s\n", title); \ 52 | printf("\n") \ 53 | ) 54 | 55 | /* 56 | * Describe `fn` with `specification` 57 | */ 58 | 59 | #define it(specification) for ( \ 60 | int __before = assert_failures(), __run = 0; \ 61 | __run++ == 0; \ 62 | __describe_after_spec(specification, __before) \ 63 | ) 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /test/flare.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "flare", 4 | "children": [ 5 | { 6 | "name": "analytics", 7 | "children": [ 8 | { 9 | "name": "cluster", 10 | "children": [ 11 | {"name": "AgglomerativeCluster", "size": 3938}, 12 | {"name": "CommunityStructure", "size": 3812}, 13 | {"name": "HierarchicalCluster", "size": 6714}, 14 | {"name": "MergeEdge", "size": 743} 15 | ] 16 | }, 17 | { 18 | "name": "graph", 19 | "children": [ 20 | {"name": "BetweennessCentrality", "size": 3534}, 21 | {"name": "LinkDistance", "size": 5731}, 22 | {"name": "MaxFlowMinCut", "size": 7840}, 23 | {"name": "ShortestPaths", "size": 5914}, 24 | {"name": "SpanningTree", "size": 3416} 25 | ] 26 | }, 27 | { 28 | "name": "optimization", 29 | "children": [ 30 | {"name": "AspectRatioBanker", "size": 7074} 31 | ] 32 | } 33 | ] 34 | }, 35 | { 36 | "name": "animate", 37 | "children": [ 38 | {"name": "Easing", "size": 17010}, 39 | {"name": "FunctionSequence", "size": 5842}, 40 | { 41 | "name": "interpolate", 42 | "children": [ 43 | {"name": "ArrayInterpolator", "size": 1983}, 44 | {"name": "ColorInterpolator", "size": 2047}, 45 | {"name": "DateInterpolator", "size": 1375}, 46 | {"name": "Interpolator", "size": 8746}, 47 | {"name": "MatrixInterpolator", "size": 2202}, 48 | {"name": "NumberInterpolator", "size": 1382}, 49 | {"name": "ObjectInterpolator", "size": 1629}, 50 | {"name": "PointInterpolator", "size": 1675}, 51 | {"name": "RectangleInterpolator", "size": 2042} 52 | ] 53 | }, 54 | {"name": "ISchedulable", "size": 1041}, 55 | {"name": "Parallel", "size": 5176}, 56 | {"name": "Pause", "size": 449}, 57 | {"name": "Scheduler", "size": 5593}, 58 | {"name": "Sequence", "size": 5534}, 59 | {"name": "Transition", "size": 9201}, 60 | {"name": "Transitioner", "size": 19975}, 61 | {"name": "TransitionEvent", "size": 1116}, 62 | {"name": "Tween", "size": 6006} 63 | ] 64 | }, 65 | { 66 | "name": "data", 67 | "children": [ 68 | { 69 | "name": "converters", 70 | "children": [ 71 | {"name": "Converters", "size": 721}, 72 | {"name": "DelimitedTextConverter", "size": 4294}, 73 | {"name": "GraphMLConverter", "size": 9800}, 74 | {"name": "IDataConverter", "size": 1314}, 75 | {"name": "JSONConverter", "size": 2220} 76 | ] 77 | }, 78 | {"name": "DataField", "size": 1759}, 79 | {"name": "DataSchema", "size": 2165}, 80 | {"name": "DataSet", "size": 586}, 81 | {"name": "DataSource", "size": 3331}, 82 | {"name": "DataTable", "size": 772}, 83 | {"name": "DataUtil", "size": 3322} 84 | ] 85 | }, 86 | { 87 | "name": "display", 88 | "children": [ 89 | {"name": "DirtySprite", "size": 8833}, 90 | {"name": "LineSprite", "size": 1732}, 91 | {"name": "RectSprite", "size": 3623}, 92 | {"name": "TextSprite", "size": 10066} 93 | ] 94 | }, 95 | { 96 | "name": "flex", 97 | "children": [ 98 | {"name": "FlareVis", "size": 4116} 99 | ] 100 | }, 101 | { 102 | "name": "physics", 103 | "children": [ 104 | {"name": "DragForce", "size": 1082}, 105 | {"name": "GravityForce", "size": 1336}, 106 | {"name": "IForce", "size": 319}, 107 | {"name": "NBodyForce", "size": 10498}, 108 | {"name": "Particle", "size": 2822}, 109 | {"name": "Simulation", "size": 9983}, 110 | {"name": "Spring", "size": 2213}, 111 | {"name": "SpringForce", "size": 1681} 112 | ] 113 | }, 114 | { 115 | "name": "query", 116 | "children": [ 117 | {"name": "AggregateExpression", "size": 1616}, 118 | {"name": "And", "size": 1027}, 119 | {"name": "Arithmetic", "size": 3891}, 120 | {"name": "Average", "size": 891}, 121 | {"name": "BinaryExpression", "size": 2893}, 122 | {"name": "Comparison", "size": 5103}, 123 | {"name": "CompositeExpression", "size": 3677}, 124 | {"name": "Count", "size": 781}, 125 | {"name": "DateUtil", "size": 4141}, 126 | {"name": "Distinct", "size": 933}, 127 | {"name": "Expression", "size": 5130}, 128 | {"name": "ExpressionIterator", "size": 3617}, 129 | {"name": "Fn", "size": 3240}, 130 | {"name": "If", "size": 2732}, 131 | {"name": "IsA", "size": 2039}, 132 | {"name": "Literal", "size": 1214}, 133 | {"name": "Match", "size": 3748}, 134 | {"name": "Maximum", "size": 843}, 135 | { 136 | "name": "methods", 137 | "children": [ 138 | {"name": "add", "size": 593}, 139 | {"name": "and", "size": 330}, 140 | {"name": "average", "size": 287}, 141 | {"name": "count", "size": 277}, 142 | {"name": "distinct", "size": 292}, 143 | {"name": "div", "size": 595}, 144 | {"name": "eq", "size": 594}, 145 | {"name": "fn", "size": 460}, 146 | {"name": "gt", "size": 603}, 147 | {"name": "gte", "size": 625}, 148 | {"name": "iff", "size": 748}, 149 | {"name": "isa", "size": 461}, 150 | {"name": "lt", "size": 597}, 151 | {"name": "lte", "size": 619}, 152 | {"name": "max", "size": 283}, 153 | {"name": "min", "size": 283}, 154 | {"name": "mod", "size": 591}, 155 | {"name": "mul", "size": 603}, 156 | {"name": "neq", "size": 599}, 157 | {"name": "not", "size": 386}, 158 | {"name": "or", "size": 323}, 159 | {"name": "orderby", "size": 307}, 160 | {"name": "range", "size": 772}, 161 | {"name": "select", "size": 296}, 162 | {"name": "stddev", "size": 363}, 163 | {"name": "sub", "size": 600}, 164 | {"name": "sum", "size": 280}, 165 | {"name": "update", "size": 307}, 166 | {"name": "variance", "size": 335}, 167 | {"name": "where", "size": 299}, 168 | {"name": "xor", "size": 354}, 169 | {"name": "_", "size": 264} 170 | ] 171 | }, 172 | {"name": "Minimum", "size": 843}, 173 | {"name": "Not", "size": 1554}, 174 | {"name": "Or", "size": 970}, 175 | {"name": "Query", "size": 13896}, 176 | {"name": "Range", "size": 1594}, 177 | {"name": "StringUtil", "size": 4130}, 178 | {"name": "Sum", "size": 791}, 179 | {"name": "Variable", "size": 1124}, 180 | {"name": "Variance", "size": 1876}, 181 | {"name": "Xor", "size": 1101} 182 | ] 183 | }, 184 | { 185 | "name": "scale", 186 | "children": [ 187 | {"name": "IScaleMap", "size": 2105}, 188 | {"name": "LinearScale", "size": 1316}, 189 | {"name": "LogScale", "size": 3151}, 190 | {"name": "OrdinalScale", "size": 3770}, 191 | {"name": "QuantileScale", "size": 2435}, 192 | {"name": "QuantitativeScale", "size": 4839}, 193 | {"name": "RootScale", "size": 1756}, 194 | {"name": "Scale", "size": 4268}, 195 | {"name": "ScaleType", "size": 1821}, 196 | {"name": "TimeScale", "size": 5833} 197 | ] 198 | }, 199 | { 200 | "name": "util", 201 | "children": [ 202 | {"name": "Arrays", "size": 8258}, 203 | {"name": "Colors", "size": 10001}, 204 | {"name": "Dates", "size": 8217}, 205 | {"name": "Displays", "size": 12555}, 206 | {"name": "Filter", "size": 2324}, 207 | {"name": "Geometry", "size": 10993}, 208 | { 209 | "name": "heap", 210 | "children": [ 211 | {"name": "FibonacciHeap", "size": 9354}, 212 | {"name": "HeapNode", "size": 1233} 213 | ] 214 | }, 215 | {"name": "IEvaluable", "size": 335}, 216 | {"name": "IPredicate", "size": 383}, 217 | {"name": "IValueProxy", "size": 874}, 218 | { 219 | "name": "math", 220 | "children": [ 221 | {"name": "DenseMatrix", "size": 3165}, 222 | {"name": "IMatrix", "size": 2815}, 223 | {"name": "SparseMatrix", "size": 3366} 224 | ] 225 | }, 226 | {"name": "Maths", "size": 17705}, 227 | {"name": "Orientation", "size": 1486}, 228 | { 229 | "name": "palette", 230 | "children": [ 231 | {"name": "ColorPalette", "size": 6367}, 232 | {"name": "Palette", "size": 1229}, 233 | {"name": "ShapePalette", "size": 2059}, 234 | {"name": "SizePalette", "size": 2291} 235 | ] 236 | }, 237 | {"name": "Property", "size": 5559}, 238 | {"name": "Shapes", "size": 19118}, 239 | {"name": "Sort", "size": 6887}, 240 | {"name": "Stats", "size": 6557}, 241 | {"name": "Strings", "size": 22026} 242 | ] 243 | }, 244 | { 245 | "name": "vis", 246 | "children": [ 247 | { 248 | "name": "axis", 249 | "children": [ 250 | {"name": "Axes", "size": 1302}, 251 | {"name": "Axis", "size": 24593}, 252 | {"name": "AxisGridLine", "size": 652}, 253 | {"name": "AxisLabel", "size": 636}, 254 | {"name": "CartesianAxes", "size": 6703} 255 | ] 256 | }, 257 | { 258 | "name": "controls", 259 | "children": [ 260 | {"name": "AnchorControl", "size": 2138}, 261 | {"name": "ClickControl", "size": 3824}, 262 | {"name": "Control", "size": 1353}, 263 | {"name": "ControlList", "size": 4665}, 264 | {"name": "DragControl", "size": 2649}, 265 | {"name": "ExpandControl", "size": 2832}, 266 | {"name": "HoverControl", "size": 4896}, 267 | {"name": "IControl", "size": 763}, 268 | {"name": "PanZoomControl", "size": 5222}, 269 | {"name": "SelectionControl", "size": 7862}, 270 | {"name": "TooltipControl", "size": 8435} 271 | ] 272 | }, 273 | { 274 | "name": "data", 275 | "children": [ 276 | {"name": "Data", "size": 20544}, 277 | {"name": "DataList", "size": 19788}, 278 | {"name": "DataSprite", "size": 10349}, 279 | {"name": "EdgeSprite", "size": 3301}, 280 | {"name": "NodeSprite", "size": 19382}, 281 | { 282 | "name": "render", 283 | "children": [ 284 | {"name": "ArrowType", "size": 698}, 285 | {"name": "EdgeRenderer", "size": 5569}, 286 | {"name": "IRenderer", "size": 353}, 287 | {"name": "ShapeRenderer", "size": 2247} 288 | ] 289 | }, 290 | {"name": "ScaleBinding", "size": 11275}, 291 | {"name": "Tree", "size": 7147}, 292 | {"name": "TreeBuilder", "size": 9930} 293 | ] 294 | }, 295 | { 296 | "name": "events", 297 | "children": [ 298 | {"name": "DataEvent", "size": 2313}, 299 | {"name": "SelectionEvent", "size": 1880}, 300 | {"name": "TooltipEvent", "size": 1701}, 301 | {"name": "VisualizationEvent", "size": 1117} 302 | ] 303 | }, 304 | { 305 | "name": "legend", 306 | "children": [ 307 | {"name": "Legend", "size": 20859}, 308 | {"name": "LegendItem", "size": 4614}, 309 | {"name": "LegendRange", "size": 10530} 310 | ] 311 | }, 312 | { 313 | "name": "operator", 314 | "children": [ 315 | { 316 | "name": "distortion", 317 | "children": [ 318 | {"name": "BifocalDistortion", "size": 4461}, 319 | {"name": "Distortion", "size": 6314}, 320 | {"name": "FisheyeDistortion", "size": 3444} 321 | ] 322 | }, 323 | { 324 | "name": "encoder", 325 | "children": [ 326 | {"name": "ColorEncoder", "size": 3179}, 327 | {"name": "Encoder", "size": 4060}, 328 | {"name": "PropertyEncoder", "size": 4138}, 329 | {"name": "ShapeEncoder", "size": 1690}, 330 | {"name": "SizeEncoder", "size": 1830} 331 | ] 332 | }, 333 | { 334 | "name": "filter", 335 | "children": [ 336 | {"name": "FisheyeTreeFilter", "size": 5219}, 337 | {"name": "GraphDistanceFilter", "size": 3165}, 338 | {"name": "VisibilityFilter", "size": 3509} 339 | ] 340 | }, 341 | {"name": "IOperator", "size": 1286}, 342 | { 343 | "name": "label", 344 | "children": [ 345 | {"name": "Labeler", "size": 9956}, 346 | {"name": "RadialLabeler", "size": 3899}, 347 | {"name": "StackedAreaLabeler", "size": 3202} 348 | ] 349 | }, 350 | { 351 | "name": "layout", 352 | "children": [ 353 | {"name": "AxisLayout", "size": 6725}, 354 | {"name": "BundledEdgeRouter", "size": 3727}, 355 | {"name": "CircleLayout", "size": 9317}, 356 | {"name": "CirclePackingLayout", "size": 12003}, 357 | {"name": "DendrogramLayout", "size": 4853}, 358 | {"name": "ForceDirectedLayout", "size": 8411}, 359 | {"name": "IcicleTreeLayout", "size": 4864}, 360 | {"name": "IndentedTreeLayout", "size": 3174}, 361 | {"name": "Layout", "size": 7881}, 362 | {"name": "NodeLinkTreeLayout", "size": 12870}, 363 | {"name": "PieLayout", "size": 2728}, 364 | {"name": "RadialTreeLayout", "size": 12348}, 365 | {"name": "RandomLayout", "size": 870}, 366 | {"name": "StackedAreaLayout", "size": 9121}, 367 | {"name": "TreeMapLayout", "size": 9191} 368 | ] 369 | }, 370 | {"name": "Operator", "size": 2490}, 371 | {"name": "OperatorList", "size": 5248}, 372 | {"name": "OperatorSequence", "size": 4190}, 373 | {"name": "OperatorSwitch", "size": 2581}, 374 | {"name": "SortOperator", "size": 2023} 375 | ] 376 | }, 377 | {"name": "Visualization", "size": 16540} 378 | ] 379 | } 380 | ] 381 | } -------------------------------------------------------------------------------- /test/gen_rgb_png.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from PIL import ImageDraw 3 | 4 | im = Image.new('RGB', (256,256)) 5 | draw = ImageDraw.Draw(im) 6 | draw.ellipse([128,128,512-128,512-128], (255,0,0)) 7 | draw.ellipse([150,150,512-150,512-150], (0,255,0)) 8 | draw.ellipse([128-64,128-64,128+64,128+64], (255,0,0)) 9 | draw.ellipse([128-32,128-32,128+32,128+32], (0,0,0)) 10 | del draw 11 | im.save('rgb.png', 'PNG') 12 | 13 | im = Image.new('RGB', (256,256)) 14 | draw = ImageDraw.Draw(im) 15 | draw.rectangle([0, 0, 256, 256], (0,0,255,20)) 16 | for x in xrange(32, 256-32, 32): 17 | draw.rectangle([x, 0, x + 16, 50], (0,255,255,20)) 18 | draw.rectangle([x, 256-50, x + 16, 256], (0,255,255,20)) 19 | if x > 32: 20 | draw.rectangle([0, x, 50, x + 16], (0,255,255,20)) 21 | draw.rectangle([256-50, x, 256, x + 16], (0,255,255,20)) 22 | draw.rectangle([50, 50, 256-50, 256-50], (255,0,255,20)) 23 | del draw 24 | im = im.resize((128,128)) 25 | im.save('tverts.png', 'PNG') 26 | 27 | im = Image.new('RGBA', (256,256)) 28 | draw = ImageDraw.Draw(im) 29 | draw.rectangle([0, 0, 256, 256], (0,0,255,20)) 30 | draw.pieslice([40,40,256-40,256-40], 45, -45, (255,0,0,100)) 31 | draw.ellipse([80,80,256-80,256-80], (0,255,0,200)) 32 | draw.ellipse([35+80,80,35+256-80,256-80], (0,200,200,200)) 33 | del draw 34 | im.save('rgba.png', 'PNG') 35 | -------------------------------------------------------------------------------- /test/getcolors.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import requests 3 | from PIL import Image 4 | from collections import defaultdict 5 | 6 | filename = 'msquares_color.png' 7 | 8 | if not os.path.exists(filename): 9 | baseurl = 'https://prideout.net/assets/' 10 | url = baseurl + filename 11 | r = requests.get(url, stream=True) 12 | with open(filename, 'wb') as fd: 13 | for chunk in r.iter_content(): 14 | fd.write(chunk) 15 | 16 | im = Image.open(filename) 17 | im.split()[3].save('alpha.png') 18 | Image.merge('RGB', im.split()[0:3]).save('rgb.png') 19 | cols = defaultdict(set) 20 | for pixel in im.getdata(): 21 | r, g, b, a = pixel 22 | argb = '%0.2x%0.2x%0.2x%0.2x' % (a, r, g, b) 23 | cols[a].add(argb) 24 | alphas = cols.keys() 25 | alphas.sort() 26 | final = [] 27 | for alpha in alphas: 28 | for col in cols[alpha]: 29 | final.append(col) 30 | x = 0 31 | for col in final: 32 | print '0x' + col + ',', 33 | x = x + 1 34 | if x == 5: 35 | print 36 | x = 0 37 | -------------------------------------------------------------------------------- /test/hierarchy.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | flare = json.load(open('flare.json')) 4 | print flare 5 | 6 | things = [] 7 | 8 | def traverse(node, parent): 9 | 10 | me = len(things) 11 | print '{:3} {}'.format(me, node['name']) 12 | things.append(parent) 13 | 14 | children = node.get('children', []) 15 | for child in children: 16 | traverse(child, me) 17 | 18 | traverse(flare, 0) 19 | 20 | for i in xrange(len(things)): 21 | print '{:3},'.format(things[i]), 22 | if (i + 1) % 12 == 0: print; 23 | 24 | print '---\n', len(things) -------------------------------------------------------------------------------- /test/msquares.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 12 | 13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /test/msquares_diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prideout/par/24f26c12926b746db5814de759163144ad79843a/test/msquares_diagram.jpg -------------------------------------------------------------------------------- /test/octasphere_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prideout/par/24f26c12926b746db5814de759163144ad79843a/test/octasphere_color.png -------------------------------------------------------------------------------- /test/octasphere_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prideout/par/24f26c12926b746db5814de759163144ad79843a/test/octasphere_normal.png -------------------------------------------------------------------------------- /test/octasphere_orm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prideout/par/24f26c12926b746db5814de759163144ad79843a/test/octasphere_orm.png -------------------------------------------------------------------------------- /test/packd3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31 | 32 | 33 | 111 | -------------------------------------------------------------------------------- /test/par_asset.h: -------------------------------------------------------------------------------- 1 | #include "whereami.h" 2 | #include "sds.h" 3 | 4 | #define PAR_EASYCURL_IMPLEMENTATION 5 | #include "par_easycurl.h" 6 | 7 | #define PAR_FILECACHE_IMPLEMENTATION 8 | #include "par_filecache.h" 9 | 10 | static sds _exedir = 0; 11 | static sds _baseurl = 0; 12 | 13 | static void asset_init() 14 | { 15 | par_easycurl_init(0); 16 | _baseurl = sdsnew("https://prideout.net/assets/"); 17 | int length = wai_getExecutablePath(0, 0, 0); 18 | _exedir = sdsnewlen("", length); 19 | int dirlen; 20 | wai_getExecutablePath(_exedir, length, &dirlen); 21 | sdsrange(_exedir, 0, dirlen); 22 | sds prefix = sdscat(sdsdup(_exedir), "cache_"); 23 | par_filecache_init(prefix, 10 * 1024 * 1024); 24 | sdsfree(prefix); 25 | } 26 | 27 | static void asset_get(char const* filename, par_byte** data, int* nbytes) 28 | { 29 | int exists = par_filecache_load(filename, data, nbytes, 0, 0); 30 | if (exists) { 31 | return; 32 | } 33 | sds srcurl = sdscat(sdsdup(_baseurl), filename); 34 | printf("Downloading %s...\n", srcurl); 35 | int success = par_easycurl_to_memory(srcurl, data, nbytes); 36 | if (!success) { 37 | exit(1); 38 | } 39 | par_filecache_save(filename, *data, *nbytes, 0, 0); 40 | sdsfree(srcurl); 41 | } 42 | -------------------------------------------------------------------------------- /test/rgb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prideout/par/24f26c12926b746db5814de759163144ad79843a/test/rgb.png -------------------------------------------------------------------------------- /test/rgba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prideout/par/24f26c12926b746db5814de759163144ad79843a/test/rgba.png -------------------------------------------------------------------------------- /test/sds.h: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Oran Agra 5 | * Copyright (c) 2015, Redis Labs, Inc 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef __SDS_H 34 | #define __SDS_H 35 | 36 | #define SDS_MAX_PREALLOC (1024 * 1024) 37 | 38 | #include 39 | #include 40 | #include 41 | 42 | typedef char* sds; 43 | 44 | /* Note: sdshdr5 is never used, we just access the flags byte directly. 45 | * However is here to document the layout of type 5 SDS strings. */ 46 | struct __attribute__((__packed__)) sdshdr5 { 47 | unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ 48 | char buf[]; 49 | }; 50 | struct __attribute__((__packed__)) sdshdr8 { 51 | uint8_t len; /* used */ 52 | uint8_t alloc; /* excluding the header and null terminator */ 53 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 54 | char buf[]; 55 | }; 56 | struct __attribute__((__packed__)) sdshdr16 { 57 | uint16_t len; /* used */ 58 | uint16_t alloc; /* excluding the header and null terminator */ 59 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 60 | char buf[]; 61 | }; 62 | struct __attribute__((__packed__)) sdshdr32 { 63 | uint32_t len; /* used */ 64 | uint32_t alloc; /* excluding the header and null terminator */ 65 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 66 | char buf[]; 67 | }; 68 | struct __attribute__((__packed__)) sdshdr64 { 69 | uint64_t len; /* used */ 70 | uint64_t alloc; /* excluding the header and null terminator */ 71 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 72 | char buf[]; 73 | }; 74 | 75 | #define SDS_TYPE_5 0 76 | #define SDS_TYPE_8 1 77 | #define SDS_TYPE_16 2 78 | #define SDS_TYPE_32 3 79 | #define SDS_TYPE_64 4 80 | #define SDS_TYPE_MASK 7 81 | #define SDS_TYPE_BITS 3 82 | #define SDS_HDR_VAR(T, s) \ 83 | struct sdshdr ## T* sh = (void*) ((s) - (sizeof(struct sdshdr ## T))); 84 | #define SDS_HDR(T, s) ((struct sdshdr ## T*)((s) - (sizeof(struct sdshdr ## T)))) 85 | #define SDS_TYPE_5_LEN(f) ((f) >> SDS_TYPE_BITS) 86 | 87 | static inline size_t sdslen(const sds s) 88 | { 89 | unsigned char flags = s[-1]; 90 | switch (flags & SDS_TYPE_MASK) { 91 | case SDS_TYPE_5: 92 | return SDS_TYPE_5_LEN(flags); 93 | case SDS_TYPE_8: 94 | return SDS_HDR(8, s)->len; 95 | case SDS_TYPE_16: 96 | return SDS_HDR(16, s)->len; 97 | case SDS_TYPE_32: 98 | return SDS_HDR(32, s)->len; 99 | case SDS_TYPE_64: 100 | return SDS_HDR(64, s)->len; 101 | } 102 | return 0; 103 | } 104 | 105 | static inline size_t sdsavail(const sds s) 106 | { 107 | unsigned char flags = s[-1]; 108 | switch (flags & SDS_TYPE_MASK) { 109 | case SDS_TYPE_5: { 110 | return 0; 111 | } 112 | case SDS_TYPE_8: { 113 | SDS_HDR_VAR(8, s); 114 | return sh->alloc - sh->len; 115 | } 116 | case SDS_TYPE_16: { 117 | SDS_HDR_VAR(16, s); 118 | return sh->alloc - sh->len; 119 | } 120 | case SDS_TYPE_32: { 121 | SDS_HDR_VAR(32, s); 122 | return sh->alloc - sh->len; 123 | } 124 | case SDS_TYPE_64: { 125 | SDS_HDR_VAR(64, s); 126 | return sh->alloc - sh->len; 127 | } 128 | } 129 | return 0; 130 | } 131 | 132 | static inline void sdssetlen(sds s, size_t newlen) 133 | { 134 | unsigned char flags = s[-1]; 135 | switch (flags & SDS_TYPE_MASK) { 136 | case SDS_TYPE_5: { 137 | unsigned char* fp = ((unsigned char*) s) - 1; 138 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); 139 | } break; 140 | case SDS_TYPE_8: 141 | SDS_HDR(8, s)->len = newlen; 142 | break; 143 | case SDS_TYPE_16: 144 | SDS_HDR(16, s)->len = newlen; 145 | break; 146 | case SDS_TYPE_32: 147 | SDS_HDR(32, s)->len = newlen; 148 | break; 149 | case SDS_TYPE_64: 150 | SDS_HDR(64, s)->len = newlen; 151 | break; 152 | } 153 | } 154 | 155 | static inline void sdsinclen(sds s, size_t inc) 156 | { 157 | unsigned char flags = s[-1]; 158 | switch (flags & SDS_TYPE_MASK) { 159 | case SDS_TYPE_5: { 160 | unsigned char* fp = ((unsigned char*) s) - 1; 161 | unsigned char newlen = SDS_TYPE_5_LEN(flags) + inc; 162 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); 163 | } break; 164 | case SDS_TYPE_8: 165 | SDS_HDR(8, s)->len += inc; 166 | break; 167 | case SDS_TYPE_16: 168 | SDS_HDR(16, s)->len += inc; 169 | break; 170 | case SDS_TYPE_32: 171 | SDS_HDR(32, s)->len += inc; 172 | break; 173 | case SDS_TYPE_64: 174 | SDS_HDR(64, s)->len += inc; 175 | break; 176 | } 177 | } 178 | 179 | /* sdsalloc() = sdsavail() + sdslen() */ 180 | static inline size_t sdsalloc(const sds s) 181 | { 182 | unsigned char flags = s[-1]; 183 | switch (flags & SDS_TYPE_MASK) { 184 | case SDS_TYPE_5: 185 | return SDS_TYPE_5_LEN(flags); 186 | case SDS_TYPE_8: 187 | return SDS_HDR(8, s)->alloc; 188 | case SDS_TYPE_16: 189 | return SDS_HDR(16, s)->alloc; 190 | case SDS_TYPE_32: 191 | return SDS_HDR(32, s)->alloc; 192 | case SDS_TYPE_64: 193 | return SDS_HDR(64, s)->alloc; 194 | } 195 | return 0; 196 | } 197 | 198 | static inline void sdssetalloc(sds s, size_t newlen) 199 | { 200 | unsigned char flags = s[-1]; 201 | switch (flags & SDS_TYPE_MASK) { 202 | case SDS_TYPE_5: 203 | /* Nothing to do, this type has no total allocation info. */ 204 | break; 205 | case SDS_TYPE_8: 206 | SDS_HDR(8, s)->alloc = newlen; 207 | break; 208 | case SDS_TYPE_16: 209 | SDS_HDR(16, s)->alloc = newlen; 210 | break; 211 | case SDS_TYPE_32: 212 | SDS_HDR(32, s)->alloc = newlen; 213 | break; 214 | case SDS_TYPE_64: 215 | SDS_HDR(64, s)->alloc = newlen; 216 | break; 217 | } 218 | } 219 | 220 | sds sdsnewlen(const void* init, size_t initlen); 221 | sds sdsnew(const char* init); 222 | sds sdsempty(void); 223 | sds sdsdup(const sds s); 224 | void sdsfree(sds s); 225 | sds sdsgrowzero(sds s, size_t len); 226 | sds sdscatlen(sds s, const void* t, size_t len); 227 | sds sdscat(sds s, const char* t); 228 | sds sdscatsds(sds s, const sds t); 229 | sds sdscpylen(sds s, const char* t, size_t len); 230 | sds sdscpy(sds s, const char* t); 231 | 232 | sds sdscatvprintf(sds s, const char* fmt, va_list ap); 233 | #ifdef __GNUC__ 234 | sds sdscatprintf(sds s, const char* fmt, ...) 235 | __attribute__((format(printf, 2, 3))); 236 | #else 237 | sds sdscatprintf(sds s, const char* fmt, ...); 238 | #endif 239 | 240 | sds sdscatfmt(sds s, char const* fmt, ...); 241 | sds sdstrim(sds s, const char* cset); 242 | void sdsrange(sds s, int start, int end); 243 | void sdsupdatelen(sds s); 244 | void sdsclear(sds s); 245 | int sdscmp(const sds s1, const sds s2); 246 | sds* sdssplitlen( 247 | const char* s, int len, const char* sep, int seplen, int* count); 248 | void sdsfreesplitres(sds* tokens, int count); 249 | void sdstolower(sds s); 250 | void sdstoupper(sds s); 251 | sds sdsfromlonglong(long long value); 252 | sds sdscatrepr(sds s, const char* p, size_t len); 253 | sds* sdssplitargs(const char* line, int* argc); 254 | sds sdsmapchars(sds s, const char* from, const char* to, size_t setlen); 255 | sds sdsjoin(char** argv, int argc, char* sep); 256 | sds sdsjoinsds(sds* argv, int argc, const char* sep, size_t seplen); 257 | 258 | /* Low level functions exposed to the user API */ 259 | sds sdsMakeRoomFor(sds s, size_t addlen); 260 | void sdsIncrLen(sds s, int incr); 261 | sds sdsRemoveFreeSpace(sds s); 262 | size_t sdsAllocSize(sds s); 263 | void* sdsAllocPtr(sds s); 264 | 265 | #ifdef REDIS_TEST 266 | int sdsTest(int argc, char* argv[]); 267 | #endif 268 | 269 | #endif 270 | -------------------------------------------------------------------------------- /test/sdsalloc.h: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Oran Agra 5 | * Copyright (c) 2015, Redis Labs, Inc 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /* SDS allocator selection. 34 | * 35 | * This file is used in order to change the SDS allocator at compile time. 36 | * Just define the following defines to what you want to use. Also add 37 | * the include of your alternate allocator if needed (not needed in order 38 | * to use the default libc allocator). */ 39 | 40 | #define s_malloc malloc 41 | #define s_realloc realloc 42 | #define s_free free 43 | -------------------------------------------------------------------------------- /test/test_bluenoise.c: -------------------------------------------------------------------------------- 1 | #include "par_asset.h" 2 | #include "lodepng.h" 3 | 4 | #define PAR_BLUENOISE_IMPLEMENTATION 5 | #include "par_bluenoise.h" 6 | 7 | const int MAXPTS = 100000; 8 | const int DENSITY = 200000; 9 | const int RESOLUTION = 384; 10 | 11 | #define CLAMP(x, min, max) ((x < min) ? min : ((x > max) ? max : x)) 12 | 13 | static void test_bluenoise() 14 | { 15 | int nbytes; 16 | par_byte* data; 17 | 18 | // Download and decode the density image. 19 | asset_get("trillium.png", &data, &nbytes); 20 | unsigned dims[2] = {0, 0}; 21 | unsigned char* pixels; 22 | lodepng_decode_memory(&pixels, &dims[0], &dims[1], data, nbytes, LCT_GREY, 23 | 8); 24 | free(data); 25 | 26 | // Download the tileset and initialize the bluenoise context. 27 | nbytes = 0; 28 | asset_get("bluenoise.trimmed.bin", &data, &nbytes); 29 | par_bluenoise_context* ctx; 30 | ctx = par_bluenoise_from_buffer(data, nbytes, MAXPTS); 31 | free(data); 32 | 33 | // Copy the density image into the bluenoise context and free it. 34 | par_bluenoise_density_from_gray(ctx, pixels, dims[0], dims[1], 1); 35 | free(pixels); 36 | 37 | // Generate points. 38 | float left = -0.5; 39 | float bottom = -0.5; 40 | float right = 0.5; 41 | float top = 0.5; 42 | int npts; 43 | par_bluenoise_set_viewport(ctx, left, bottom, right, top); 44 | float* cpupts = par_bluenoise_generate(ctx, DENSITY, &npts); 45 | par_bluenoise_free(ctx); 46 | 47 | // Draw points. 48 | pixels = malloc(RESOLUTION * RESOLUTION); 49 | memset(pixels, 0xff, RESOLUTION * RESOLUTION); 50 | for (int i = 0; i < npts; i++) { 51 | float x = *cpupts++; 52 | float y = *cpupts++; 53 | cpupts++; 54 | int i = CLAMP(RESOLUTION * (x + 0.5), 0, RESOLUTION - 1); 55 | int j = CLAMP(RESOLUTION * (0.5 - y), 0, RESOLUTION - 1); 56 | pixels[i + j * RESOLUTION] = 0; 57 | } 58 | 59 | // Write out the image. 60 | lodepng_encode_file("build/bluenoise.png", pixels, RESOLUTION, RESOLUTION, 61 | LCT_GREY, 8); 62 | free(pixels); 63 | } 64 | 65 | int main(int argc, char* argv[]) 66 | { 67 | asset_init(); 68 | test_bluenoise(); 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /test/test_bubbles.c: -------------------------------------------------------------------------------- 1 | #include "describe.h" 2 | 3 | #define PAR_BUBBLES_IMPLEMENTATION 4 | #include "par_bubbles.h" 5 | 6 | #define PAR_SHAPES_T uint32_t 7 | #define PAR_SHAPES_IMPLEMENTATION 8 | #include "par_shapes.h" 9 | 10 | static par_bubbles_t* bubbles; 11 | 12 | #define NRADIUSES 100 13 | static double radiuses[NRADIUSES]; 14 | 15 | #define NNODES 252 16 | static int hierarchy[NNODES] = { 17 | 0, 0, 1, 2, 2, 2, 2, 1, 7, 7, 7, 7, 18 | 7, 1, 13, 0, 15, 15, 15, 18, 18, 18, 18, 18, 19 | 18, 18, 18, 18, 15, 15, 15, 15, 15, 15, 15, 15, 20 | 15, 0, 37, 38, 38, 38, 38, 38, 37, 37, 37, 37, 21 | 37, 37, 0, 50, 50, 50, 50, 0, 55, 0, 57, 57, 22 | 57, 57, 57, 57, 57, 57, 0, 66, 66, 66, 66, 66, 23 | 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 24 | 66, 66, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 25 | 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 26 | 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 66, 66, 27 | 66, 66, 66, 66, 66, 66, 66, 66, 0, 128, 128, 128, 28 | 128, 128, 128, 128, 128, 128, 128, 0, 139, 139, 139, 139, 29 | 139, 139, 139, 146, 146, 139, 139, 139, 139, 152, 152, 152, 30 | 139, 139, 139, 158, 158, 158, 158, 139, 139, 139, 139, 139, 31 | 0, 168, 169, 169, 169, 169, 169, 168, 175, 175, 175, 175, 32 | 175, 175, 175, 175, 175, 175, 175, 168, 187, 187, 187, 187, 33 | 187, 187, 193, 193, 193, 193, 187, 187, 187, 168, 201, 201, 34 | 201, 201, 168, 206, 206, 206, 168, 210, 211, 211, 211, 210, 35 | 215, 215, 215, 215, 215, 210, 221, 221, 221, 210, 210, 226, 36 | 226, 226, 210, 230, 230, 230, 230, 230, 230, 230, 230, 230, 37 | 230, 230, 230, 230, 230, 230, 210, 210, 210, 210, 210, 168, 38 | }; 39 | 40 | static int get_depth(int* tree, int i) 41 | { 42 | int d = 0; 43 | while (i) { 44 | i = tree[i]; 45 | d++; 46 | } 47 | return d; 48 | } 49 | 50 | int main() 51 | { 52 | for (int i = 0; i < NRADIUSES; i++) { 53 | radiuses[i] = 1 + rand() % 10; 54 | } 55 | 56 | describe("par_bubbles_pack") { 57 | 58 | it("should pass a simple smoke test") { 59 | bubbles = par_bubbles_pack(radiuses, NRADIUSES); 60 | assert_ok(bubbles); 61 | assert_equal(bubbles->count, (int) NRADIUSES); 62 | par_bubbles_export(bubbles, "build/test_bubbles_pack.svg"); 63 | par_bubbles_free_result(bubbles); 64 | } 65 | 66 | it("should handle a small number of nodes") { 67 | bubbles = par_bubbles_pack(radiuses, 0); 68 | par_bubbles_export(bubbles, "build/test_bubbles_pack0.svg"); 69 | par_bubbles_free_result(bubbles); 70 | bubbles = par_bubbles_pack(radiuses, 1); 71 | par_bubbles_export(bubbles, "build/test_bubbles_pack1.svg"); 72 | par_bubbles_free_result(bubbles); 73 | bubbles = par_bubbles_pack(radiuses, 2); 74 | par_bubbles_export(bubbles, "build/test_bubbles_pack2.svg"); 75 | par_bubbles_free_result(bubbles); 76 | bubbles = par_bubbles_pack(radiuses, 3); 77 | par_bubbles_export(bubbles, "build/test_bubbles_pack3.svg"); 78 | par_bubbles_free_result(bubbles); 79 | } 80 | 81 | it("should work with small hierarchy") { 82 | static int hierarchy[10] = { 83 | 0, 0, 0, 0, 1, 1, 2, 84 | 5, 5, 5, 85 | }; 86 | bubbles = par_bubbles_hpack_circle(hierarchy, 10, 100); 87 | par_bubbles_export(bubbles, "build/test_bubbles_hpack_circle1.svg"); 88 | par_bubbles_free_result(bubbles); 89 | } 90 | 91 | it("can be exported to an SVG") { 92 | bubbles = par_bubbles_hpack_circle(hierarchy, NNODES, 100); 93 | par_bubbles_export(bubbles, "build/test_bubbles_hpack_circle2.svg"); 94 | par_bubbles_free_result(bubbles); 95 | } 96 | 97 | it("can be exported to an OBJ") { 98 | 99 | const int nnodes = 2e3; 100 | const float zscale = 0.01; 101 | 102 | // First, generate a random tree. Square the random parent pointers 103 | // to make the graph distribution a bit more interesting, and to 104 | // make it easier for humans to find deep portions of the tree. 105 | int* tree = malloc(sizeof(int) * nnodes); 106 | tree[0] = 0; 107 | for (int i = 1; i < nnodes; i++) { 108 | float a = (float) rand() / RAND_MAX; 109 | float b = (float) rand() / RAND_MAX; 110 | tree[i] = i * a * b; 111 | } 112 | 113 | // Perform circle packing. 114 | bubbles = par_bubbles_hpack_circle(tree, nnodes, 1.0); 115 | 116 | // Create template shape. 117 | float normal[3] = {0, 0, 1}; 118 | float center[3] = {0, 0, 0}; 119 | par_shapes_mesh* template = par_shapes_create_disk(1.0, 64, 120 | center, normal); 121 | 122 | // Merge each circle into the scene. 123 | par_shapes_mesh* scene = par_shapes_create_empty(); 124 | double const* xyr = bubbles->xyr; 125 | par_shapes_mesh* clone = 0; 126 | for (int i = 0; i < bubbles->count; i++, xyr += 3) { 127 | float d = get_depth(tree, i); 128 | clone = par_shapes_clone(template, clone); 129 | par_shapes_scale(clone, xyr[2], xyr[2], 1.0); 130 | par_shapes_translate(clone, xyr[0], xyr[1], d * zscale); 131 | par_shapes_merge(scene, clone); 132 | } 133 | 134 | // Export the OBJ file. 135 | char const* const filename = "build/bubbles.obj"; 136 | par_shapes_export(scene, filename); 137 | 138 | // Free memory. 139 | par_shapes_free_mesh(template); 140 | par_shapes_free_mesh(clone); 141 | par_shapes_free_mesh(scene); 142 | par_bubbles_free_result(bubbles); 143 | free(tree); 144 | } 145 | 146 | } 147 | 148 | describe("precision") { 149 | 150 | #define H1 10 151 | #define H2 20 152 | 153 | it("works well with relative coordinate systems") { 154 | bubbles = par_bubbles_hpack_local(hierarchy, NNODES); 155 | par_bubbles_export_local(bubbles, 0, 156 | "build/test_bubbles_hpack_local1.svg"); 157 | par_bubbles_export_local(bubbles, 158, 158 | "build/test_bubbles_hpack_local2.svg"); 159 | par_bubbles_free_result(bubbles); 160 | } 161 | 162 | it("is poor with deep nesting and non-local packing") { 163 | static int hierarchy[H2] = {0}; 164 | for (int i = 1; i < H1; i++) { 165 | hierarchy[i] = i - 1; 166 | } 167 | for (int i = H1; i < H2; i++) { 168 | hierarchy[i] = H1 - 1; 169 | } 170 | bubbles = par_bubbles_hpack_circle(hierarchy, H2, 1); 171 | par_bubbles_export_local(bubbles, H1 - 1, 172 | "build/test_bubbles_hpack_local3.svg"); 173 | par_bubbles_free_result(bubbles); 174 | } 175 | 176 | it("is great with deep nesting and local packing") { 177 | static int hierarchy[H2] = {0}; 178 | for (int i = 1; i < H1; i++) { 179 | hierarchy[i] = i - 1; 180 | } 181 | for (int i = H1; i < H2; i++) { 182 | hierarchy[i] = H1 - 1; 183 | } 184 | bubbles = par_bubbles_hpack_local(hierarchy, H2); 185 | par_bubbles_export_local(bubbles, H1 - 1, 186 | "build/test_bubbles_hpack_local4.svg"); 187 | par_bubbles_free_result(bubbles); 188 | } 189 | 190 | #undef H1 191 | #undef H2 192 | } 193 | 194 | describe("par_bubbles_find_local") { 195 | 196 | it("finds the smallest node that completely encloses a box") { 197 | bubbles = par_bubbles_hpack_local(hierarchy, NNODES); 198 | // This aabb encloses node 158, which means node 139 is the deepest 199 | // circle that encloses it. 200 | double cx = 40.804406 / 100.0f; 201 | double cy = -15.209295 / 100.0f; 202 | double r = 8.133015 / 100.0f; 203 | double aabb[4] = { cx - r, cy - r, cx + r, cy + r }; 204 | int node = par_bubbles_find_local(bubbles, aabb, 0); 205 | par_bubbles_free_result(bubbles); 206 | assert_equal(node, 139); 207 | } 208 | 209 | it("ditto, but using a non-root coordinate system") { 210 | bubbles = par_bubbles_hpack_local(hierarchy, NNODES); 211 | par_bubbles_export_local(bubbles, 139, 212 | "build/test_bubbles_hpack_local5.svg"); 213 | double cx = -0.511578, cy = 0.147831, r = 0.08; 214 | double aabb[4] = { cx - r, cy - r, cx + r, cy + r }; 215 | int node = par_bubbles_find_local(bubbles, aabb, 139); 216 | par_bubbles_free_result(bubbles); 217 | assert_equal(node, 158); 218 | } 219 | 220 | it("is used by the pick_local function") { 221 | bubbles = par_bubbles_hpack_local(hierarchy, NNODES); 222 | double cx = -0.654258, cy = 0.065455; 223 | double minradius = 0; 224 | int node = par_bubbles_pick_local(bubbles, cx, cy, 139, minradius); 225 | assert_equal(node, 162); 226 | minradius = 0.095; 227 | node = par_bubbles_pick_local(bubbles, cx, cy, 139, minradius); 228 | assert_equal(node, 158); 229 | par_bubbles_free_result(bubbles); 230 | } 231 | } 232 | 233 | return assert_failures(); 234 | } 235 | -------------------------------------------------------------------------------- /test/test_c.c: -------------------------------------------------------------------------------- 1 | #define PAR_STRING_BLOCKS_IMPLEMENTATION 2 | #include "par_string_blocks.h" 3 | 4 | #define PAR_MSQUARES_IMPLEMENTATION 5 | #include "par_msquares.h" 6 | 7 | #define PAR_SHAPES_IMPLEMENTATION 8 | #include "par_shapes.h" 9 | 10 | #define PAR_BLUENOISE_IMPLEMENTATION 11 | #include "par_bluenoise.h" 12 | 13 | #define PAR_EASYCURL_IMPLEMENTATION 14 | #include "par_easycurl.h" 15 | 16 | #define PAR_FILECACHE_IMPLEMENTATION 17 | #include "par_filecache.h" 18 | 19 | #define PAR_BUBBLES_IMPLEMENTATION 20 | #include "par_bubbles.h" 21 | 22 | #define PAR_SPRUNE_IMPLEMENTATION 23 | #include "par_sprune.h" 24 | 25 | #define PAR_EASINGS_IMPLEMENTATION 26 | #include "par_easings.h" 27 | 28 | #define PAR_STREAMLINES_IMPLEMENTATION 29 | #include "par_streamlines.h" 30 | 31 | #define PAR_CAMERA_CONTROL_IMPLEMENTATION 32 | #include "par_camera_control.h" 33 | 34 | #define PAR_OCTASPHERE_IMPLEMENTATION 35 | #include "par_octasphere.h" 36 | 37 | int main(int argc, char* argv[]) 38 | { 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /test/test_cpp.cpp: -------------------------------------------------------------------------------- 1 | #define PAR_MSQUARES_IMPLEMENTATION 2 | #include "par_msquares.h" 3 | 4 | #define PAR_SHAPES_IMPLEMENTATION 5 | #include "par_shapes.h" 6 | 7 | #define PAR_BLUENOISE_IMPLEMENTATION 8 | #include "par_bluenoise.h" 9 | 10 | #define PAR_EASYCURL_IMPLEMENTATION 11 | #include "par_easycurl.h" 12 | 13 | #define PAR_FILECACHE_IMPLEMENTATION 14 | #include "par_filecache.h" 15 | 16 | #define PAR_BUBBLES_IMPLEMENTATION 17 | #include "par_bubbles.h" 18 | 19 | #define PAR_SPRUNE_IMPLEMENTATION 20 | #include "par_sprune.h" 21 | 22 | #define PAR_EASINGS_IMPLEMENTATION 23 | #include "par_easings.h" 24 | 25 | #define PAR_STREAMLINES_IMPLEMENTATION 26 | #include "par_streamlines.h" 27 | 28 | #define PAR_STRING_BLOCKS_IMPLEMENTATION 29 | #include "par_string_blocks.h" 30 | 31 | #define PAR_CAMERA_CONTROL_IMPLEMENTATION 32 | #include "par_camera_control.h" 33 | 34 | #define PAR_OCTASPHERE_IMPLEMENTATION 35 | #include "par_octasphere.h" 36 | 37 | int main(int argc, char* argv[]) 38 | { 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /test/test_filecache.c: -------------------------------------------------------------------------------- 1 | #include "describe.h" 2 | 3 | #define PAR_FILECACHE_IMPLEMENTATION 4 | #include "par_filecache.h" 5 | 6 | #include 7 | #include 8 | 9 | #if ENABLE_LZ4 10 | #define PREFIX "build/tfc.lz4." 11 | #else 12 | #define PREFIX "build/tfc.unc." 13 | #endif 14 | 15 | int main() 16 | { 17 | describe("the basics") { 18 | it("should be able to initialize and evict") { 19 | par_filecache_init(PREFIX, 1200); 20 | par_filecache_evict_all(); 21 | par_filecache_evict_all(); 22 | } 23 | it("should save and load small files") { 24 | char const* payload = "01234"; 25 | par_filecache_save("small", (uint8_t*) payload, 5, 0, 0); 26 | uint8_t* received = 0; 27 | int nbytes = 0; 28 | int loaded = par_filecache_load("small", &received, &nbytes, 0, 0); 29 | assert_ok(loaded); 30 | assert_ok(received); 31 | assert_equal(nbytes, 5); 32 | assert_ok(!memcmp(received, payload, 5)); 33 | free(received); 34 | } 35 | it("should save and load larger files") { 36 | char* payload = calloc(1024, 1); 37 | srand(1); 38 | for (int i = 0; i < 512; i++) { 39 | payload[i] = rand() % 256; 40 | } 41 | par_filecache_save("big", (uint8_t*) payload, 1024, 0, 0); 42 | uint8_t* received = 0; 43 | int nbytes = 0; 44 | int loaded = par_filecache_load("big", &received, &nbytes, 0, 0); 45 | assert_ok(loaded); 46 | assert_ok(received); 47 | assert_equal(nbytes, 1024); 48 | assert_ok(!memcmp(received, payload, 1024)); 49 | free(received); 50 | free(payload); 51 | } 52 | it("should evict the oldest file when exceeding max size") { 53 | uint8_t* received = 0; 54 | int nbytes = 0; 55 | char* payload = calloc(1024, 1); 56 | par_filecache_save("second", (uint8_t*) payload, 1024, 0, 0); 57 | free(payload); 58 | int loaded = par_filecache_load("big", &received, &nbytes, 0, 0); 59 | #if ENABLE_LZ4 60 | assert_equal(loaded, 1); 61 | free(received); 62 | #else 63 | assert_equal(loaded, 0); 64 | #endif 65 | } 66 | it("returns false when not found") { 67 | uint8_t* received = 0; 68 | int nbytes = 0; 69 | int loaded = par_filecache_load("bar", &received, &nbytes, 0, 0); 70 | assert_equal(loaded, 0); 71 | } 72 | it("supports a fixed-size header (e.g., version number)") { 73 | char* payload = "philip"; 74 | char* header = "v0001"; 75 | par_filecache_save("versioned", (uint8_t*) payload, 76 | strlen(payload), (uint8_t*) header, 5); 77 | char* loaded_payload; 78 | char loaded_header[5] = {0}; 79 | int nbytes = 0; 80 | int loaded = par_filecache_load("versioned", 81 | (uint8_t**) &loaded_payload, &nbytes, 82 | (uint8_t*) loaded_header, 5); 83 | assert_equal(loaded, 1); 84 | assert_equal(nbytes, 6); 85 | assert_ok(!memcmp(loaded_header, header, 5)); 86 | assert_ok(!memcmp(loaded_payload, payload, 6)); 87 | free(loaded_payload); 88 | } 89 | } 90 | 91 | describe("robustness") { 92 | it("is graceful when files are deleted") { 93 | int error = remove(PREFIX "versioned"); 94 | assert_equal(error, 0); 95 | char* loaded_payload; 96 | char loaded_header[5] = {0}; 97 | int nbytes = 0; 98 | int loaded = par_filecache_load("versioned", 99 | (uint8_t**) &loaded_payload, &nbytes, 100 | (uint8_t*) loaded_header, 5); 101 | assert_equal(loaded, 0); 102 | } 103 | it("is graceful when file content vanishes") { 104 | int error = remove(PREFIX "second"); 105 | assert_equal(error, 0); 106 | FILE* cachefile = fopen(PREFIX "second", "wt"); 107 | fclose(cachefile); 108 | char* loaded_payload; 109 | char loaded_header[5] = {0}; 110 | int nbytes = 0; 111 | int loaded = par_filecache_load("second", 112 | (uint8_t**) &loaded_payload, &nbytes, 113 | (uint8_t*) loaded_header, 5); 114 | assert_equal(loaded, 0); 115 | } 116 | } 117 | 118 | return assert_failures(); 119 | } 120 | -------------------------------------------------------------------------------- /test/test_filecache_lz4.c: -------------------------------------------------------------------------------- 1 | #define ENABLE_LZ4 1 2 | #include "test_filecache.c" 3 | #include "lz4.c" 4 | -------------------------------------------------------------------------------- /test/test_linkage.c: -------------------------------------------------------------------------------- 1 | #define PAR_MSQUARES_IMPLEMENTATION 2 | #include "par_msquares.h" 3 | 4 | #define PAR_SHAPES_IMPLEMENTATION 5 | #include "par_shapes.h" 6 | 7 | #define PAR_BLUENOISE_IMPLEMENTATION 8 | #include "par_bluenoise.h" 9 | 10 | #define PAR_EASYCURL_IMPLEMENTATION 11 | #include "par_easycurl.h" 12 | 13 | #define PAR_FILECACHE_IMPLEMENTATION 14 | #include "par_filecache.h" 15 | 16 | #define PAR_BUBBLES_IMPLEMENTATION 17 | #include "par_bubbles.h" 18 | 19 | #define PAR_SPRUNE_IMPLEMENTATION 20 | #include "par_sprune.h" 21 | 22 | #define PAR_EASINGS_IMPLEMENTATION 23 | #include "par_easings.h" 24 | -------------------------------------------------------------------------------- /test/test_linkage.cpp: -------------------------------------------------------------------------------- 1 | #include "par_msquares.h" 2 | #include "par_shapes.h" 3 | #include "par_bluenoise.h" 4 | #include "par_easycurl.h" 5 | #include "par_filecache.h" 6 | #include "par_bubbles.h" 7 | #include "par_sprune.h" 8 | #include "par_easings.h" 9 | 10 | int main(int argc, char* argv[]) 11 | { 12 | par_shapes_mesh* test = par_shapes_create_cylinder(10, 10); 13 | par_shapes_free_mesh(test); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /test/test_octasphere.cpp: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | #include "describe.h" 3 | } 4 | 5 | #define PAR_OCTASPHERE_IMPLEMENTATION 6 | #include "par_octasphere.h" 7 | 8 | #define CGLTF_IMPLEMENTATION 9 | #define CGLTF_WRITE_IMPLEMENTATION 10 | #include "cgltf_write.h" 11 | 12 | static void generate_gltf(const char* gltf_path, const char* bin_path, int num_vertices, 13 | int num_indices, float minpos[3], float maxpos[3]) { 14 | cgltf_image images[3] = { 15 | {(char*)"octasphere color", (char*)"octasphere_color.png"}, 16 | {(char*)"octasphere orm", (char*)"octasphere_orm.png"}, 17 | {(char*)"octasphere normal", (char*)"octasphere_normal.png"}, 18 | }; 19 | cgltf_image& color_image = images[0]; 20 | cgltf_image& orm_image = images[1]; 21 | cgltf_image& normal_image = images[2]; 22 | 23 | cgltf_texture textures[3] = { 24 | {(char*)"octasphere color", &color_image}, 25 | {(char*)"octasphere orm", &orm_image}, 26 | {(char*)"octasphere normal", &normal_image}, 27 | }; 28 | cgltf_texture& color_texture = textures[0]; 29 | cgltf_texture& orm_texture = textures[1]; 30 | cgltf_texture& normal_texture = textures[2]; 31 | 32 | cgltf_material materials[1] = {}; 33 | cgltf_material& octasphere_material = materials[0]; 34 | 35 | octasphere_material.name = (char*)"octamat"; 36 | octasphere_material.alpha_cutoff = 0.5f; 37 | octasphere_material.has_pbr_metallic_roughness = true; 38 | octasphere_material.pbr_metallic_roughness = { 39 | .base_color_texture = {&color_texture, 0, 1.0f}, 40 | .metallic_roughness_texture = {&orm_texture, 0, 1.0f}, 41 | .base_color_factor = {1, 1, 1, 1}, 42 | .metallic_factor = 1, 43 | .roughness_factor = 1, 44 | }; 45 | octasphere_material.occlusion_texture = {&orm_texture, 0, 1.0f}; 46 | octasphere_material.normal_texture = {&normal_texture, 0, 1.0f}; 47 | 48 | // GEOMETRY 49 | 50 | cgltf_buffer buffers[1] = {}; 51 | cgltf_mesh meshes[1] = {}; 52 | cgltf_buffer_view buffer_views[4] = {}; 53 | cgltf_accessor accessors[4] = {}; 54 | 55 | cgltf_attribute attributes[3] = {}; 56 | cgltf_primitive prims[2] = {}; 57 | 58 | cgltf_buffer& octasphere_buffer = buffers[0]; 59 | 60 | octasphere_buffer.uri = (char*) bin_path; 61 | octasphere_buffer.size = num_vertices * 32 + num_indices * 2; 62 | 63 | buffer_views[0].buffer = &octasphere_buffer; 64 | buffer_views[0].size = num_vertices * sizeof(float) * 3; 65 | buffer_views[0].type = cgltf_buffer_view_type_vertices; 66 | 67 | buffer_views[1].buffer = &octasphere_buffer; 68 | buffer_views[1].size = num_vertices * sizeof(float) * 3; 69 | buffer_views[1].offset = buffer_views[0].offset; 70 | buffer_views[1].offset += buffer_views[0].size; 71 | buffer_views[1].type = cgltf_buffer_view_type_vertices; 72 | 73 | buffer_views[2].buffer = &octasphere_buffer; 74 | buffer_views[2].size = num_vertices * sizeof(float) * 2; 75 | buffer_views[2].offset = buffer_views[1].offset; 76 | buffer_views[2].offset += buffer_views[1].size; 77 | buffer_views[2].type = cgltf_buffer_view_type_vertices; 78 | 79 | buffer_views[3].buffer = &octasphere_buffer; 80 | buffer_views[3].size = num_indices * sizeof(uint16_t); 81 | buffer_views[3].offset = buffer_views[2].offset; 82 | buffer_views[3].offset += buffer_views[2].size; 83 | buffer_views[3].type = cgltf_buffer_view_type_indices; 84 | 85 | accessors[0].buffer_view = &buffer_views[0]; 86 | accessors[0].component_type = cgltf_component_type_r_32f; 87 | accessors[0].type = cgltf_type_vec3; 88 | accessors[0].count = num_vertices; 89 | accessors[0].has_min = accessors[0].has_max = true; 90 | 91 | for (int i = 0; i < 3; i++) { 92 | accessors[0].min[i] = minpos[i]; 93 | accessors[0].max[i] = maxpos[i]; 94 | } 95 | 96 | accessors[1].buffer_view = &buffer_views[1]; 97 | accessors[1].component_type = cgltf_component_type_r_32f; 98 | accessors[1].type = cgltf_type_vec3; 99 | accessors[1].count = num_vertices; 100 | 101 | accessors[2].buffer_view = &buffer_views[2]; 102 | accessors[2].component_type = cgltf_component_type_r_32f; 103 | accessors[2].type = cgltf_type_vec2; 104 | accessors[2].count = num_vertices; 105 | 106 | accessors[3].buffer_view = &buffer_views[3]; 107 | accessors[3].component_type = cgltf_component_type_r_16u; 108 | accessors[3].type = cgltf_type_scalar; 109 | accessors[3].count = num_indices; 110 | 111 | // ATTRIBUTES 112 | 113 | attributes[0].name = (char*)"POSITION"; 114 | attributes[0].data = &accessors[0]; 115 | attributes[0].type = cgltf_attribute_type_position; 116 | 117 | attributes[1].name = (char*)"NORMAL"; 118 | attributes[1].data = &accessors[1]; 119 | attributes[1].type = cgltf_attribute_type_normal; 120 | 121 | attributes[2].name = (char*)"TEXCOORD_0"; 122 | attributes[2].data = &accessors[2]; 123 | attributes[2].type = cgltf_attribute_type_texcoord; 124 | 125 | // PRIMITIVES AND MESHES 126 | 127 | cgltf_primitive& prim = prims[0]; 128 | prim.type = cgltf_primitive_type_triangles; 129 | prim.attributes = attributes; 130 | prim.attributes_count = 3; 131 | prim.indices = &accessors[3]; 132 | prim.material = &octasphere_material; 133 | 134 | cgltf_mesh& mesh = meshes[0]; 135 | mesh.name = (char*)"octasphere mesh"; 136 | mesh.primitives = &prim; 137 | mesh.primitives_count = 1; 138 | 139 | // NODE HIERARCHY 140 | 141 | cgltf_node nodes[2]; 142 | cgltf_node* pnodes[2] = {&nodes[0], &nodes[1]}; 143 | 144 | nodes[0] = { 145 | .name = (char*)"root", 146 | .parent = {}, 147 | .children = &pnodes[1], 148 | .children_count = 1, 149 | .skin = {}, 150 | .mesh = {}, 151 | .camera = {}, 152 | .light = {}, 153 | .weights = {}, 154 | .weights_count = 0, 155 | .has_translation = false, 156 | .has_rotation = false, 157 | .has_scale = false, 158 | .has_matrix = true, 159 | .translation = {}, 160 | .rotation = {}, 161 | .scale = {}, 162 | .matrix = { 163 | 1, 0, 0, 0, 164 | 0, 1, 0, 0, 165 | 0, 0, 1, 0, 166 | 0, 0, 0, 1, 167 | }, 168 | }; 169 | 170 | nodes[1] = { 171 | .name = (char*) "octasphere", 172 | .parent = {}, 173 | .children = {}, 174 | .children_count = 0, 175 | .skin = {}, 176 | .mesh = &meshes[0] 177 | }; 178 | 179 | cgltf_scene scene = {}; 180 | scene.nodes = pnodes; 181 | scene.nodes_count = 1; // only one root 182 | 183 | // ASSET 184 | 185 | cgltf_data asset = {}; 186 | asset.file_type = cgltf_file_type_gltf; 187 | asset.asset.generator = (char*)"test_octasphere"; 188 | asset.asset.version = (char*)"2.0"; 189 | 190 | asset.buffers = buffers; 191 | asset.buffers_count = sizeof(buffers) / sizeof(buffers[0]); 192 | 193 | asset.buffer_views = buffer_views; 194 | asset.buffer_views_count = sizeof(buffer_views) / sizeof(buffer_views[0]); 195 | 196 | asset.accessors = accessors; 197 | asset.accessors_count = sizeof(accessors) / sizeof(accessors[0]); 198 | 199 | asset.meshes = meshes; 200 | asset.meshes_count = sizeof(meshes) / sizeof(meshes[0]); 201 | 202 | asset.materials = materials; 203 | asset.materials_count = sizeof(materials) / sizeof(materials[0]); 204 | 205 | asset.images = images; 206 | asset.images_count = sizeof(images) / sizeof(images[0]); 207 | 208 | asset.textures = textures; 209 | asset.textures_count = sizeof(textures) / sizeof(textures[0]); 210 | 211 | asset.nodes = nodes; 212 | asset.nodes_count = sizeof(nodes) / sizeof(nodes[0]); 213 | 214 | asset.scenes = &scene; 215 | asset.scenes_count = 1; 216 | 217 | cgltf_options options = {}; 218 | cgltf_write_file(&options, gltf_path, &asset); 219 | } 220 | 221 | int main() 222 | { 223 | describe("octasphere generator") { 224 | 225 | it("should generate a tile-like shape") { 226 | par_octasphere_config config = { 227 | .corner_radius = 0.1, 228 | .width = 1.2, 229 | .height = 1.2, 230 | .depth = 0.3, 231 | .num_subdivisions = 2, 232 | .uv_mode = PAR_OCTASPHERE_UV_LATLONG, 233 | }; 234 | 235 | uint32_t indices_per_tile; 236 | uint32_t vertices_per_tile; 237 | par_octasphere_get_counts(&config, &indices_per_tile, &vertices_per_tile); 238 | 239 | par_octasphere_mesh octasphere = {}; 240 | octasphere.positions = (float*)malloc(vertices_per_tile * 12); 241 | octasphere.normals = (float*)malloc(vertices_per_tile * 12); 242 | octasphere.texcoords = (float*)malloc(vertices_per_tile * 8); 243 | octasphere.indices = (uint16_t*)malloc(indices_per_tile * 2); 244 | par_octasphere_populate(&config, &octasphere); 245 | 246 | free(octasphere.positions); 247 | free(octasphere.normals); 248 | free(octasphere.texcoords); 249 | free(octasphere.indices); 250 | } 251 | 252 | it("should generate rounded cube into glTF + bin files") { 253 | par_octasphere_config config = { 254 | .corner_radius = 0.4, 255 | .width = 1.2, 256 | .height = 1.2, 257 | .depth = 1.2, 258 | .num_subdivisions = 3, 259 | .uv_mode = PAR_OCTASPHERE_UV_LATLONG, 260 | }; 261 | 262 | uint32_t num_indices; 263 | uint32_t num_vertices; 264 | par_octasphere_get_counts(&config, &num_indices, &num_vertices); 265 | 266 | par_octasphere_mesh octasphere = {}; 267 | octasphere.positions = (float*)malloc(num_vertices * 12); 268 | octasphere.normals = (float*)malloc(num_vertices * 12); 269 | octasphere.texcoords = (float*)malloc(num_vertices * 8); 270 | octasphere.indices = (uint16_t*)malloc(num_indices * 2); 271 | par_octasphere_populate(&config, &octasphere); 272 | 273 | float minpos[3] = { 99, 99, 99}; 274 | float maxpos[3] = {-99, -99, -99}; 275 | float* ppos = octasphere.positions; 276 | for (int i = 0; i < num_vertices; i++, ppos += 3) { 277 | minpos[0] = PARO_MIN(minpos[0], ppos[0]); 278 | minpos[1] = PARO_MIN(minpos[1], ppos[1]); 279 | minpos[2] = PARO_MIN(minpos[2], ppos[2]); 280 | maxpos[0] = PARO_MAX(maxpos[0], ppos[0]); 281 | maxpos[1] = PARO_MAX(maxpos[1], ppos[1]); 282 | maxpos[2] = PARO_MAX(maxpos[2], ppos[2]); 283 | } 284 | 285 | FILE* file = fopen("octasphere.bin", "wb"); 286 | if (!file) { 287 | puts("unable to open bin file for writing"); 288 | exit(1); 289 | } 290 | fwrite(octasphere.positions, 12, num_vertices, file); 291 | fwrite(octasphere.normals, 12, num_vertices, file); 292 | fwrite(octasphere.texcoords, 8, num_vertices, file); 293 | fwrite(octasphere.indices, 2, num_indices, file); 294 | fclose(file); 295 | 296 | free(octasphere.positions); 297 | free(octasphere.normals); 298 | free(octasphere.texcoords); 299 | free(octasphere.indices); 300 | 301 | generate_gltf("octasphere.gltf", "octasphere.bin", num_vertices, num_indices, 302 | minpos, maxpos); 303 | } 304 | } 305 | 306 | return assert_failures(); 307 | } 308 | -------------------------------------------------------------------------------- /test/test_shapes.c: -------------------------------------------------------------------------------- 1 | #include "describe.h" 2 | 3 | #define PAR_SHAPES_IMPLEMENTATION 4 | #include "par_shapes.h" 5 | 6 | #include 7 | #include 8 | 9 | #define STRINGIFY(A) #A 10 | 11 | int main() 12 | { 13 | describe("cylinders and spheres") { 14 | it("should fail when the number of stacks or slices is invalid") { 15 | par_shapes_mesh* bad1 = par_shapes_create_cylinder(1, 1); 16 | par_shapes_mesh* bad2 = par_shapes_create_cylinder(1, 3); 17 | par_shapes_mesh* good = par_shapes_create_cylinder(3, 1); 18 | assert_null(bad1); 19 | assert_null(bad2); 20 | assert_ok(good); 21 | par_shapes_free_mesh(good); 22 | } 23 | it("should generate correct number of vertices") { 24 | par_shapes_mesh* m = par_shapes_create_cylinder(5, 6); 25 | assert_equal(m->npoints, 42); 26 | par_shapes_free_mesh(m); 27 | } 28 | it("should have expected number of triangles") { 29 | par_shapes_mesh* m; 30 | int slices, stacks ; 31 | 32 | slices = 5; stacks = 6; 33 | m = par_shapes_create_cylinder(slices, stacks); 34 | assert_equal(m->ntriangles, slices * stacks * 2); 35 | par_shapes_free_mesh(m); 36 | 37 | slices = 5; stacks = 6; 38 | m = par_shapes_create_parametric_sphere(slices, stacks); 39 | assert_equal(m->ntriangles, slices * 2 + (stacks - 2) * slices * 2); 40 | par_shapes_free_mesh(m); 41 | 42 | slices = 12; stacks = 13; 43 | m = par_shapes_create_parametric_sphere(slices, stacks); 44 | assert_equal(m->ntriangles, slices * 2 + (stacks - 2) * slices * 2); 45 | par_shapes_free_mesh(m); 46 | 47 | slices = 16; stacks = 16; 48 | m = par_shapes_create_parametric_sphere(slices, stacks); 49 | assert_equal(m->ntriangles, slices * 2 + (stacks - 2) * slices * 2); 50 | par_shapes_free_mesh(m); 51 | } 52 | } 53 | 54 | describe("par_shapes_create_plane") { 55 | it("should not have NaN's") { 56 | par_shapes_mesh* m = par_shapes_create_plane(5, 6); 57 | for (int i = 0; i < m->npoints * 3; i++) { 58 | assert_ok(m->points[i] == m->points[i]); 59 | assert_ok(m->normals[i] == m->normals[i]); 60 | } 61 | par_shapes_free_mesh(m); 62 | } 63 | } 64 | 65 | describe("par_shapes_export") { 66 | it("should generate an OBJ file") { 67 | par_shapes_mesh* m; 68 | 69 | m = par_shapes_create_torus(7, 10, 0.5); 70 | par_shapes_export(m, "build/test_shapes_torus.obj"); 71 | par_shapes_free_mesh(m); 72 | 73 | m = par_shapes_create_subdivided_sphere(2); 74 | par_shapes_export(m, "build/test_shapes_ssphere.obj"); 75 | par_shapes_free_mesh(m); 76 | 77 | m = par_shapes_create_klein_bottle(10, 20); 78 | par_shapes_export(m, "build/test_shapes_klein.obj"); 79 | par_shapes_free_mesh(m); 80 | 81 | m = par_shapes_create_trefoil_knot(20, 100, 0.5); 82 | par_shapes_export(m, "build/test_shapes_trefoil.obj"); 83 | par_shapes_free_mesh(m); 84 | 85 | m = par_shapes_create_hemisphere(5, 6); 86 | par_shapes_export(m, "build/test_shapes_hemisphere.obj"); 87 | par_shapes_free_mesh(m); 88 | 89 | m = par_shapes_create_icosahedron(); 90 | par_shapes_export(m, "build/test_shapes_icosahedron.obj"); 91 | par_shapes_free_mesh(m); 92 | 93 | m = par_shapes_create_dodecahedron(); 94 | par_shapes_export(m, "build/test_shapes_dodecahedron.obj"); 95 | par_shapes_free_mesh(m); 96 | 97 | m = par_shapes_create_octahedron(); 98 | par_shapes_export(m, "build/test_shapes_octahedron.obj"); 99 | par_shapes_free_mesh(m); 100 | 101 | m = par_shapes_create_tetrahedron(); 102 | par_shapes_export(m, "build/test_shapes_tetrahedron.obj"); 103 | par_shapes_free_mesh(m); 104 | 105 | m = par_shapes_create_cube(); 106 | par_shapes_export(m, "build/test_shapes_cube.obj"); 107 | par_shapes_free_mesh(m); 108 | 109 | m = par_shapes_create_rock(1, 3); 110 | par_shapes_export(m, "build/test_shapes_rock.obj"); 111 | par_shapes_free_mesh(m); 112 | 113 | m = par_shapes_create_cone(15, 3); 114 | par_shapes_export(m, "build/test_shapes_cone.obj"); 115 | par_shapes_free_mesh(m); 116 | 117 | m = par_shapes_create_parametric_disk(15, 3); 118 | par_shapes_export(m, "build/test_shapes_parametric_disk.obj"); 119 | par_shapes_free_mesh(m); 120 | 121 | float center[3] = {0, 0, 0}; 122 | float normal[3] = {0, 0, 1}; 123 | m = par_shapes_create_disk(1, 5, center, normal); 124 | par_shapes_export(m, "build/test_shapes_disk.obj"); 125 | par_shapes_free_mesh(m); 126 | 127 | } 128 | } 129 | 130 | describe("par_shapes_merge") { 131 | it("should concatenate two meshes") { 132 | par_shapes_mesh* a, *b; 133 | a = par_shapes_create_klein_bottle(10, 20); 134 | int npts = a->npoints; 135 | int ntris = a->ntriangles; 136 | b = par_shapes_create_plane(3, 3); 137 | par_shapes_merge(a, b); 138 | assert_equal(a->npoints, npts + b->npoints); 139 | assert_equal(a->ntriangles, ntris + b->ntriangles); 140 | par_shapes_free_mesh(a); 141 | par_shapes_free_mesh(b); 142 | } 143 | } 144 | 145 | describe("transforms") { 146 | it("should support translation") { 147 | par_shapes_mesh* a, *b; 148 | a = par_shapes_create_cylinder(20, 3); 149 | b = par_shapes_create_cylinder(4, 3); 150 | par_shapes_translate(a, 0.5, 0.5, 0.25); 151 | par_shapes_merge(a, b); 152 | par_shapes_free_mesh(a); 153 | par_shapes_free_mesh(b); 154 | } 155 | it("should support rotation") { 156 | par_shapes_mesh* a, *b; 157 | a = par_shapes_create_cylinder(20, 3); 158 | b = par_shapes_create_cylinder(4, 3); 159 | float axis1[3] = {0, 1, 0}; 160 | float axis2[3] = {0, 0, 1}; 161 | par_shapes_rotate(a, PAR_PI * 0.5, axis1); 162 | par_shapes_rotate(a, PAR_PI * 0.25, axis2); 163 | par_shapes_merge(a, b); 164 | par_shapes_free_mesh(a); 165 | par_shapes_free_mesh(b); 166 | } 167 | it("should support non-uniform scale") { 168 | par_shapes_mesh* a; 169 | a = par_shapes_create_cylinder(15, 3); 170 | par_shapes_scale(a, 1, 1, 5); 171 | par_shapes_free_mesh(a); 172 | } 173 | it("should support degenerate scale") { 174 | par_shapes_mesh* a; 175 | a = par_shapes_create_cone(15, 3); 176 | assert_ok(a); 177 | par_shapes_scale(a, 1, 1, 0); 178 | for (int i = 0; i < a->npoints * 3; i++) { 179 | // should not have nans 180 | assert_ok(a->points[i] == a->points[i]); 181 | assert_ok(a->normals[i] == a->normals[i]); 182 | // check components 183 | if (i % 3 != 2) { 184 | assert_ok(a->normals[i] == 0.0f); 185 | } else { 186 | assert_ok(a->normals[i] == 1.0f); 187 | assert_ok(a->points[i] == 0.0f); 188 | } 189 | } 190 | par_shapes_free_mesh(a); 191 | } 192 | } 193 | 194 | describe("misc shapes") { 195 | it("create an orientable disk in 3-space") { 196 | int slices = 32; 197 | float aradius = 1; 198 | float anormal[3] = {0, 0, 1}; 199 | float acenter[3] = {0, 0, 0}; 200 | par_shapes_mesh* a, *b; 201 | a = par_shapes_create_disk(aradius, slices, acenter, anormal); 202 | float bradius = 0.2; 203 | float bcenter[3] = {0, 0, 0.2}; 204 | float bnormal[3] = {0, 1, 0}; 205 | b = par_shapes_create_disk(bradius, slices, bcenter, bnormal); 206 | par_shapes_merge(a, b); 207 | par_shapes_free_mesh(a); 208 | par_shapes_free_mesh(b); 209 | } 210 | it("create a rock on the Y plane") { 211 | int slices = 32; 212 | float radius = 2; 213 | float normal[3] = {0, 1, 0}; 214 | float center[3] = {0, 0, 0}; 215 | par_shapes_mesh* a, *b; 216 | a = par_shapes_create_disk(radius, slices, center, normal); 217 | b = par_shapes_create_rock(1, 2); 218 | float aabb[6]; 219 | par_shapes_compute_aabb(b, aabb); 220 | par_shapes_translate(b, 0, -aabb[1] / 2, 0); 221 | par_shapes_merge(a, b); 222 | par_shapes_free_mesh(a); 223 | par_shapes_free_mesh(b); 224 | } 225 | it("create a polyhedron on the Y plane") { 226 | int slices = 32; 227 | float radius = 2; 228 | float normal[3] = {0, 1, 0}; 229 | float center[3] = {0, 0, 0}; 230 | par_shapes_mesh* a, *b; 231 | a = par_shapes_create_disk(radius, slices, center, normal); 232 | b = par_shapes_create_dodecahedron(); 233 | par_shapes_translate(b, 0, 0.934, 0); 234 | par_shapes_merge(a, b); 235 | par_shapes_free_mesh(a); 236 | par_shapes_free_mesh(b); 237 | } 238 | it("create a rounded cylinder via composition") { 239 | const float O[3] = {0, 0, 0}; 240 | const float I[3] = {1, 0, 0}; 241 | const float J[3] = {0, 1, 0}; 242 | const float K[3] = {0, 0, 1}; 243 | const float top_center[3] = {0, 1.2, 0}; 244 | const int tess = 30; 245 | par_shapes_mesh *a, *b, *c, *d; 246 | a = par_shapes_create_disk(2.5, tess, O, J); 247 | b = par_shapes_create_cylinder(tess, 3); 248 | c = par_shapes_create_torus(15, tess, 0.1); 249 | d = par_shapes_create_disk(1, tess, top_center, J); 250 | par_shapes_rotate(c, PAR_PI / tess, K); 251 | par_shapes_translate(c, 0, 0, 1); 252 | par_shapes_scale(b, 1.2, 1.2, 1); 253 | par_shapes_merge(b, c); 254 | par_shapes_rotate(b, -PAR_PI * 0.5, I); 255 | par_shapes_merge(b, d); 256 | par_shapes_merge(b, a); 257 | par_shapes_scale(b, 1, 2, 1); 258 | par_shapes_free_mesh(a); 259 | par_shapes_free_mesh(b); 260 | par_shapes_free_mesh(c); 261 | par_shapes_free_mesh(d); 262 | } 263 | } 264 | 265 | describe("lsystems") { 266 | it("export a tree-like shape") { 267 | char const* program = STRINGIFY( 268 | sx 2 sy 2 269 | ry 90 rx 90 270 | shape tube rx 15 call rlimb rx -15 271 | shape tube rx -15 call llimb rx 15 272 | shape tube ry 15 call rlimb ry -15 273 | shape tube ry 15 call llimb ry -15 274 | rule rlimb 275 | sx 0.925 sy 0.925 tz 1 rx 1.2 276 | call rlimb2 277 | rule rlimb2.1 278 | shape connect 279 | call rlimb 280 | rule rlimb2.1 281 | rx 15 shape tube call rlimb rx -15 282 | rx -15 shape tube call llimb rx 15 283 | rule rlimb.1 284 | call llimb 285 | rule llimb.1 286 | call rlimb 287 | rule llimb.10 288 | sx 0.925 sy 0.925 289 | tz 1 290 | rx -1.2 291 | shape connect 292 | call llimb 293 | ); 294 | const float O[3] = {0, 0, 0}; 295 | const float J[3] = {0, 1, 0}; 296 | par_shapes_mesh* mesh = par_shapes_create_lsystem(program, 5, 60); 297 | par_shapes_mesh* disk = par_shapes_create_disk(10, 30, O, J); 298 | par_shapes_merge(mesh, disk); 299 | par_shapes_free_mesh(disk); 300 | par_shapes_export(mesh, "build/lsystem.obj"); 301 | par_shapes_free_mesh(mesh); 302 | } 303 | } 304 | 305 | return assert_failures(); 306 | } 307 | -------------------------------------------------------------------------------- /test/test_sprune.c: -------------------------------------------------------------------------------- 1 | #define PAR_SPRUNE_IMPLEMENTATION 2 | #include "par_sprune.h" 3 | #include "describe.h" 4 | 5 | static par_sprune_context* context; 6 | 7 | static float boxes20[20 * 4] = { 8 | 0.41, 0.45, 0.57, 0.57, 9 | 0.7, 0.049, 0.74, 0.073, 10 | 0.3, 0.55, 0.46, 0.61, 11 | 0.31, 0.19, 0.62, 0.38, 12 | 0.3, 0.1, 0.34, 0.15, 13 | 0.23, 0.34, 0.23, 0.34, 14 | 0.58, 0.22, 0.82, 0.61, // 6 15 | 0.18, 0.71, 0.27, 0.72, 16 | 0.69, 0.34, 0.81, 0.55, 17 | 0.53, 0.1, 0.56, 0.17, 18 | 0.33, 0.69, 0.39, 0.69, 19 | 0.64, 0.46, 0.65, 0.46, 20 | 0.35, 0.78, 0.4, 0.8, 21 | 0.69, 0.63, 0.79, 0.68, 22 | 0.7, 0.51, 0.77, 0.53, 23 | 0.17, 0.79, 0.79, 0.81, 24 | 0.66, 0.49, 0.67, 0.5, // 16 25 | 0.36, 0.49, 0.41, 0.5, 26 | 0.58, 0.53, 0.58, 0.54, 27 | 0.25, 0.81, 0.51, 0.85, 28 | }; 29 | 30 | static float boxes10[10 * 4] = { 31 | 0.55, 0.38, 0.55, 0.38, 32 | 0.5, 0.36, 0.51, 0.37, 33 | 0.28, 0.2, 0.4, 0.24, 34 | 0.39, 0.19, 0.4, 0.22, 35 | 0.31, 0.29, 0.7, 0.43, 36 | 0.019, 0.4, 0.02, 0.46, 37 | 0.58, 0.44, 0.64, 0.75, 38 | 0.39, 0.47, 0.48, 0.5, 39 | 0.15, 0.91, 0.21, 0.93, 40 | 0.62, 0.58, 0.63, 0.58, 41 | }; 42 | 43 | static void export_svg(const char* filename, const char* color, float* boxes, 44 | int nboxes) 45 | { 46 | FILE* svgfile = fopen(filename, "wt"); 47 | fprintf(svgfile, 48 | "\n"); 51 | fprintf(svgfile, 52 | "\n", color); 54 | fprintf(svgfile, 55 | "\n"); 57 | for (int i = 0; i < nboxes; i++) { 58 | float minx = boxes[i * 4 + 0]; 59 | float miny = boxes[i * 4 + 1]; 60 | float maxx = boxes[i * 4 + 2]; 61 | float maxy = boxes[i * 4 + 3]; 62 | float w = maxx - minx; 63 | float h = maxy - miny; 64 | fprintf(svgfile, 65 | "\n", minx, miny, w, h); 66 | fprintf(svgfile, "%d\n", 68 | 0.5 * (minx + maxx), 0.5 * (miny + maxy), 0.025, i); 69 | 70 | } 71 | fputs("\n", svgfile); 72 | fclose(svgfile); 73 | } 74 | 75 | int main() 76 | { 77 | describe("par_sprune_overlap") { 78 | 79 | it("should have a usage example") { 80 | float boxes[] = { 81 | 0.1, 0.1, 0.3, 0.3, // box0 82 | 0.2, 0.2, 0.4, 0.4, // box1 83 | 0.6, 0.15, 0.7, 0.25, // box2 84 | }; 85 | int nboxes = sizeof(boxes) / sizeof(float) / 4; 86 | par_sprune_context* c = par_sprune_overlap(boxes, nboxes, 0); 87 | int const* pairs = c->collision_pairs; 88 | for (int i = 0; i < c->ncollision_pairs * 2; i += 2) { 89 | printf("box%d collides with box%d\n", pairs[i], pairs[i + 1]); 90 | } 91 | boxes[8] -= 0.45; boxes[10] -= 0.45; // Move box2 leftward. 92 | bool changed = par_sprune_update(c); 93 | if (changed) { 94 | printf("There are now %d collisions.\n", c->ncollision_pairs); 95 | } 96 | export_svg("build/test_sprune_usage.svg", "#2AB68B", boxes, nboxes); 97 | par_sprune_free_context(c); 98 | } 99 | 100 | it("should pass a simple smoke test") { 101 | context = par_sprune_overlap(boxes20, 20, 0); 102 | assert_ok(context); 103 | export_svg("build/test_sprune_overlap_20.svg", "#2AB68B", 104 | boxes20, 20); 105 | par_sprune_overlap(boxes10, 10, context); 106 | export_svg("build/test_sprune_overlap_10.svg", "#8B2AB6", 107 | boxes10, 10); 108 | assert_equal(context->ncollision_pairs, 4); 109 | 110 | boxes10[5] -= 0.1; 111 | boxes10[7] -= 0.1; 112 | export_svg("build/test_sprune_overlap_10b.svg", "#8B2AB6", 113 | boxes10, 10); 114 | par_sprune_overlap(boxes10, 10, context); 115 | assert_equal(context->ncollision_pairs, 3); 116 | int const* pairs = context->collision_pairs; 117 | assert_ok(pairs[0] == 0 && pairs[1] == 4); 118 | assert_ok(pairs[2] == 2 && pairs[3] == 3); 119 | assert_ok(pairs[4] == 6 && pairs[5] == 9); 120 | 121 | boxes10[5] += 0.1; 122 | boxes10[7] += 0.1; 123 | bool changed = par_sprune_update(context); 124 | assert_equal(context->ncollision_pairs, 4); 125 | assert_equal(changed, true); 126 | pairs = context->collision_pairs; 127 | assert_ok(pairs[0] == 0 && pairs[1] == 4); 128 | assert_ok(pairs[2] == 1 && pairs[3] == 4); 129 | assert_ok(pairs[4] == 2 && pairs[5] == 3); 130 | assert_ok(pairs[6] == 6 && pairs[7] == 9); 131 | 132 | boxes10[5] -= 0.1; 133 | boxes10[7] -= 0.1; 134 | changed = par_sprune_update(context); 135 | assert_equal(context->ncollision_pairs, 3); 136 | assert_equal(changed, true); 137 | pairs = context->collision_pairs; 138 | assert_ok(pairs[0] == 0 && pairs[1] == 4); 139 | assert_ok(pairs[2] == 2 && pairs[3] == 3); 140 | assert_ok(pairs[4] == 6 && pairs[5] == 9); 141 | 142 | changed = par_sprune_update(context); 143 | assert_equal(context->ncollision_pairs, 3); 144 | assert_equal(changed, false); 145 | 146 | par_sprune_cull(context); 147 | assert_equal(context->nculled, 3); 148 | 149 | par_sprune_free_context(context); 150 | } 151 | } 152 | 153 | return assert_failures(); 154 | } 155 | -------------------------------------------------------------------------------- /test/test_strings.cpp: -------------------------------------------------------------------------------- 1 | #define PAR_STRING_BLOCKS_IMPLEMENTATION 2 | #include "par_string_blocks.h" 3 | 4 | extern "C" { 5 | #include "describe.h" 6 | } 7 | 8 | const char test_string[] = R"( 9 | --- my_shader 10 | void main() { ... } 11 | 12 | --- common 13 | uniform vec4 resolution; 14 | uniform vec4 color; 15 | --- spooky 16 | -- hello 17 | !! world 18 | )"; 19 | 20 | FILE* global_file; 21 | 22 | int main() 23 | { 24 | parsb_context* blocks; 25 | 26 | describe("simple") { 27 | 28 | it("can create a context") { 29 | blocks = parsb_create_context((parsb_options){}); 30 | assert_equal((int) sizeof(test_string), (int) strlen(test_string) + 1); 31 | parsb_add_blocks(blocks, test_string, strlen(test_string)); 32 | } 33 | 34 | it("can extract single blocks") { 35 | assert_str_equal(parsb_get_blocks(blocks, "my_shader"), 36 | "void main() { ... }\n\n"); 37 | assert_str_equal(parsb_get_blocks(blocks, "common"), 38 | "uniform vec4 resolution;\nuniform vec4 color;\n"); 39 | assert_str_equal(parsb_get_blocks(blocks, "spooky"), 40 | "-- hello\n!! world\n "); 41 | } 42 | 43 | it("can glue blocks") { 44 | assert_str_equal(parsb_get_blocks(blocks, "spooky common"), 45 | "-- hello\n!! world\n uniform vec4 resolution;\nuniform vec4 color;\n"); 46 | } 47 | 48 | it("can add a named block") { 49 | parsb_add_block(blocks, "prefix", "13"); 50 | assert_str_equal(parsb_get_blocks(blocks, "prefix spooky common"), 51 | "13-- hello\n!! world\n uniform vec4 resolution;\nuniform vec4 color;\n"); 52 | } 53 | 54 | it("can replace a named block") { 55 | parsb_add_block(blocks, "prefix", "11"); 56 | assert_str_equal(parsb_get_blocks(blocks, "prefix spooky common"), 57 | "11-- hello\n!! world\n uniform vec4 resolution;\nuniform vec4 color;\n"); 58 | } 59 | 60 | it("add and replace multiple blocks") { 61 | const char newblocks[] = R"( 62 | --- prefix 63 | 12 64 | --- great 65 | goodbye)"; 66 | parsb_add_blocks(blocks, newblocks, strlen(newblocks)); 67 | assert_str_equal(parsb_get_blocks(blocks, "prefix spooky"), 68 | "12\n-- hello\n!! world\n "); 69 | assert_str_equal(parsb_get_blocks(blocks, "great prefix"), "goodbye12\n"); 70 | } 71 | 72 | it("can export the database") { 73 | global_file = fopen("test0.glsl", "w"); 74 | parsb_write_blocks(blocks, [](const char* line, void* user) { 75 | fprintf(global_file, "%s\n", line); 76 | }, nullptr); 77 | fclose(global_file); 78 | parsb_destroy_context(blocks); 79 | } 80 | 81 | it("can import the database") { 82 | blocks = parsb_create_context((parsb_options){}); 83 | assert_equal((int) sizeof(test_string), (int) strlen(test_string) + 1); 84 | parsb_add_blocks_from_file(blocks, "test0.glsl"); 85 | } 86 | 87 | it("can re-export the database") { 88 | global_file = fopen("test1.glsl", "w"); 89 | parsb_write_blocks(blocks, [](const char* line, void* user) { 90 | fprintf(global_file, "%s\n", line); 91 | }, nullptr); 92 | fclose(global_file); 93 | parsb_destroy_context(blocks); 94 | } 95 | } 96 | 97 | return assert_failures(); 98 | } 99 | -------------------------------------------------------------------------------- /test/tverts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prideout/par/24f26c12926b746db5814de759163144ad79843a/test/tverts.png -------------------------------------------------------------------------------- /test/whereami.c: -------------------------------------------------------------------------------- 1 | // (‑●‑●)> released under the WTFPL v2 license, by Gregory Pakosz (@gpakosz) 2 | 3 | // in case you want to #include "whereami.c" in a larger compilation unit 4 | #if !defined(WHEREAMI_H) 5 | #include "whereami.h" 6 | #endif 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | #if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC) 13 | #include 14 | #endif 15 | 16 | #if !defined(WAI_MALLOC) 17 | #define WAI_MALLOC(size) malloc(size) 18 | #endif 19 | 20 | #if !defined(WAI_FREE) 21 | #define WAI_FREE(p) free(p) 22 | #endif 23 | 24 | #if !defined(WAI_REALLOC) 25 | #define WAI_REALLOC(p, size) realloc(p, size) 26 | #endif 27 | 28 | #if defined(_WIN32) 29 | 30 | #define WIN32_LEAN_AND_MEAN 31 | #if defined(_MSC_VER) 32 | #pragma warning(push, 3) 33 | #endif 34 | #include 35 | #include 36 | #if defined(_MSC_VER) 37 | #pragma warning(pop) 38 | #endif 39 | 40 | #ifndef WAI_NOINLINE 41 | #ifdef _MSC_VER 42 | #define WAI_NOINLINE __declspec(noinline) 43 | #endif 44 | #endif 45 | 46 | static int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length) 47 | { 48 | wchar_t buffer1[MAX_PATH]; 49 | wchar_t buffer2[MAX_PATH]; 50 | wchar_t* path = NULL; 51 | int length = -1; 52 | 53 | for (;;) 54 | { 55 | DWORD size; 56 | int length_; 57 | 58 | size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0])); 59 | 60 | if (size == 0) 61 | break; 62 | else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0]))) 63 | { 64 | DWORD size_ = size; 65 | do 66 | { 67 | size_ *= 2; 68 | path = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_); 69 | size = GetModuleFileNameW(NULL, path, size_); 70 | } while (size == size_); 71 | } 72 | else 73 | path = buffer1; 74 | 75 | _wfullpath(buffer2, path, MAX_PATH); 76 | length_ = WideCharToMultiByte(CP_UTF8, 0, buffer2, -1, out, capacity, NULL, NULL); 77 | 78 | if (length_ == 0) 79 | length_ = WideCharToMultiByte(CP_UTF8, 0, buffer2, -1, NULL, 0, NULL, NULL); 80 | if (length_ == 0) 81 | break; 82 | 83 | if (length_ <= capacity && dirname_length) 84 | { 85 | int i; 86 | 87 | for (i = length_ - 1; i >= 0; --i) 88 | { 89 | if (out[i] == '\\') 90 | { 91 | *dirname_length = i; 92 | break; 93 | } 94 | } 95 | } 96 | 97 | length = length_; 98 | 99 | break; 100 | } 101 | 102 | if (path != buffer1) 103 | WAI_FREE(path); 104 | 105 | return length; 106 | } 107 | 108 | WAI_NOINLINE 109 | WAI_FUNCSPEC 110 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) 111 | { 112 | return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length); 113 | } 114 | 115 | WAI_NOINLINE 116 | WAI_FUNCSPEC 117 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) 118 | { 119 | HMODULE module; 120 | int length = -1; 121 | 122 | #if defined(_MSC_VER) 123 | #pragma warning(push) 124 | #pragma warning(disable: 4054) 125 | #endif 126 | if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)_ReturnAddress(), &module)) 127 | #if defined(_MSC_VER) 128 | #pragma warning(pop) 129 | #endif 130 | { 131 | length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length); 132 | } 133 | 134 | return length; 135 | } 136 | 137 | #elif defined(__linux__) 138 | 139 | #include 140 | #include 141 | #include 142 | #include 143 | #ifndef __STDC_FORMAT_MACROS 144 | #define __STDC_FORMAT_MACROS 145 | #endif 146 | #include 147 | 148 | char *realpath(const char * pathname, char * resolved_path); 149 | 150 | #ifndef PATH_MAX 151 | #define PATH_MAX 4096 152 | #endif 153 | 154 | #if !defined(WAI_PROC_SELF_EXE) 155 | #define WAI_PROC_SELF_EXE "/proc/self/exe" 156 | #endif 157 | 158 | #ifndef WAI_NOINLINE 159 | #ifdef __GNUC__ 160 | #define WAI_NOINLINE __attribute__((noinline)) 161 | #endif 162 | #endif 163 | 164 | WAI_FUNCSPEC 165 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) 166 | { 167 | char buffer[PATH_MAX]; 168 | char* resolved = NULL; 169 | int length = -1; 170 | 171 | for (;;) 172 | { 173 | resolved = realpath(WAI_PROC_SELF_EXE, buffer); 174 | if (!resolved) 175 | break; 176 | 177 | length = (int)strlen(resolved); 178 | if (length <= capacity) 179 | { 180 | memcpy(out, resolved, length); 181 | 182 | if (dirname_length) 183 | { 184 | int i; 185 | 186 | for (i = length - 1; i >= 0; --i) 187 | { 188 | if (out[i] == '/') 189 | { 190 | *dirname_length = i; 191 | break; 192 | } 193 | } 194 | } 195 | } 196 | 197 | break; 198 | } 199 | 200 | return length; 201 | } 202 | 203 | #if !defined(WAI_PROC_SELF_MAPS_RETRY) 204 | #define WAI_PROC_SELF_MAPS_RETRY 5 205 | #endif 206 | 207 | #if !defined(WAI_PROC_SELF_MAPS) 208 | #define WAI_PROC_SELF_MAPS "/proc/self/maps" 209 | #endif 210 | 211 | WAI_NOINLINE 212 | WAI_FUNCSPEC 213 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) 214 | { 215 | int length = -1; 216 | FILE* maps = NULL; 217 | int i; 218 | 219 | for (i = 0; i < WAI_PROC_SELF_MAPS_RETRY; ++i) 220 | { 221 | maps = fopen(WAI_PROC_SELF_MAPS, "r"); 222 | if (!maps) 223 | break; 224 | 225 | for (;;) 226 | { 227 | char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX]; 228 | uint64_t low, high; 229 | char perms[5]; 230 | uint64_t offset; 231 | uint32_t major, minor; 232 | char path[PATH_MAX]; 233 | uint32_t inode; 234 | 235 | if (!fgets(buffer, sizeof(buffer), maps)) 236 | break; 237 | 238 | if (sscanf(buffer, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %u %s\n", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8) 239 | { 240 | uint64_t addr = (uint64_t)(uintptr_t) __builtin_extract_return_addr(__builtin_return_address(0)); 241 | if (low <= addr && addr <= high) 242 | { 243 | char* resolved; 244 | 245 | resolved = realpath(path, buffer); 246 | if (!resolved) 247 | break; 248 | 249 | length = (int)strlen(resolved); 250 | if (length <= capacity) 251 | { 252 | memcpy(out, resolved, length); 253 | 254 | if (dirname_length) 255 | { 256 | int i; 257 | 258 | for (i = length - 1; i >= 0; --i) 259 | { 260 | if (out[i] == '/') 261 | { 262 | *dirname_length = i; 263 | break; 264 | } 265 | } 266 | } 267 | } 268 | 269 | break; 270 | } 271 | } 272 | } 273 | 274 | fclose(maps); 275 | 276 | if (length != -1) 277 | break; 278 | } 279 | 280 | return length; 281 | } 282 | 283 | #elif defined(__APPLE__) 284 | 285 | #define _DARWIN_BETTER_REALPATH 286 | #include 287 | #include 288 | #include 289 | #include 290 | #include 291 | 292 | #ifndef WAI_NOINLINE 293 | #ifdef __GNUC__ 294 | #define WAI_NOINLINE __attribute__((noinline)) 295 | #endif 296 | #endif 297 | 298 | WAI_FUNCSPEC 299 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) 300 | { 301 | char buffer1[PATH_MAX]; 302 | char buffer2[PATH_MAX]; 303 | char* path = buffer1; 304 | char* resolved = NULL; 305 | int length = -1; 306 | 307 | for (;;) 308 | { 309 | uint32_t size = (uint32_t)sizeof(buffer1); 310 | if (_NSGetExecutablePath(path, &size) == -1) 311 | { 312 | path = (char*)WAI_MALLOC(size); 313 | if (!_NSGetExecutablePath(path, &size)) 314 | break; 315 | } 316 | 317 | resolved = realpath(path, buffer2); 318 | if (!resolved) 319 | break; 320 | 321 | length = (int)strlen(resolved); 322 | if (length <= capacity) 323 | { 324 | memcpy(out, resolved, length); 325 | 326 | if (dirname_length) 327 | { 328 | int i; 329 | 330 | for (i = length - 1; i >= 0; --i) 331 | { 332 | if (out[i] == '/') 333 | { 334 | *dirname_length = i; 335 | break; 336 | } 337 | } 338 | } 339 | } 340 | 341 | break; 342 | } 343 | 344 | if (path != buffer1) 345 | WAI_FREE(path); 346 | 347 | return length; 348 | } 349 | 350 | WAI_NOINLINE 351 | WAI_FUNCSPEC 352 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) 353 | { 354 | char buffer[PATH_MAX]; 355 | char* resolved = NULL; 356 | int length = -1; 357 | 358 | for(;;) 359 | { 360 | Dl_info info; 361 | 362 | if (dladdr(__builtin_extract_return_addr(__builtin_return_address(0)), &info)) 363 | { 364 | resolved = realpath(info.dli_fname, buffer); 365 | if (!resolved) 366 | break; 367 | 368 | length = (int)strlen(resolved); 369 | if (length <= capacity) 370 | { 371 | memcpy(out, resolved, length); 372 | 373 | if (dirname_length) 374 | { 375 | int i; 376 | 377 | for (i = length - 1; i >= 0; --i) 378 | { 379 | if (out[i] == '/') 380 | { 381 | *dirname_length = i; 382 | break; 383 | } 384 | } 385 | } 386 | } 387 | } 388 | 389 | break; 390 | } 391 | 392 | return length; 393 | } 394 | 395 | #elif defined(__QNXNTO__) 396 | 397 | #include 398 | #include 399 | #include 400 | #include 401 | #include 402 | 403 | #ifndef WAI_NOINLINE 404 | #ifdef __GNUC__ 405 | #define WAI_NOINLINE __attribute__((noinline)) 406 | #endif 407 | #endif 408 | 409 | #if !defined(WAI_PROC_SELF_EXE) 410 | #define WAI_PROC_SELF_EXE "/proc/self/exefile" 411 | #endif 412 | 413 | WAI_FUNCSPEC 414 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) 415 | { 416 | char buffer1[PATH_MAX]; 417 | char buffer2[PATH_MAX]; 418 | char* resolved = NULL; 419 | FILE* self_exe = NULL; 420 | int length = -1; 421 | 422 | for (;;) 423 | { 424 | self_exe = fopen(WAI_PROC_SELF_EXE, "r"); 425 | if (!self_exe) 426 | break; 427 | 428 | if (!fgets(buffer1, sizeof(buffer1), self_exe)) 429 | break; 430 | 431 | resolved = realpath(buffer1, buffer2); 432 | if (!resolved) 433 | break; 434 | 435 | length = (int)strlen(resolved); 436 | if (length <= capacity) 437 | { 438 | memcpy(out, resolved, length); 439 | 440 | if (dirname_length) 441 | { 442 | int i; 443 | 444 | for (i = length - 1; i >= 0; --i) 445 | { 446 | if (out[i] == '/') 447 | { 448 | *dirname_length = i; 449 | break; 450 | } 451 | } 452 | } 453 | } 454 | 455 | break; 456 | } 457 | 458 | fclose(self_exe); 459 | 460 | return length; 461 | } 462 | 463 | WAI_FUNCSPEC 464 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) 465 | { 466 | char buffer[PATH_MAX]; 467 | char* resolved = NULL; 468 | int length = -1; 469 | 470 | for(;;) 471 | { 472 | Dl_info info; 473 | 474 | if (dladdr(__builtin_extract_return_addr(__builtin_return_address(0)), &info)) 475 | { 476 | resolved = realpath(info.dli_fname, buffer); 477 | if (!resolved) 478 | break; 479 | 480 | length = (int)strlen(resolved); 481 | if (length <= capacity) 482 | { 483 | memcpy(out, resolved, length); 484 | 485 | if (dirname_length) 486 | { 487 | int i; 488 | 489 | for (i = length - 1; i >= 0; --i) 490 | { 491 | if (out[i] == '/') 492 | { 493 | *dirname_length = i; 494 | break; 495 | } 496 | } 497 | } 498 | } 499 | } 500 | 501 | break; 502 | } 503 | 504 | return length; 505 | } 506 | 507 | #endif 508 | 509 | #ifdef __cplusplus 510 | } 511 | #endif 512 | -------------------------------------------------------------------------------- /test/whereami.h: -------------------------------------------------------------------------------- 1 | // (‑●‑●)> released under the WTFPL v2 license, by Gregory Pakosz (@gpakosz) 2 | 3 | #ifndef WHEREAMI_H 4 | #define WHEREAMI_H 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #ifndef WAI_FUNCSPEC 11 | #define WAI_FUNCSPEC 12 | #endif 13 | #ifndef WAI_PREFIX 14 | #define WAI_PREFIX(function) wai_##function 15 | #endif 16 | 17 | /** 18 | * Returns the path to the current executable. 19 | * 20 | * Usage: 21 | * - first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to 22 | * retrieve the length of the path 23 | * - allocate the destination buffer with `path = (char*)malloc(length + 1);` 24 | * - call `wai_getExecutablePath(path, length, NULL)` again to retrieve the 25 | * path 26 | * - add a terminal NUL character with `path[length] = '\0';` 27 | * 28 | * @param out destination buffer, optional 29 | * @param capacity destination buffer capacity 30 | * @param dirname_length optional recipient for the length of the dirname part 31 | * of the path. 32 | * 33 | * @return the length of the executable path on success (without a terminal NUL 34 | * character), otherwise `-1` 35 | */ 36 | WAI_FUNCSPEC 37 | int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length); 38 | 39 | /** 40 | * Returns the path to the current module 41 | * 42 | * Usage: 43 | * - first call `int length = wai_getModulePath(NULL, 0, NULL);` to retrieve 44 | * the length of the path 45 | * - allocate the destination buffer with `path = (char*)malloc(length + 1);` 46 | * - call `wai_getModulePath(path, length, NULL)` again to retrieve the path 47 | * - add a terminal NUL character with `path[length] = '\0';` 48 | * 49 | * @param out destination buffer, optional 50 | * @param capacity destination buffer capacity 51 | * @param dirname_length optional recipient for the length of the dirname part 52 | * of the path. 53 | * 54 | * @return the length of the module path on success (without a terminal NUL 55 | * character), otherwise `-1` 56 | */ 57 | WAI_FUNCSPEC 58 | int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length); 59 | 60 | #ifdef __cplusplus 61 | } 62 | #endif 63 | 64 | #endif // #ifndef WHEREAMI_H 65 | -------------------------------------------------------------------------------- /tools/.uncrustify: -------------------------------------------------------------------------------- 1 | indent_columns = 4 2 | indent_with_tabs = 0 3 | indent_continue = 4 4 | indent_func_call_param = true 5 | indent_func_def_param = true 6 | indent_func_proto_param = true 7 | 8 | nl_func_leave_one_liners = true 9 | nl_collapse_empty_body = true 10 | nl_after_return = False 11 | nl_max = 2 12 | nl_after_func_proto = 0 13 | nl_after_func_proto_group = 1 14 | nl_after_func_body = 2 15 | nl_after_func_body_one_liner = 2 16 | nl_before_block_comment = 1 17 | nl_before_cpp_comment = 1 18 | nl_after_multiline_comment = False 19 | nl_after_struct = 1 20 | nl_end_of_file = add 21 | nl_end_of_file_min = 1 22 | -------------------------------------------------------------------------------- /tools/aliases.sh: -------------------------------------------------------------------------------- 1 | alias init="cmake test -Bbuild" 2 | alias initdbg="cmake -DCMAKE_BUILD_TYPE=Debug test -Bbuild" 3 | alias build="cmake --build build" 4 | alias clean="rm -rf build" 5 | alias verbose_build="cmake --build build -- VERBOSE=1" 6 | alias run="build/test_shapes" 7 | alias debug="lldb -o run build/test_bubbles" 8 | alias format="tools/format.py" 9 | -------------------------------------------------------------------------------- /tools/format.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | Code formatting tool. Runs uncrustify for constraints that get 5 | enforced automatically, then checks for remaining violations. 6 | """ 7 | 8 | import argparse 9 | import os 10 | import sys 11 | import glob 12 | 13 | SCRIPTDIR = os.path.normpath(os.path.dirname(__file__)) 14 | parser = argparse.ArgumentParser(description=__doc__.strip()) 15 | parser.add_argument('--check', dest='check', action='store_true', 16 | help='Check the code but do not mutate it') 17 | 18 | 19 | class bcolors: 20 | HEADER = '\033[95m' 21 | OKBLUE = '\033[94m' 22 | OKGREEN = '\033[92m' 23 | WARNING = '\033[93m' 24 | FAIL = '\033[91m' 25 | ENDC = '\033[0m' 26 | BOLD = '\033[1m' 27 | UNDERLINE = '\033[4m' 28 | 29 | 30 | def check_format(filename): 31 | infile = open(filename) 32 | lineno = 1 33 | 34 | def fail(msg): 35 | print(bcolors.FAIL + msg.format(filename, lineno) + bcolors.ENDC) 36 | 37 | bad = False 38 | previous = '' 39 | previous_is_blank = False 40 | for line in infile: 41 | line = line.rstrip('\n') 42 | if len(line) > 100: 43 | fail('{}:{} Line is over 100 chars.') 44 | bad = True 45 | is_blank = len(line) == 0 46 | if previous_is_blank and line.lstrip(' ') == '}': 47 | fail('{}:{} Extra newline before ending brace.') 48 | bad = True 49 | previous_is_blank = is_blank 50 | previous = line 51 | lineno = lineno + 1 52 | 53 | return not bad 54 | 55 | if __name__ == '__main__': 56 | args = parser.parse_args() 57 | cmd = 'uncrustify -l C -c {}/.uncrustify *.h '.format(SCRIPTDIR) 58 | if os.path.exists('uncrustify'): 59 | cmd = './' + cmd 60 | if args.check: 61 | cmd += '--check' 62 | else: 63 | cmd += '--no-backup' 64 | if os.system(cmd) and args.check: 65 | sys.exit(1) 66 | good = True 67 | for filename in glob.glob('*.h'): 68 | good = check_format(filename) and good 69 | if not good: 70 | print('Illegal formatting detected.') 71 | sys.exit(1) 72 | --------------------------------------------------------------------------------