ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/gen/genssky.c
Revision: 2.2
Committed: Fri Jul 19 23:38:28 2024 UTC (9 months, 2 weeks ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.1: +279 -91 lines
Log Message:
feat(genssky): Major changes and improvements by Taoning Wang

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id$";
3 #endif
4 /* Main function for generating spectral sky */
5 /* Cloudy sky computed as weight average of clear and cie overcast sky */
6
7 #include "atmos.h"
8 #include "copyright.h"
9 #include "resolu.h"
10 #include "rtio.h"
11 #include "view.h"
12 #include <ctype.h>
13 #ifdef _WIN32
14 #include <windows.h>
15 #else
16 #include <errno.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19 #endif
20
21 char *progname;
22
23 const double ARCTIC_LAT = 67.;
24 const double TROPIC_LAT = 23.;
25 const int SUMMER_START = 4;
26 const int SUMMER_END = 9;
27 const double GNORM = 0.777778;
28
29 const double D65EFF = 203.; /* standard illuminant D65 */
30
31 /* Mean normalized relative daylight spectra where CCT = 6415K for overcast; */
32 const double D6415[NSSAMP] = {0.63231, 1.06171, 1.00779, 1.36423, 1.34133,
33 1.27258, 1.26276, 1.26352, 1.22201, 1.13246,
34 1.0434, 1.05547, 0.98212, 0.94445, 0.9722,
35 0.82387, 0.87853, 0.82559, 0.75111, 0.78925};
36
37 /* European and North American zones */
38 struct {
39 char zname[8]; /* time zone name (all caps) */
40 float zmer; /* standard meridian */
41 } tzone[] = {{"YST", 135}, {"YDT", 120}, {"PST", 120}, {"PDT", 105},
42 {"MST", 105}, {"MDT", 90}, {"CST", 90}, {"CDT", 75},
43 {"EST", 75}, {"EDT", 60}, {"AST", 60}, {"ADT", 45},
44 {"NST", 52.5}, {"NDT", 37.5}, {"GMT", 0}, {"BST", -15},
45 {"CET", -15}, {"CEST", -30}, {"EET", -30}, {"EEST", -45},
46 {"AST", -45}, {"ADT", -60}, {"GST", -60}, {"GDT", -75},
47 {"IST", -82.5}, {"IDT", -97.5}, {"JST", -135}, {"NDT", -150},
48 {"NZST", -180}, {"NZDT", -195}, {"", 0}};
49
50 static int make_directory(const char *path) {
51 #ifdef _WIN32
52 if (CreateDirectory(path, NULL) || GetLastError() == ERROR_ALREADY_EXISTS) {
53 return 1;
54 }
55 return 0;
56 #else
57 if (mkdir(path, 0777) == 0 || errno == EEXIST) {
58 return 1;
59 }
60 return 0;
61 #endif
62 }
63
64 static int cvthour(char *hs, int *tsolar, double *hour) {
65 char *cp = hs;
66 int i, j;
67
68 if ((*tsolar = *cp == '+'))
69 cp++; /* solar time? */
70 while (isdigit(*cp))
71 cp++;
72 if (*cp == ':')
73 *hour = atoi(hs) + atoi(++cp) / 60.0;
74 else {
75 *hour = atof(hs);
76 if (*cp == '.')
77 cp++;
78 }
79 while (isdigit(*cp))
80 cp++;
81 if (!*cp)
82 return (0);
83 if (*tsolar || !isalpha(*cp)) {
84 fprintf(stderr, "%s: bad time format: %s\n", progname, hs);
85 exit(1);
86 }
87 i = 0;
88 do {
89 for (j = 0; cp[j]; j++)
90 if (toupper(cp[j]) != tzone[i].zname[j])
91 break;
92 if (!cp[j] && !tzone[i].zname[j]) {
93 s_meridian = tzone[i].zmer * (PI / 180);
94 return (1);
95 }
96 } while (tzone[i++].zname[0]);
97
98 fprintf(stderr, "%s: unknown time zone: %s\n", progname, cp);
99 fprintf(stderr, "Known time zones:\n\t%s", tzone[0].zname);
100 for (i = 1; tzone[i].zname[0]; i++)
101 fprintf(stderr, " %s", tzone[i].zname);
102 putc('\n', stderr);
103 exit(1);
104 }
105
106 static void basename(const char *path, char *output, size_t outsize) {
107 const char *last_slash = strrchr(path, '/');
108 const char *last_backslash = strrchr(path, '\\');
109 const char *filename = path;
110 const char *last_dot;
111
112 if (last_slash && last_backslash) {
113 filename =
114 (last_slash > last_backslash) ? last_slash + 1 : last_backslash + 1;
115 } else if (last_slash) {
116 filename = last_slash + 1;
117 } else if (last_backslash) {
118 filename = last_backslash + 1;
119 }
120
121 last_dot = strrchr(filename, '.');
122 if (last_dot) {
123 size_t length = last_dot - filename;
124 if (length < outsize) {
125 strncpy(output, filename, length);
126 output[length] = '\0';
127 } else {
128 strncpy(output, filename, outsize - 1);
129 output[outsize - 1] = '\0';
130 }
131 }
132 }
133
134 char *join_paths(const char *path1, const char *path2) {
135 size_t len1 = strlen(path1);
136 size_t len2 = strlen(path2);
137 int need_separator = (path1[len1 - 1] != DIRSEP);
138
139 char *result = malloc(len1 + len2 + (need_separator ? 2 : 1));
140 if (!result)
141 return NULL;
142
143 strcpy(result, path1);
144 if (need_separator) {
145 result[len1] = DIRSEP;
146 len1++;
147 }
148 strcpy(result + len1, path2);
149
150 return result;
151 }
152
153 static inline double wmean2(const double a, const double b, const double x) {
154 return a * (1 - x) + b * x;
155 }
156
157 static inline double wmean(const double a, const double x, const double b,
158 const double y) {
159 return (a * x + b * y) / (a + b);
160 }
161
162 static double get_zenith_brightness(const double sundir[3]) {
163 double zenithbr;
164 if (sundir[2] < 0) {
165 zenithbr = 0;
166 } else {
167 zenithbr = (8.6 * sundir[2] + .123) * 1000.0 / D65EFF;
168 }
169 return zenithbr;
170 }
171
172 /* from gensky.c */
173 static double get_overcast_brightness(const double dz, const double zenithbr) {
174 double groundbr = zenithbr * GNORM;
175 return wmean(pow(dz + 1.01, 10), zenithbr * (1 + 2 * dz) / 3,
176 pow(dz + 1.01, -10), groundbr);
177 }
178
179 static void write_header(const int argc, char **argv, const double cloud_cover,
180 const double grefl, const int res) {
181 printf("# ");
182 for (int i = 0; i < argc; i++) {
183 printf("%s ", argv[i]);
184 }
185 printf("\n");
186 printf("#Cloud cover: %g\n#Ground reflectance: %g\n#Sky map resolution: %d\n\n",
187 cloud_cover, grefl, res);
188 }
189
190 static void write_rad(const double *sun_radiance, const FVECT sundir,
191 const char skyfile[PATH_MAX],
192 const char grndfile[PATH_MAX]) {
193 if (sundir[2] > 0) {
194 printf("void spectrum sunrad\n0\n0\n22 380 780 ");
195 /* Normalize to one */
196 double sum = 0.0;
197 for (int i = 0; i < NSSAMP; ++i) {
198 sum += sun_radiance[i];
199 }
200 double mean = sum / NSSAMP;
201 for (int i = 0; i < NSSAMP; ++i) {
202 printf("%.3f ", sun_radiance[i] / mean);
203 }
204 double intensity = mean * WVLSPAN;
205 printf("\n\nsunrad light solar\n0\n0\n3 %.1f %.1f %.1f\n\n", intensity,
206 intensity, intensity);
207 printf("solar source sun\n0\n0\n4 %f %f %f 0.533\n\n", sundir[0], sundir[1],
208 sundir[2]);
209 }
210 printf("void specpict skymap\n8 noop %s fisheye.cal fish_u fish_v -rx 90 "
211 "-mx\n0\n0\n\n",
212 skyfile);
213
214 printf("void specpict grndmap\n8 noop %s fisheye.cal fish_u fish_v -rx -90 "
215 "-my\n0\n0\n\n",
216 grndfile);
217 printf("void mixfunc skyfunc\n4 skymap grndmap if(Dz,1,0) .\n0\n0\n");
218 }
219
220 static void write_hsr_header(FILE *fp, RESOLU *res) {
221 float wvsplit[4] = {380, 480, 588,
222 780}; /* RGB wavelength limits+partitions (nm) */
223 newheader("RADIANCE", fp);
224 fputncomp(NSSAMP, fp);
225 fputwlsplit(wvsplit, fp);
226 fputformat(SPECFMT, fp);
227 fputc('\n', fp);
228 fputsresolu(res, fp);
229 }
230
231 int gen_spect_sky(DATARRAY *tau_clear, DATARRAY *scat_clear,
232 DATARRAY *scat1m_clear, DATARRAY *irrad_clear,
233 const double cloud_cover, const FVECT sundir,
234 const double grefl, const int res, const char *outname) {
235 char skyfile[PATH_MAX];
236 char grndfile[PATH_MAX];
237 if (!snprintf(skyfile, sizeof(skyfile), "%s_sky.hsr", outname)) {
238 fprintf(stderr, "Error setting sky file name\n");
239 return 0;
240 };
241 if (!snprintf(grndfile, sizeof(grndfile), "%s_ground.hsr", outname)) {
242 fprintf(stderr, "Error setting ground file name\n");
243 return 0;
244 }
245 RESOLU rs = {PIXSTANDARD, res, res};
246 FILE *skyfp = fopen(skyfile, "w");
247 FILE *grndfp = fopen(grndfile, "w");
248 write_hsr_header(grndfp, &rs);
249 write_hsr_header(skyfp, &rs);
250 VIEW skyview = {VT_ANG, {0., 0., 0.}, {0., 0., 1.}, {0., 1., 0.}, 1.,
251 180., 180., 0., 0., 0.,
252 0., {0., 0., 0.}, {0., 0., 0.}, 0., 0.};
253 VIEW grndview = {
254 VT_ANG, {0., 0., 0.}, {0., 0., -1.}, {0., 1., 0.}, 1., 180., 180., 0., 0.,
255 0., 0., {0., 0., 0.}, {0., 0., 0.}, 0., 0.};
256 setview(&skyview);
257 setview(&grndview);
258
259 CNDX[3] = NSSAMP;
260
261 FVECT view_point = {0, 0, ER};
262 const double radius = VLEN(view_point);
263 const double sun_ct = fdot(view_point, sundir) / radius;
264 for (unsigned int j = 0; j < res; ++j) {
265 for (unsigned int i = 0; i < res; ++i) {
266 RREAL loc[2];
267 FVECT rorg = {0};
268 FVECT rdir_sky = {0};
269 FVECT rdir_grnd = {0};
270 SCOLOR sky_radiance = {0};
271 SCOLOR ground_radiance = {0};
272 SCOLR sky_sclr = {0};
273 SCOLR ground_sclr = {0};
274
275 pix2loc(loc, &rs, i, j);
276 viewray(rorg, rdir_sky, &skyview, loc[0], loc[1]);
277 viewray(rorg, rdir_grnd, &grndview, loc[0], loc[1]);
278
279 const double mu_sky = fdot(view_point, rdir_sky) / radius;
280 const double nu_sky = fdot(rdir_sky, sundir);
281
282 const double mu_grnd = fdot(view_point, rdir_grnd) / radius;
283 const double nu_grnd = fdot(rdir_grnd, sundir);
284
285 get_sky_radiance(scat_clear, scat1m_clear, radius, mu_sky, sun_ct, nu_sky,
286 sky_radiance);
287 get_ground_radiance(tau_clear, scat_clear, scat1m_clear, irrad_clear,
288 view_point, rdir_grnd, radius, mu_grnd, sun_ct,
289 nu_grnd, grefl, sundir, ground_radiance);
290
291 for (int k = 0; k < NSSAMP; ++k) {
292 sky_radiance[k] *= WVLSPAN;
293 ground_radiance[k] *= WVLSPAN;
294 }
295
296 if (cloud_cover > 0) {
297 double zenithbr = get_zenith_brightness(sundir);
298 double grndbr = zenithbr * GNORM;
299 double skybr = get_overcast_brightness(rdir_sky[2], zenithbr);
300 for (int k = 0; k < NSSAMP; ++k) {
301 sky_radiance[k] =
302 wmean2(sky_radiance[k], skybr * D6415[k], cloud_cover);
303 ground_radiance[k] =
304 wmean2(ground_radiance[k], grndbr * D6415[k], cloud_cover);
305 }
306 }
307
308 scolor2scolr(sky_sclr, sky_radiance, 20);
309 putbinary(sky_sclr, LSCOLR, 1, skyfp);
310
311 scolor2scolr(ground_sclr, ground_radiance, 20);
312 putbinary(ground_sclr, LSCOLR, 1, grndfp);
313 }
314 }
315 fclose(skyfp);
316 fclose(grndfp);
317
318 /* Get solar radiance */
319 double sun_radiance[NSSAMP] = {0};
320 get_solar_radiance(tau_clear, scat_clear, scat1m_clear, sundir, radius,
321 sun_ct, sun_radiance);
322 if (cloud_cover > 0) {
323 double zenithbr = get_zenith_brightness(sundir);
324 double skybr = get_overcast_brightness(sundir[2], zenithbr);
325 for (int i = 0; i < NSSAMP; ++i) {
326 sun_radiance[i] =
327 wmean2(sun_radiance[i], D6415[i] * skybr / WVLSPAN, cloud_cover);
328 }
329 }
330
331 write_rad(sun_radiance, sundir, skyfile, grndfile);
332 return 1;
333 }
334
335 static DpPaths get_dppaths(const char *dir, const double aod, const char *mname,
336 const char *tag) {
337 DpPaths paths;
338
339 snprintf(paths.tau, PATH_MAX, "%s%ctau_%s_%s_%.2f.dat", dir, DIRSEP, tag,
340 mname, aod);
341 snprintf(paths.scat, PATH_MAX, "%s%cscat_%s_%s_%.2f.dat", dir, DIRSEP, tag,
342 mname, aod);
343 snprintf(paths.scat1m, PATH_MAX, "%s%cscat1m_%s_%s_%.2f.dat", dir, DIRSEP,
344 tag, mname, aod);
345 snprintf(paths.irrad, PATH_MAX, "%s%cirrad_%s_%s_%.2f.dat", dir, DIRSEP, tag,
346 mname, aod);
347
348 return paths;
349 }
350
351 static void set_rayleigh_density_profile(Atmosphere *atmos, char *tag,
352 const int is_summer,
353 const double s_latitude) {
354 /* Set rayleigh density profile */
355 if (fabs(s_latitude * 180.0 / PI) > ARCTIC_LAT) {
356 tag[0] = 's';
357 if (is_summer) {
358 tag[1] = 's';
359 atmos->rayleigh_density.layers[0].exp_scale = -1.0 / HR_SS;
360 atmos->beta_r0 = BR0_SS;
361 } else {
362 tag[1] = 'w';
363 atmos->rayleigh_density.layers[0].exp_scale = -1.0 / HR_SW;
364 atmos->beta_r0 = BR0_SW;
365 }
366 } else if (fabs(s_latitude * 180.0 / PI) > TROPIC_LAT) {
367 tag[0] = 'm';
368 if (is_summer) {
369 tag[1] = 's';
370 atmos->rayleigh_density.layers[0].exp_scale = -1.0 / HR_MS;
371 atmos->beta_r0 = BR0_MS;
372 } else {
373 tag[1] = 'w';
374 atmos->rayleigh_density.layers[0].exp_scale = -1.0 / HR_MW;
375 atmos->beta_r0 = BR0_MW;
376 }
377 } else {
378 tag[0] = 't';
379 tag[1] = 'r';
380 atmos->rayleigh_density.layers[0].exp_scale = -1.0 / HR_T;
381 atmos->beta_r0 = BR0_T;
382 }
383 tag[2] = '\0';
384 }
385
386 static Atmosphere init_atmos(const double aod, const double grefl) {
387 Atmosphere atmos = {.ozone_density = {.layers =
388 {
389 {.width = 25000.0,
390 .exp_term = 0.0,
391 .exp_scale = 0.0,
392 .linear_term = 1.0 / 15000.0,
393 .constant_term = -2.0 / 3.0},
394 {.width = AH,
395 .exp_term = 0.0,
396 .exp_scale = 0.0,
397 .linear_term = -1.0 / 15000.0,
398 .constant_term = 8.0 / 3.0},
399 }},
400 .rayleigh_density = {.layers =
401 {
402 {.width = AH,
403 .exp_term = 1.0,
404 .exp_scale = -1.0 / HR_MS,
405 .linear_term = 0.0,
406 .constant_term = 0.0},
407 }},
408 .beta_r0 = BR0_MS,
409 .beta_scale = aod / AOD0_CA,
410 .beta_m = NULL,
411 .grefl = grefl};
412 return atmos;
413 }
414
415 int main(int argc, char *argv[]) {
416 progname = argv[0];
417 int month, day;
418 double hour;
419 FVECT sundir;
420 int num_threads = 1;
421 int sorder = 4;
422 int year = 0;
423 int tsolar = 0;
424 int got_meridian = 0;
425 double grefl = 0.2;
426 double ccover = 0.0;
427 int res = 128;
428 double aod = AOD0_CA;
429 char *outname = "out";
430 char *mie_path = getpath("mie_ca.dat", getrlibpath(), R_OK);
431 char mie_name[20] = "mie_ca";
432 char lstag[3];
433 char *ddir = ".";
434
435 if (!strcmp(argv[1], "-defaults")) {
436 printf("-i %d\t\t\t\t#scattering order\n", sorder);
437 printf("-g %f\t\t\t#ground reflectance\n", grefl);
438 printf("-c %f\t\t\t#cloud cover\n", ccover);
439 printf("-r %d\t\t\t\t#image resolution\n", res);
440 printf("-d %f\t\t\t#broadband aerosol optical depth\n", AOD0_CA);
441 printf("-f %s\t\t\t\t#output name (-f)\n", outname);
442 printf("-p %s\t\t\t\t#atmos data directory\n", ddir);
443 exit(1);
444 }
445
446 if (argc < 4) {
447 fprintf(stderr,
448 "Usage: %s month day hour -y year -a lat -o lon -m tz -d aod -r "
449 "res -n nproc -c ccover -l mie -g grefl -f outpath\n",
450 argv[0]);
451 return 0;
452 }
453
454 month = atoi(argv[1]);
455 if (month < 1 || month > 12) {
456 fprintf(stderr, "bad month");
457 exit(1);
458 }
459 day = atoi(argv[2]);
460 if (day < 1 || day > 31) {
461 fprintf(stderr, "bad month");
462 exit(1);
463 }
464 got_meridian = cvthour(argv[3], &tsolar, &hour);
465
466 if (!compute_sundir(year, month, day, hour, tsolar, sundir)) {
467 fprintf(stderr, "Cannot compute solar angle\n");
468 exit(1);
469 }
470
471 for (int i = 4; i < argc; i++) {
472 if (argv[i][0] == '-') {
473 switch (argv[i][1]) {
474 case 'a':
475 s_latitude = atof(argv[++i]) * (PI / 180.0);
476 break;
477 case 'c':
478 ccover = atof(argv[++i]);
479 break;
480 case 'd':
481 aod = atof(argv[++i]);
482 break;
483 case 'f':
484 outname = argv[++i];
485 break;
486 case 'g':
487 grefl = atof(argv[++i]);
488 break;
489 case 'i':
490 sorder = atoi(argv[++i]);
491 break;
492 case 'l':
493 mie_path = argv[++i];
494 basename(mie_path, mie_name, sizeof(mie_name));
495 break;
496 case 'm':
497 if (got_meridian) {
498 ++i;
499 break;
500 }
501 s_meridian = atof(argv[++i]) * (PI / 180.0);
502 break;
503 case 'n':
504 num_threads = atoi(argv[++i]);
505 break;
506 case 'o':
507 s_longitude = atof(argv[++i]) * (PI / 180.0);
508 break;
509 case 'p':
510 ddir = argv[++i];
511 break;
512 case 'r':
513 res = atoi(argv[++i]);
514 break;
515 case 'y':
516 year = atoi(argv[++i]);
517 break;
518 default:
519 fprintf(stderr, "Unknown option %s\n", argv[i]);
520 exit(1);
521 }
522 }
523 }
524 if (year && (year < 1950) | (year > 2050))
525 fprintf(stderr, "%s: warning - year should be in range 1950-2050\n",
526 progname);
527 if (month && !tsolar && fabs(s_meridian - s_longitude) > 45 * PI / 180)
528 fprintf(stderr,
529 "%s: warning - %.1f hours btwn. standard meridian and longitude\n",
530 progname, (s_longitude - s_meridian) * 12 / PI);
531
532 Atmosphere clear_atmos = init_atmos(aod, grefl);
533
534 int is_summer = (month >= SUMMER_START && month <= SUMMER_END);
535 if (s_latitude < 0) {
536 is_summer = !is_summer;
537 }
538 set_rayleigh_density_profile(&clear_atmos, lstag, is_summer, s_latitude);
539
540 /* Load mie density data */
541 DATARRAY *mie_dp = getdata(mie_path);
542 if (mie_dp == NULL) {
543 fprintf(stderr, "Error reading mie data\n");
544 return 0;
545 }
546 clear_atmos.beta_m = mie_dp;
547
548 char gsdir[PATH_MAX];
549 size_t siz = strlen(ddir);
550 if (ISDIRSEP(ddir[siz-1]))
551 ddir[siz-1] = '\0';
552 snprintf(gsdir, PATH_MAX, "%s%catmos_data", ddir, DIRSEP);
553 printf("gsdir: %s\n", gsdir);
554 if (!make_directory(gsdir)) {
555 fprintf(stderr, "Failed creating atmos_data directory");
556 exit(1);
557 }
558 DpPaths clear_paths = get_dppaths(gsdir, aod, mie_name, lstag);
559
560 if (getpath(clear_paths.tau, ".", R_OK) == NULL ||
561 getpath(clear_paths.scat, ".", R_OK) == NULL ||
562 getpath(clear_paths.scat1m, ".", R_OK) == NULL ||
563 getpath(clear_paths.irrad, ".", R_OK) == NULL) {
564 printf("# Pre-computing...\n");
565 if (!precompute(sorder, clear_paths, &clear_atmos, num_threads)) {
566 fprintf(stderr, "Pre-compute failed\n");
567 return 0;
568 }
569 }
570
571 DATARRAY *tau_clear_dp = getdata(clear_paths.tau);
572 DATARRAY *irrad_clear_dp = getdata(clear_paths.irrad);
573 DATARRAY *scat_clear_dp = getdata(clear_paths.scat);
574 DATARRAY *scat1m_clear_dp = getdata(clear_paths.scat1m);
575
576 write_header(argc, argv, ccover, grefl, res);
577
578 if (!gen_spect_sky(tau_clear_dp, scat_clear_dp, scat1m_clear_dp,
579 irrad_clear_dp, ccover, sundir, grefl, res, outname)) {
580 fprintf(stderr, "gen_spect_sky failed\n");
581 exit(1);
582 }
583
584 freedata(mie_dp);
585 freedata(tau_clear_dp);
586 freedata(scat_clear_dp);
587 freedata(irrad_clear_dp);
588 freedata(scat1m_clear_dp);
589
590 return 1;
591 }