solstice_args.c (18109B)
1 /* Copyright (C) 2018-2026 |Meso|Star> (contact@meso-star.com) 2 * Copyright (C) 2016-2018 CNRS 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. */ 16 17 #define _POSIX_C_SOURCE 2 18 19 #include "solstice_args.h" 20 #include "solstice_version.h" 21 22 #include <rsys/cstr.h> 23 #include <rsys/double3.h> 24 #include <rsys/stretchy_array.h> 25 26 #ifdef COMPILER_CL 27 #include <getopt.h> 28 #define strtok_r strtok_s 29 #else 30 #include <unistd.h> 31 #endif 32 33 #include <string.h> 34 35 /******************************************************************************* 36 * Helper functions 37 ******************************************************************************/ 38 static void 39 print_help(const char* program) 40 { 41 printf( 42 "Usage: %s [OPTIONS] [FILE]\n" 43 "Integrate the solar flux in a complex solar facility described in FILE. If not\n" 44 "define, the solar facility is read from standard input. Refer to solstice(1)\n" 45 "man page for more informations.\n\n", 46 program); 47 printf( 48 " -D <dirs> list of sun directions.\n"); 49 printf( 50 " -f overwrite the output files if they already exist, i.e. the\n" 51 " OUTPUT file and the output RNG state.\n"); 52 printf( 53 " -G <rng> save and restore the state of the random number generator.\n"); 54 printf( 55 " -g <dump> switch in dump geometry mode and configure it.\n"); 56 printf( 57 " -h display this help and exit.\n"); 58 printf( 59 " -n SAMPLES number of Monte Carlo samples. Default is %lu.\n", 60 SOLSTICE_ARGS_DEFAULT.nexperiments); 61 printf( 62 " -o OUTPUT write results to OUTPUT. If not defined, write results to\n" 63 " standard output.\n"); 64 printf( 65 " -p <dump-paths> switch in dump radiative paths mode and configure it.\n"); 66 printf( 67 " -q do not print the helper message when no FILE is submitted.\n"); 68 printf( 69 " -R RECEIVERS define the file from which the list of receivers are read.\n"); 70 printf( 71 " -r <rendering> switch in rendering mode and configure it.\n"); 72 printf( 73 " -t THREADS hint on the number of threads to use. By default use as\n" 74 " many threads as CPU cores.\n"); 75 printf( 76 " -v make the program more verbose.\n"); 77 printf( 78 " --version display version information and exit.\n"); 79 printf("\n"); 80 printf( 81 "Copyright (C) 2018-2026 |Meso|Star> (contact@meso-star.com).\n" 82 "Copyright (C) 2016-2018 CNRS.\n" 83 "Solstice is a free software released under the GNU GPL license, version 3 or\n" 84 "later. You are free to change or redistribute it under certain conditions\n" 85 "<http://gnu.org/licenses/gpl.html>.\n"); 86 } 87 88 static res_T 89 parse_fov(const char* str, double* out_fov) 90 { 91 double fov; 92 res_T res = RES_OK; 93 ASSERT(str && out_fov); 94 95 res = cstr_to_double(str, &fov); 96 if(res != RES_OK) { 97 fprintf(stderr, "Invalid field of view `%s'.\n", str); 98 return RES_BAD_ARG; 99 } 100 101 if(fov < 30 || fov > 120) { 102 fprintf(stderr, "The field of view %g is not in [30, 120].\n", fov); 103 return RES_BAD_ARG; 104 } 105 *out_fov = fov; 106 return RES_OK; 107 } 108 109 static res_T 110 parse_multiple_options 111 (const char* str, 112 struct solstice_args* args, 113 res_T (*parse_option)(const char* str, struct solstice_args* args)) 114 { 115 char buf[512]; 116 char* tk; 117 char* ctx; 118 res_T res = RES_OK; 119 ASSERT(args && str); 120 121 if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { 122 fprintf(stderr, "Could not duplicate the option string `%s'.\n", str); 123 res = RES_MEM_ERR; 124 goto error; 125 } 126 strncpy(buf, str, sizeof(buf)); 127 128 tk = strtok_r(buf, ":", &ctx); 129 do { 130 res = parse_option(tk, args); 131 if(res != RES_OK) goto error; 132 tk = strtok_r(NULL, ":", &ctx); 133 } while(tk); 134 135 exit: 136 return res; 137 error: 138 goto exit; 139 } 140 141 static res_T 142 parse_sun_dir_list(const char* str, struct solstice_args* args) 143 { 144 char buf[512]; 145 char* tk; 146 char* ctx; 147 size_t len; 148 res_T res = RES_OK; 149 ASSERT(str && args); 150 151 if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { 152 fprintf(stderr, 153 "Could not duplicate the list of sun directions `%s'.\n", str); 154 res = RES_MEM_ERR; 155 goto error; 156 } 157 strncpy(buf, str, sizeof(buf)); 158 159 tk = strtok_r(buf, ":", &ctx); 160 while(tk) { 161 struct solstice_args_spherical spherical; 162 double tmp[2]; 163 164 res = cstr_to_list_double(tk, ',', tmp, &len, 2); 165 if(res == RES_OK && len != 2) res = RES_BAD_ARG; 166 if(res != RES_OK) { 167 fprintf(stderr, "Invalid sun direction `%s'.\n", tk); 168 goto error; 169 } 170 171 if(tmp[0] < 0 || tmp[0] >= 360) { 172 fprintf(stderr, 173 "Invalid azimuth angle `%g'. Azimuth must be in [0, 360[ degrees.\n", 174 tmp[0]); 175 res = RES_BAD_ARG; 176 goto error; 177 } 178 if(tmp[1] < 0 || tmp[1] > 90) { 179 fprintf(stderr, 180 "Invalid elevation angle `%g'. Elevation must be in [0, 90] degrees.\n", 181 tmp[1]); 182 res = RES_BAD_ARG; 183 goto error; 184 } 185 186 spherical.azimuth = tmp[0]; 187 spherical.elevation = tmp[1]; 188 sa_push(args->sun_dirs, spherical); 189 190 tk = strtok_r(NULL, ":", &ctx); 191 } 192 193 args->nsun_dirs += sa_size(args->sun_dirs); 194 195 exit: 196 return res; 197 error: 198 if(args->sun_dirs) { 199 sa_release(args->sun_dirs); 200 args->sun_dirs = NULL; 201 args->nsun_dirs = 0; 202 } 203 goto exit; 204 } 205 206 static res_T 207 parse_image_definition 208 (const char* str, 209 unsigned long* width, 210 unsigned long* height) 211 { 212 char buf[64]; 213 char* tk; 214 char* ctx; 215 res_T res = RES_OK; 216 ASSERT(str && width && height); 217 218 if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { 219 fprintf(stderr, 220 "Could not duplicate the image definition string `%s'.\n", str); 221 return RES_MEM_ERR; 222 } 223 strncpy(buf, str, sizeof(buf)); 224 225 tk = strtok_r(buf, "x", &ctx); 226 res = cstr_to_ulong(tk, width); 227 if(res == RES_OK && !*width) res = RES_BAD_ARG; 228 if(res != RES_OK) { 229 fprintf(stderr, "Invalid image width `%s'\n", tk); 230 return res; 231 } 232 233 tk = strtok_r(NULL, "", &ctx); 234 res = cstr_to_ulong(tk, height); 235 if(res == RES_OK && !*height) res = RES_BAD_ARG; 236 if(res != RES_OK) { 237 fprintf(stderr, "Invalid image height `%s'\n", tk); 238 return res; 239 } 240 241 return res; 242 } 243 244 static res_T 245 parse_render_mode(const char* str, enum solstice_args_render_mode* mode) 246 { 247 res_T res = RES_OK; 248 ASSERT(str && mode); 249 250 if(!strcmp(str, "draft")) { 251 *mode = SOLSTICE_ARGS_RENDER_DRAFT; 252 } else if(!strcmp(str, "pt")) { 253 *mode = SOLSTICE_ARGS_RENDER_PATH_TRACING; 254 } else { 255 fprintf(stderr, "Invalid render mode `%s'.\n", str); 256 res = RES_BAD_ARG; 257 goto error; 258 } 259 exit: 260 return res; 261 error: 262 goto exit; 263 } 264 265 static res_T 266 parse_rendering_option(const char* str, struct solstice_args* args) 267 { 268 char buf[128]; 269 char* key; 270 char* val; 271 char* ctx; 272 size_t len; 273 res_T res = RES_OK; 274 ASSERT(str && args); 275 276 if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { 277 fprintf(stderr, 278 "Could not duplicate the rendering option string `%s'\n", str); 279 res = RES_MEM_ERR; 280 goto error; 281 } 282 strncpy(buf, str, sizeof(buf)); 283 284 key = strtok_r(buf, "=", &ctx); 285 val = strtok_r(NULL, "", &ctx); 286 287 if(!val) { 288 fprintf(stderr, "Missing a value to the rendering option `%s'.\n", key); 289 res = RES_BAD_ARG; 290 goto error; 291 } 292 293 if(!strcmp(key, "fov")) { /* Camera horizontal field of view in degrees */ 294 res = parse_fov(val, &args->camera.fov_x); 295 if(res != RES_OK) goto error; 296 } else if(!strcmp(key, "img")) { /* Image definition */ 297 res = parse_image_definition(val, &args->img.width, &args->img.height); 298 if(res != RES_OK) goto error; 299 } else if(!strcmp(key, "pos")) { /* Camera position */ 300 res = cstr_to_list_double(val, ',', args->camera.pos, &len, 3); 301 if(res == RES_OK && len != 3) res = RES_BAD_ARG; 302 if(res != RES_OK ) { 303 fprintf(stderr, "Invalid camera position `%s'.\n", val); 304 goto error; 305 } 306 args->camera.auto_look_at = 0; /* Disable auto look at */ 307 } else if(!strcmp(key, "rmode")) { /* Render mode */ 308 res = parse_render_mode(val, &args->render_mode); 309 if(res != RES_OK) goto error; 310 } else if(!strcmp(key, "spp")) { /*# Samples per pixel */ 311 res = cstr_to_uint(val, &args->img.spp); 312 if(res == RES_OK && !args->img.spp) res = RES_BAD_ARG; 313 if(res != RES_OK) { 314 fprintf(stderr, "Invalid number of samples per pixel `%s'.\n", val); 315 goto error; 316 } 317 } else if(!strcmp(key, "tgt")) { /* Camera target */ 318 res = cstr_to_list_double(val, ',', args->camera.tgt, &len, 3); 319 if(res == RES_OK && len != 3) res = RES_BAD_ARG; 320 if(res != RES_OK) { 321 fprintf(stderr, "Invalid camera target `%s'.\n", val); 322 goto error; 323 } 324 args->camera.auto_look_at = 0; /* Disable auto look at */ 325 } else if(!strcmp(key, "up")) { /* Camera up vector */ 326 res = cstr_to_list_double(val, ',', args->camera.up, &len, 3); 327 if(res == RES_OK && len != 3) res = RES_BAD_ARG; 328 if(res != RES_OK) { 329 fprintf(stderr, "Invalid camera up vector `%s'.\n", val); 330 goto error; 331 } 332 } else { 333 fprintf(stderr, "Invalid rendering option `%s'.\n", key); 334 res = RES_BAD_ARG; 335 goto error; 336 } 337 338 exit: 339 return res; 340 error: 341 goto exit; 342 } 343 344 static res_T 345 parse_dump_format(const char* str, enum solstice_args_dump_format* fmt) 346 { 347 res_T res = RES_OK; 348 ASSERT(str && fmt); 349 350 if(!strcmp(str, "obj")) { 351 *fmt = SOLSTICE_ARGS_DUMP_OBJ; 352 } else { 353 fprintf(stderr, "Invalid dump format `%s'.\n", str); 354 res = RES_BAD_ARG; 355 goto error; 356 } 357 358 exit: 359 return res; 360 error: 361 goto exit; 362 } 363 364 static res_T 365 parse_dump_split_mode(const char* str, enum solstice_args_dump_split_mode* mode) 366 { 367 res_T res = RES_OK; 368 ASSERT(str && mode); 369 370 if(!strcmp(str, "geometry")) { 371 *mode = SOLSTICE_ARGS_DUMP_SPLIT_GEOMETRY; 372 } else if(!strcmp(str, "none")) { 373 *mode = SOLSTICE_ARGS_DUMP_SPLIT_NONE; 374 } else if(!strcmp(str, "object")) { 375 *mode = SOLSTICE_ARGS_DUMP_SPLIT_OBJECT; 376 } else { 377 fprintf(stderr, "Invalid dump split mode `%s'.\n", str); 378 res = RES_BAD_ARG; 379 goto error; 380 } 381 exit: 382 return res; 383 error: 384 goto exit; 385 } 386 387 static res_T 388 parse_dump_option(const char* str, struct solstice_args* args) 389 { 390 char buf[128]; 391 char* key; 392 char* val; 393 char* ctx; 394 res_T res = RES_OK; 395 ASSERT(str && args); 396 397 if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { 398 fprintf(stderr, 399 "Could not duplicate the dump geometry option string `%s'\n", str); 400 res = RES_MEM_ERR; 401 goto error; 402 } 403 strncpy(buf, str, sizeof(buf)); 404 405 key = strtok_r(buf, "=", &ctx); 406 val = strtok_r(NULL, "", &ctx); 407 408 if(!val) { 409 fprintf(stderr, "Missing a value to the dump option `%s'.\n", key); 410 res = RES_BAD_ARG; 411 goto error; 412 } 413 414 if(!strcmp(key, "format")) { 415 res = parse_dump_format(val, &args->dump_format); 416 } else if(!strcmp(key, "split")) { 417 res = parse_dump_split_mode(val, &args->dump_split_mode); 418 } else { 419 fprintf(stderr, "Invalid dump option `%s'.\n", val); 420 res = RES_BAD_ARG; 421 goto error; 422 } 423 if(res != RES_OK) goto error; 424 425 exit: 426 return res; 427 error: 428 goto exit; 429 } 430 431 static res_T 432 parse_dump_paths_option(const char* str, struct solstice_args* args) 433 { 434 char buf[128]; 435 char* key; 436 char* val; 437 char* ctx; 438 res_T res = RES_OK; 439 ASSERT(str && args); 440 441 if(!strcmp(str, "default")) { 442 args->infinite_ray_length = SOLSTICE_ARGS_DEFAULT.infinite_ray_length; 443 args->sun_ray_length = SOLSTICE_ARGS_DEFAULT.sun_ray_length; 444 goto exit; 445 } 446 447 if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { 448 fprintf(stderr, 449 "Could not duplicate the dump radiative paths option string `%s'.\n", str); 450 res = RES_MEM_ERR; 451 goto error; 452 } 453 454 strncpy(buf, str, sizeof(buf)); 455 456 key = strtok_r(buf, "=", &ctx); 457 val = strtok_r(NULL, "", &ctx); 458 459 if(!val) { 460 fprintf(stderr, 461 "Missing a value to the dump radiative paths option `%s'.\n", key); 462 res = RES_BAD_ARG; 463 goto error; 464 } 465 466 if(!strcmp(key, "irlen")) { 467 res = cstr_to_double(val, &args->infinite_ray_length); 468 if(res != RES_OK) { 469 fprintf(stderr, "Invalid infinite ray length `%s'.\n", val); 470 goto error; 471 } 472 } else if(!strcmp(key, "srlen")) { 473 res = cstr_to_double(val, &args->sun_ray_length); 474 if(res != RES_OK) { 475 fprintf(stderr, "Invalid sun ray length `%s'.\n", val); 476 goto error; 477 } 478 } else { 479 fprintf(stderr, "Invalid dump radiative paths option `%s'.\n", val); 480 res = RES_BAD_ARG; 481 goto error; 482 } 483 if(res != RES_OK) goto error; 484 485 exit: 486 return res; 487 error: 488 goto exit; 489 } 490 491 static res_T 492 parse_rng_option(const char* str, struct solstice_args* args) 493 { 494 char buf[128]; 495 char* key; 496 char* val; 497 char* ctx; 498 size_t len; 499 res_T res = RES_OK; 500 ASSERT(str && args); 501 502 if(strlen(str) >= sizeof(buf)-1/*NULL char*/) { 503 fprintf(stderr, 504 "Could not duplicate the RNG option string `%s'\n", str); 505 res = RES_MEM_ERR; 506 goto error; 507 } 508 strncpy(buf, str, sizeof(buf)); 509 510 key = strtok_r(buf, "=", &ctx); 511 val = strtok_r(NULL, "", &ctx); 512 513 if(!val) { 514 fprintf(stderr, "Missing a value to the RNG option `%s'.\n", key); 515 res = RES_BAD_ARG; 516 goto error; 517 } 518 519 if(!strcmp(key, "istate")) { /* Input state */ 520 len = strlen(val); 521 args->rng_state_input_filename = mem_calloc(len+1, sizeof(char)); 522 if(!args->rng_state_input_filename) { res = RES_MEM_ERR; goto error; } 523 strcpy(args->rng_state_input_filename, val); 524 } else if(!strcmp(key, "ostate")) { /* Output state */ 525 len = strlen(val); 526 args->rng_state_output_filename = mem_calloc(len+1, sizeof(char)); 527 if(!args->rng_state_output_filename) { res = RES_MEM_ERR; goto error; } 528 strcpy(args->rng_state_output_filename, val); 529 } else { 530 fprintf(stderr, "Invalid RNG option `%s'.\n", key); 531 res = RES_BAD_ARG; 532 goto error; 533 } 534 535 exit: 536 return res; 537 error: 538 goto exit; 539 } 540 541 /******************************************************************************* 542 * Local function 543 ******************************************************************************/ 544 res_T 545 solstice_args_init(struct solstice_args* args, const int argc, char** argv) 546 { 547 int opt; 548 int i; 549 res_T res = RES_OK; 550 ASSERT(args && argc && argv); 551 552 *args = SOLSTICE_ARGS_DEFAULT; 553 554 FOR_EACH(i, 1, argc) { 555 if(!strcmp(argv[i], "--version")) { 556 printf("Solstice %d.%d.%d\n", 557 SOLSTICE_VERSION_MAJOR, 558 SOLSTICE_VERSION_MINOR, 559 SOLSTICE_VERSION_PATCH); 560 args->quit = 1; 561 goto exit; 562 } 563 } 564 565 optind = 0; 566 while((opt = getopt(argc, argv, "D:fG:g:hn:o:p:qR:r:t:v")) != -1) { 567 switch(opt) { 568 case 'D': /* Sun directions */ 569 res = parse_sun_dir_list(optarg, args); 570 break; 571 case 'f': args->force_overwriting = 1; break; 572 case 'h': /* Print short help and exit */ 573 print_help(argv[0]); 574 solstice_args_release(args); 575 args->quit = 1; 576 goto exit; 577 case 'n': /* Define the number of experiments */ 578 res = cstr_to_ulong(optarg, &args->nexperiments); 579 if(res == RES_OK && !args->nexperiments) res = RES_BAD_ARG; 580 break; 581 case 'G': /* Setup the random number generator */ 582 res = parse_multiple_options(optarg, args, parse_rng_option); 583 break; 584 case 'g': /* Switch in dump geometry mode and configure it */ 585 res = parse_multiple_options(optarg, args, parse_dump_option); 586 if(res == RES_OK && args->dump_format == SOLSTICE_ARGS_DUMP_NONE) { 587 fprintf(stderr, "%s: missing a dump format -- `%s'.\n", 588 argv[0], optarg); 589 res = RES_BAD_ARG; 590 } 591 break; 592 case 'o': args->output_filename = optarg; break; 593 case 'p': /* Switch in dump radiative paths mode and configure it */ 594 args->dump_paths = 1; 595 res = parse_multiple_options(optarg, args, parse_dump_paths_option); 596 break; 597 case 'q': args->quiet = 1; break; 598 case 'R': args->receivers_filename = optarg; break; 599 case 'r': /* Switch in rendering mode and configure it */ 600 args->rendering = 1; 601 res = parse_multiple_options(optarg, args, parse_rendering_option); 602 break; 603 case 't': /* Submit an hint on the number of threads to use */ 604 res = cstr_to_uint(optarg, &args->nthreads); 605 if(res == RES_OK && !args->nthreads) res = RES_BAD_ARG; 606 break; 607 case 'v': args->verbose = 1; break; 608 default: res = RES_BAD_ARG; break; 609 } 610 if(res != RES_OK) { 611 if(optarg) { 612 fprintf(stderr, "%s: invalid option argument '%s' -- '%c'\n", 613 argv[0], optarg, opt); 614 } 615 goto error; 616 } 617 } 618 619 if(!args->rendering 620 && args->dump_format == SOLSTICE_ARGS_DUMP_NONE 621 && !args->nsun_dirs) { 622 fprintf(stderr, "Missing sun direction.\n"); 623 res = RES_BAD_ARG; 624 goto error; 625 } 626 627 if(args->dump_format != SOLSTICE_ARGS_DUMP_NONE && args->rendering) { 628 fprintf(stderr, "The '-g' and '-r' options are exclusives.\n"); 629 res = RES_BAD_ARG; 630 goto error; 631 } 632 633 if(args->dump_format != SOLSTICE_ARGS_DUMP_NONE && args->dump_paths) { 634 fprintf(stderr, "The '-g' and '-p' options are exclusives.\n"); 635 res = RES_BAD_ARG; 636 goto error; 637 } 638 639 if(args->dump_paths && args->rendering) { 640 fprintf(stderr, "The '-p' and '-r' options are exclusives.\n"); 641 res = RES_BAD_ARG; 642 goto error; 643 } 644 645 if(optind < argc) { 646 args->input_filename = argv[optind]; 647 } 648 649 exit: 650 optind = 1; 651 return res; 652 error: 653 solstice_args_release(args); 654 goto exit; 655 } 656 657 void 658 solstice_args_release(struct solstice_args* args) 659 { 660 ASSERT(args); 661 sa_release(args->sun_dirs); 662 if(args->rng_state_input_filename) mem_rm(args->rng_state_input_filename); 663 if(args->rng_state_output_filename) mem_rm(args->rng_state_output_filename); 664 *args = SOLSTICE_ARGS_NULL; 665 } 666