commit f6080cb9576a54572bf68f1b680b766df344d892
parent 7e8bae83c0da83fb307ec720a95ab881c5192ec6
Author: Vincent Forest <vincent.forest@meso-star.com>
Date: Thu, 18 Feb 2021 11:49:36 +0100
Major code refactoring to handle different computation modes
Draft that do not compile!
Diffstat:
76 files changed, 8545 insertions(+), 8064 deletions(-)
diff --git a/src/atmosphere/htrdr_atmosphere.c b/src/atmosphere/htrdr_atmosphere.c
@@ -0,0 +1,537 @@
+/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019, 2021 CNRS
+ * Copyright (C) 2018, 2019, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr_atmosphere.h"
+#include "htrdr_atmosphere_args.h"
+#include "htrdr_atmosphere_sun.h"
+
+#include "htrdr_buffer.h"
+#include "htrdr_camera.h"
+#include "htrdr_cie_xyz.h"
+#include "htrdr_geometry.h"
+#include "htrdr_log.h"
+#include "htrdr_materials.h"
+
+#include <star/s3d.h>
+
+struct pixel_format {
+ size_t size; /* In bytes */
+ size_t alignment; /* In bytes */
+};
+#define PIXEL_FORMAT_NULL__ {0, 0}
+static const struct pixel_format PIXEL_FORMAT_NULL = PIXEL_FORMAT_NULL__;
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static enum htsky_spectral_type
+htrdr_to_sky_spectral_type(const enum htrdr_spectral_type type)
+{
+ enum htsky_spectral_type spectype;
+ switch(type) {
+ case HTRDR_SPECTRAL_LW:
+ spectype = HTSKY_SPECTRAL_LW;
+ break;
+ case HTRDR_SPECTRAL_SW:
+ case HTRDR_SPECTRAL_SW_CIE_XYZ:
+ spectype = HTSKY_SPECTRAL_SW;
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+ return spectype;
+}
+
+static INLINE void
+spherical_to_cartesian_dir
+ (const double azimuth, /* In radians */
+ const double elevation, /* In radians */
+ double dir[3])
+{
+ double cos_azimuth;
+ double sin_azimuth;
+ double cos_elevation;
+ double sin_elevation;
+ ASSERT(azimuth >= 0 && azimuth < 2*PI);
+ ASSERT(elevation >= 0 && elevation <= PI/2.0);
+ ASSERT(dir);
+
+ cos_azimuth = cos(azimuth);
+ sin_azimuth = sin(azimuth);
+ cos_elevation = cos(elevation);
+ sin_elevation = sin(elevation);
+
+ dir[0] = cos_elevation * cos_azimuth;
+ dir[1] = cos_elevation * sin_azimuth;
+ dir[2] = sin_elevation;
+}
+
+static INLINE void
+get_pixel_format
+ (const struct htrdr_atmosphere* cmd,
+ struct pixel_format* fmt)
+{
+ switch(cmd->sensor_type) {
+ case HTRDR_SENSOR_RECTANGLE:
+ fmt->size = sizeof(struct atmosphere_pixel_flux);
+ fmt->alignment = ALIGNOF(struct atmosphere_pixel_flux);
+ break;
+ case HTRDR_SENSOR_CAMERA:
+ switch(cmd->spectral_type) {
+ case HTRDR_SPECTRAL_LW:
+ case HTRDR_SPECTRAL_SW:
+ fmt->size = sizeof(struct atmosphere_pixel_xwave);
+ fmt->alignment = ALIGNOF(struct atmosphere_pixel_xwave);
+ break;
+ case HTRDR_SPECTRAL_SW_CIE_XYZ:
+ fmt->size = sizeof(struct atmosphere_pixel_image);
+ fmt->alignment = ALIGNOF(struct atmosphere_pixel_image);
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+}
+
+static res_T
+setup_sensor
+ (struct htrdr_atmosphere* cmd,
+ const struct htrdr_atmosphere_args* args)
+{
+ double proj_ratio;
+ res_T res = RES_OK;
+ ASSERT(htrdr && args);
+
+ cmd->sensor.type = args->sensor_type;
+
+ if(args->spectral.type == HTRDR_SPECTRAL_SW_CIE_XYZ
+ && args->sensor_type != HTRDR_SENSOR_CAMERA) {
+ htrdr_log_err(cmd->htrdr, "the CIE 1931 XYZ spectral integration can be used "
+ "only with a camera sensor.\n");
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ switch(args->sensor_type) {
+ case HTRDR_SENSOR_CAMERA:
+ proj_ratio =
+ (double)args->image.definition[0]
+ / (double)args->image.definition[1];
+ res = htrdr_camera_create(htrdr, args->camera.position,
+ args->camera.target, args->camera.up, proj_ratio,
+ MDEG2RAD(args->camera.fov_y), &cmd->sensor.camera);
+ break;
+ case HTRDR_SENSOR_RECTANGLE:
+ res = htrdr_rectangle_create(htrdr, args->rectangle.size,
+ args->rectangle.position, args->rectangle.target, args->rectangle.up,
+ &cmd->sensor.rectangle);
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+ if(res != RES_OK) goto error;
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+dump_volumetric_acceleration_structure(struct htrdr_atmosphere* cmd)
+{
+ size_t nbands;
+ size_t i;
+ res_T res = RES_OK;
+ ASSERT(cmd);
+
+ nbands = htsky_get_spectral_bands_count(cmd->sky);
+
+ /* Nothing to do */
+ if(htrdr->mpi_rank != 0) goto exit;
+
+ FOR_EACH(i, 0, nbands) {
+ size_t iquad;
+ const size_t iband = htsky_get_spectral_band_id(cmd->sky, i);
+ const size_t nquads = htsky_get_spectral_band_quadrature_length
+ (cmd->sky, iband);
+
+ FOR_EACH(iquad, 0, nquads) {
+ res = htsky_dump_cloud_vtk(htrdr->sky, iband, iquad, htrdr->output);
+ if(res != RES_OK) goto error;
+ fprintf(htrdr->output, "---\n");
+ }
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static INLINE void
+dump_accum
+ (const struct htrdr_accum* acc, /* Accum to dump */
+ struct htrdr_accum* out_acc, /* May be NULL */
+ FILE* stream)
+{
+ ASSERT(acc && stream);
+
+ if(acc->nweights == 0) {
+ fprintf(streamn, "0 0 ");
+ } else {
+ struct htrdr_estimate estimate = HTRDR_ESTIMATE_NULL;
+
+ htrdr_accum_get_estimation(acc, &estimate);
+ fprintf("%g %g\n", estimate.E, estimate.SE);
+
+ if(out_acc) {
+ out_acc->sum_weights += acc->sum_weights;
+ out_acc->sum_weights_sqr += acc->sum_weights_sqr;
+ out_acc->nweights += acc->nweights;
+ }
+ }
+}
+
+static INLINE void
+dump_pixel_flux
+ (const struct htrdr_pixel_flux* pix,
+ struct htrdr_accum* time_acc, /* May be NULL */
+ struct htrdr_accum* flux_acc, /* May be NULL */
+ FILE* stream)
+{
+ struct htrdr_estimate pix_time = HTRDR_ESTIMATE_NULL;
+
+ ASSERT(pix && stream);
+ dump_accum(&pix->flux, flux_acc, stream_name, stream);
+ fprintf(stream, "0 0 0 0 ");
+ dump_accum(&pix->time, time_acc, stream_name, stream);
+ fprintf(stream, "\n");
+}
+
+static INLINE void
+dump_pixel_image
+ (const struct htrdr_pixel_image* pix,
+ struct htrdr_accum* time_acc, /* May be NULL */
+ FILE* stream)
+{
+ ASSERT(pix && stream_name && stream);
+ fprintf(stream, "%g %g ", pix->X.E, pix->X.SE);
+ fprintf(stream, "%g %g ", pix->Y.E, pix->Y.SE);
+ fprintf(stream, "%g %g ", pix->Z.E, pix->Z.SE);
+ dump_accum(pix->time, time_acc, stream);
+ fprintf(stream, "\n");
+}
+
+static INLINE void
+dump_pixel_xwave
+ (const struct htrdr_pixel_xwave* pix,
+ struct htrdr_accum* time_acc, /* May be NULL */
+ const char* stream_name,
+ FILE* stream)
+{
+ fprintf(stream, "%g %g %f %f 0 0 ",
+ pix->radiance_temperature.E,
+ pix->radiance_temperature.SE,
+ pix->radiance.E,
+ pix->radiance.SE);
+ dump_accum(pix->time, time_acc, stream);
+ fprintf(stream, "\n");
+}
+
+static res_T
+dump_buffer
+ (struct htrdr_atmosphere* cmd,
+ struct htrdr_buffer* buf,
+ struct htrdr_accum* time_acc, /* May be NULL */
+ struct htrdr_accum* flux_acc, /* May be NULL */
+ const char* stream_name,
+ FILE* stream)
+{
+ struct htrdr_buffer_layout layout;
+ size_t pixsz, pixal;
+ size_t x, y;
+ res_T res = RES_OK;
+ ASSERT(cmd && buf && stream_name && stream);
+ (void)stream_name;
+
+ pixsz = htrdr_spectral_type_get_pixsz(cmd->spectral_type, cmd->sensor.type);
+ pixal = htrdr_spectral_type_get_pixal(cmd->spectral_type, cmd->sensor.type);
+
+ htrdr_buffer_get_layout(buf, &layout);
+ if(layout.elmt_size != pixsz || layout.alignment != pixal) {
+ htrdr_log_err(cmd->htrdr, "%s: invalid buffer layout. ", FUNC_NAME);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ fprintf(stream, "%lu %lu\n", layout.width, layout.height);
+
+ if(time_acc) *time_acc = HTRDR_ACCUM_NULL;
+ if(flux_acc) *flux_acc = HTRDR_ACCUM_NULL;
+
+ FOR_EACH(y, 0, layout.height) {
+ FOR_EACH(x, 0, layout.width) {
+ struct htrdr_estimate pix_time = HTRDR_ESTIMATE_NULL;
+ const struct htrdr_accum* pix_time_acc = NULL;
+
+ if(cmd->sensor.type == HTRDR_SENSOR_RECTANGLE) {
+ const struct htrdr_pixel_flux* pix = htrdr_buffer_at(buf, x, y);
+ dump_pixel_flux(pix, time_acc, flux_acc, stream_name, stream);
+ } else if(cmd->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ) {
+ const struct htrdr_pixel_image* pix = htrdr_buffer_at(buf, x, y);
+ dump_pixel_image(pix, time_acc, stream_name, stream);
+ } else {
+ const struct htrdr_pixel_xwave* pix = htrdr_buffer_at(buf, x, y);
+ dump_pixel_xwave(pix, time_acc, stream_name, stream);
+ }
+ }
+ fprintf(stream, "\n");
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static void
+atmosphere_release(ref_T* ref)
+{
+ struct htrdr_atmosphere* cmd = CONTAINER_OF(ref, struct htrdr_atmosphere, ref);
+ struct htrdr* htrdr = NULL;
+ ASSERT(ref);
+
+ if(cmd->s3d) S3D(device_ref_put(cmd->s3d));
+ if(cmd->ground) htrdr_atmosphere_ground_ref_put(cmd->ground);
+ if(cmd->mats) htrdr_materials_ref_put(cmd->mats);
+ if(cmd->sun) htrdr_atmosphere_sun_ref_put(cmd->sun);
+ if(cmd->cie) htrdr_cie_xyz_ref_put(cmd->cie);
+ if(cmd->ran_wlen) htrdr_ran_wlen_ref_put(cmd->ran_wlen);
+ if(cmd->sensor.camera) htrdr_camera_ref_put(cmd->sensor.camera);
+ if(cmd->sensor.rectangle) htrdr_rectangle_ref_put(cmd->sensor.rectangle);
+ if(cmd->but) htrdr_buffer_ref_put(cmd->but);
+ if(cmd->sky) HTSKY(ref_put(cmd->sky));
+ if(cmd->output && cmd->output != stdout) fclose(cmd->output);
+ str_release(cmd->output_name);
+
+ htrdr = cmd->htrdr;
+ MEM_RM(htrdr->allocator, cmd);
+ htrdr_ref_put(htrdr);
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_atmosphere_create
+ (struct htrdr* htrdr,
+ const struct htrdr_atmosphere_args* args,
+ struct htrdr_atmosphere** out_cmd)
+{
+ struct htrdr_atmosphere* cmd = NULL;
+ struct htsky_args htsky_args = HTSKY_ARGS_DEFAULT;
+ double sun_dir[3];
+ double spectral_range[2];
+ const char* output_name = NULL;
+ res_T res = RES_OK;
+ ASSERT(htrdr && args && out_cmd);
+
+ cmd = MEM_CALLOC(htrdr->allocator, 1, sizeof(*cmd));
+ if(!cmd) {
+ htrdr_log_err(htrdr,
+ "%s: could not allocate the htrdr_atmosphere data.\n", FUNC_NAME);
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ ref_init(&cmd->ref);
+ str_init(htrdr->allocator, &cmd->output_name);
+ cmd->dump_vtk = args->dump_vtk;
+ cmd->verbose = args->verbose;
+ cmd->spp = args->image.spp;
+ cmd->width = args->image.definition[0];
+ cmd->height = args->image.definition[1];
+ cmd->grid_max_definition[0] = args->grid_max_definition[0];
+
+ /* Get ownership onf the htrdr structure */
+ htrdr_ref_get(htrdr);
+ cmd->htrdr = htrdr;
+
+ if(!args->filename_output) {
+ cmd->output = stdout;
+ output_name = "<stdout>";
+ } else if(htrdr->mpi_rank != 0) {
+ cmd->output = NULL;
+ output_name = "<null>";
+ } else {
+ res = htrdr_open_output_stream
+ (htrdr, args->output, 0/*read*/, args->force_overwriting, &cmd->output);
+ if(res != RES_OK) goto error;
+ output_name = args->output;
+ }
+ res = str_set(&cmd->output_name, output_name);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr,
+ "%s: could not store the name of the output stream `%s' -- %s.\n",
+ FUNC_NAME, output_name, res_to_cstr(res));
+ goto error;
+ }
+
+ /* Disable the Star-3D verbosity since the Embree backend prints some messages
+ * on stdout rather than stderr. This is annoying since stdout may be used by
+ * htrdr to write output data */
+ res = s3d_device_create
+ (&htrdr->logger, htrdr->allocator, 0, &cmd->s3d);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr,
+ "%s: could not create the Star-3D device -- %s.\n",
+ FUNC_NAME, res_to_cstr(res));
+ goto error;
+ }
+
+ /* Materials are necessary only if a ground geometry is defined */
+ if(args->filename_obj) {
+ res = htrdr_materials_create(htrdr, args->filename_mtl, &cmd->mats);
+ if(res != RES_OK) goto error;
+ }
+
+ res = htrdr_atmosphere_ground_create(htrdr, args->filename_obj,
+ args->repeat_ground, &cmd->ground);
+ if(res != RES_OK) goto error;
+
+ res = setup_sensor(cmd, args);
+ if(res != RES_OK) goto error;
+
+ res = htrdr_atmosphere_sun_create(htrdr, &cmd->sun);
+ if(res != RES_OK) goto error;
+ spherical_to_cartesian_dir
+ (MDEG2RAD(args->sun_azimuth), MDEG2RAD(args->sun_elevation), sun_dir);
+ htrdr_atmosphere_sun_set_direction(cmd->sun, sun_dir);
+
+ htsky_args.htcp_filename = args->filename_les;
+ htsky_args.htgop_filename = args->filename_gas;
+ htsky_args.htmie_filename = args->filename_mie;
+ htsky_args.cache_filename = args->filename_cache;
+ htsky_args.grid_max_definition[0] = args->grid_max_definition[0];
+ htsky_args.grid_max_definition[1] = args->grid_max_definition[1];
+ htsky_args.grid_max_definition[2] = args->grid_max_definition[2];
+ htsky_args.optical_thickness = args->optical_thickness;
+ htsky_args.nthreads = htrdr->nthreads;
+ htsky_args.repeat_clouds = args->repeat_clouds;
+ htsky_args.verbose = htrdr->mpi_rank == 0 ? args->verbose : 0;
+ htsky_args.spectral_type = htrdr_to_sky_spectral_type(args->spectral_type);
+ htsky_args.wlen_range[0] = args->spectral.wlen_range[0];
+ htsky_args.wlen_range[1] = args->spectral.wlen_range[1];
+ res = htsky_create(&htrdr->logger, htrdr->allocator, &htsky_args, &cmd->sky);
+ if(res != RES_OK) goto error;
+
+ HTSKY(get_raw_spectral_bounds(cmd->sky, spectral_range));
+
+ spectral_range[0] = MMAX(args->wlen_range[0], spectral_range[0]);
+ spectral_range[1] = MMIN(args->wlen_range[1], spectral_range[1]);
+ if(spectral_range[0] != args->wlen_range[0]
+ || spectral_range[1] != args->wlen_range[1]) {
+ htrdr_log_warn(htrdr,
+ "%s: the submitted spectral range overflowed the spectral data.\n", FUNC_NAME);
+ }
+
+ cmd->wlen_range_m[0] = spectral_range[0]*1e-9; /* Convert in meters */
+ cmd->wlen_range_m[1] = spectral_range[1]*1e-9; /* Convert in meters */
+
+ if(cmd->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ) {
+ size_t n;
+ n = (size_t)(spectral_range[1] - spectral_range[0]);
+ res = htrdr_cie_xyz_create(htrdr, spectral_range, n, &cmd->cie);
+ if(res != RES_OK) goto error;
+
+ } else {
+ size_t n;
+
+ if(cmd->ref_temperature <= 0) {
+ htrdr_log_err(htrdr, "%s: invalid reference temperature %g K.\n",
+ FUNC_NAME, cmd->ref_temperature);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ ASSERT(cmd->wlen_range_m[0] <= cmd->wlen_range_m[1]);
+ n = (size_t)(spectral_range[1] - spectral_range[0]);
+
+ res = htrdr_ran_wlen_create
+ (htrdr, spectral_range, n, cmd->ref_temperature, &cmd->ran_wlen);
+ if(res != RES_OK) goto error;
+ }
+
+ /* Create the image buffer only on the master process; the image parts
+ * rendered by the processes are gathered onto the master process. */
+ if(!cmd->dump_volumetric_acceleration_structure && htrdr->mpi_rank == 0) {
+ struct pixel_format pixfmt = PIXEL_FORMAT_NULL;
+ get_pixel_format(cmd, &pixfmt);
+
+ res = htrdr_buffer_create(htrdr,
+ args->image.definition[0], /* Width */
+ args->image.definition[1], /* Height */
+ args->image.definition[0] * pixfmt.size, /* Pitch */
+ pixfmt.size, /* Size of a pixel */
+ pixfmt.alignment, /* Alignment of a pixel */
+ &cmd->buf);
+ if(res != RES_OK) goto error;
+ }
+
+exit:
+ *out_cmd = cmd;
+ return res;
+error:
+ if(cmd) {
+ htrdr_atmosphere_ref_put(cmd);
+ cmd = NULL;
+ }
+ goto exit;
+}
+
+void
+htrdr_atmosphere_ref_get(struct htrdr_atmosphere* cmd)
+{
+ ASSERT(cmd);
+ ref_get(cmd->ref);
+}
+
+void
+htrdr_atmosphere_ref_put(struct htrdr_atmosphere* cmd)
+{
+ ASSERT(cmd);
+ ref_put(cmd->ref, atmosphere_release);
+}
+
+void
+htrdr_atmosphere_run(struct htrdr_atmosphere* cmd)
+{
+ res_T res = RES_OK;
+
+ if(cmd->dump_volumetric_acceleration_structure) {
+ res = dump_volumetric_acceleration_structure(cmd);
+ if(res != RES_OK) goto error;
+ } else {
+ res = draw_map(cmd);
+ if(res != RES_OK) goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
diff --git a/src/atmosphere/htrdr_atmosphere.h b/src/atmosphere/htrdr_atmosphere.h
@@ -0,0 +1,47 @@
+/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019, 2021 CNRS
+ * Copyright (C) 2018, 2019, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_ATMOSPHERE_H
+#define HTRDR_ATMOSPHERE_H
+
+#include <rsys/rsys.h>
+
+/* Forward declarations */
+struct htrdr;
+struct htrdr_atmosphere;
+struct htrdr_atmosphere_args;
+
+extern LOCAL_SYM res_T
+htrdr_atmosphere_create
+ (struct htrdr* htrdr,
+ const struct htrdr_atmosphere_args* args,
+ struct htrdr_atmosphere** cmd);
+
+extern LOCAL_SYM void
+htrdr_atmosphere_ref_get
+ (struct htrdr_atmosphere* cmd);
+
+extern LOCAL_SYM void
+htrdr_atmosphere_ref_put
+ (struct htrdr_atmosphere* cmd);
+
+extern LOCAL_SYM res_T
+htrdr_atmosphere_run
+ (struct htrdr_atmosphere* cmd);
+
+#endif /* HTRDR_ATMOSPHERE_H */
+
diff --git a/src/atmosphere/htrdr_atmosphere_args.c b/src/atmosphere/htrdr_atmosphere_args.c
@@ -0,0 +1,288 @@
+/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019, 2021 CNRS
+ * Copyright (C) 2018, 2019, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr_atmosphere_args.h"
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static void
+print_help_atmosphere(const char* cmd)
+{
+ ASSERT(cmd);
+ printf("Usage: %s [<opions>] -a GAS\n", cmd);
+ printf(
+"Render an image or compute a flux map in both the longwave and\n"
+"shortwave domains, for scenes with a cloudy atmosphere and a ground\n"
+"geometry.\n\n");
+
+ printf(
+" -a GAS filename of the gas optical properties.\n");
+
+ printf(
+" -C <camera> define the rendering point of view. Refer to the\n"
+" %s man page for the list of camera options.\n", cmd);
+ printf(
+" -c CLOUDS filename of the clouds properties.\n");
+ printf(
+" -D AZIMUTH,ELEVATION\n"
+" direction in degrees toward the sun center. By default\n"
+" AZIMUTH is %g and ELEVATION is %g.\n",
+ HTRDR_ATMOSPHERE_ARGS_DEFAULT.sun_azimuth,
+ HTRDR_ATMOSPHERE_ARGS_DEFAULT.sun_elevation);
+ printf(
+" -d dump volumetric acceleration structures to OUTPUT\n"
+" and exit.\n");"
+ printf(
+" -f overwrite the OUTPUT file if it already exists.\n");
+ printf(
+" -g GROUND filename of the ground geometry.\n");
+ printf(
+" -h display this help and exit.\n");
+ printf(
+" -i <image> define the image to compute. Refer to the %s man\n"
+" page for the list of image options\n", cmd);
+ printf(
+" -M MATERIALS filename of the ground materials.\n");
+ printf(
+" -m MIE filename of the Mie's data.\n");
+ printf(
+" -n SKY-NAME name used to identify the sky in the MATERIALS file.\n"
+" Its default value is `%s'.\n",
+ HTRDR_ATMOSPHERE_ARGS_DEFAULT.sky_mtl_name);
+ printf(
+" -O CACHE filenaname of the cache file used to store/restore the
+ volumetric data. By default do not use any cache.\n");
+ printf(
+" -o OUTPUT file where data are written. If not defined, data are\n"
+" written to standard output.\n");
+ printf(
+" -p <rectangle> switch in flux computation by defining the rectangular\n"
+" sensor onto wich the flux is computed. Refer to the\n"
+" %s man page for the list of rectangle options.\n", cmd);
+ printf(
+" -R infinitely repeat the ground along the X and Y axis.\n");
+ printf(
+" -r infinitely repeat the clouds along the X and Y axis.\n");
+ printf(
+" -s <spectral> define the type and range of the spectral\n"
+" integration. Refer to the %s man page for the list\n"
+" of spectral options\n", cmd);
+ printf(
+" -T THRESHOLD optical thickness used as threshold during the\n"
+" building of the volumetric acceleration structure.\n"
+" By default its value is `%g'.\n",
+ HTRDR_ATMOSPHERE_ARGS_DEFAULT.optical_thickness);
+ printf(
+" -t THREADS hint on the number of threads to use. By default use\n"
+" as many threads as CPU cores.\n");
+ printf(
+" -V X,Y,Z maximum definition along the 3 axis of the 3D\n"
+" volumetric majorant field to partition. By default use\n"
+" the definition of the clouds data.\n");
+ printf(
+" -v make the command verbose.\n");
+ printf("\n");
+
+ htrdr_fprint_copyright(cmd, stdout);
+ htrdr_fprint_license(cmd, stdout);
+}
+
+static res_T
+parse_grid_definition(struct htrdr_atmosphere_args* args, const char* str)
+{
+ unsigned def[3];
+ size_t len;
+ res_T res = RES_OK;
+ ASSERT(args && str);
+
+ res = cstr_to_list_uint(str, ',', def, &len, 3);
+ if(res == RES_OK && len != 3) res = RES_BAD_ARG;
+ if(res != RES_OK) {
+ fprintf(stderr, "Invalid grid definition `%s'.\n", str);
+ goto error;
+ }
+
+ if(!def[0] || !def[1] || !def[2]) {
+ fprintf(stderr,
+ "Invalid null grid definition {%u, %u, %u}.\n", SPLIT3(def));
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ args->grid_max_definition[0] = def[0];
+ args->grid_max_definition[1] = def[1];
+ args->grid_max_definition[2] = def[2];
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+parse_sun_dir(struct htrdr_atmosphere_args* args, const char* str)
+{
+ double angles[2];
+ size_t len;
+ res_T res = RES_OK;
+ ASSERT(args && str);
+
+ res = cstr_to_list_double(str, ',', angles, &len, 2);
+ if(res == RES_OK && len != 2) res = RES_BAD_ARG;
+ if(res != RES_OK) {
+ fprintf(stderr, "Invalid direction `%s'.\n", str);
+ goto error;
+ }
+
+ if(angles[0] < 0 || angles[0] >= 360) {
+ fprintf(stderr,
+ "Invalid azimuth angle `%g'. Azimuth must be in [0, 360[ degrees.\n",
+ angles[0]);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ if(angles[1] < 0 || angles[1] > 90) {
+ fprintf(stderr,
+ "Invalid elevation angle `%g'. Elevation must be in [0, 90] degrees.\n",
+ angles[1]);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ args->sun_azimuth = angles[0];
+ args->sun_elevation = angles[1];
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_atmosphere_args_init
+ (struct htrdr_atmosphere_args* args,
+ int argc,
+ char** argv)
+{
+ int opt;
+ int i;
+ res_T res = RES_OK;
+ ASSERT(args && argc && argv);
+
+ *args = HTRDR_ATMOSPHERE_ARGS_DEFAULT;
+
+ while((opt = getopt(argc, argv, "a:C:c:D:dfg:hi:M:m:n:O:o:p:Rrs:T:t:V:v")) != -1) {
+ switch(opt) {
+ case 'a': args->filename_gas = optarg; break;
+ case 'C':
+ args->sensor_type = HTRDR_SENSOR_CAMERA;
+ res = htrdr_args_camera_parse(&args->sensor.camera, optarg);
+ break;
+ case 'c': args->filename_les = optarg; break;
+ case 'D': res = parse_sun_dir(args, optarg); break;
+ case 'd': args->dump_volumetric_acceleration_structure = 1; break;
+ case 'f': args->force_overwriting = 1; break;
+ case 'g': args->filename_obj = optarg; break;
+ case 'h':
+ print_help(argv[0]);
+ htrdr_args_release(args);
+ args->quit = 1;
+ goto exit;
+ case 'i':
+ res = htrdr_args_image_parse(&args->image, optarg);
+ break;
+ case 'M': args->filename_mtl = optarg; break;
+ case 'm': args->filename_mie = optarg; break;
+ case 'n': args->sky_mtl_name = optarg; break;
+ case 'O': args->cache = optarg; break;
+ case 'o': args->output = optarg; break;
+ case 'p':
+ args->sensor_type = HTRDR_SENSOR_RECTANGLE;
+ res = htrdr_args_rectangle_parse(&args->sensor.rectangle, optarg);
+ break;
+ case 'r': args->repeat_clouds = 1; break;
+ case 'R': args->repeat_ground = 1; break;
+ case 's':
+ res = htrdr_args_spectral_parse(&args->sensor.spectral, optarg);
+ break;
+ case 'T':
+ res = cstr_to_double(optarg, &args->optical_thickness);
+ if(res == RES_OK && args->optical_thickness < 0) res = RES_BAD_ARG;
+ break;
+ case 't': /* Submit an hint on the number of threads to use */
+ res = cstr_to_uint(optarg, &args->nthreads);
+ if(res == RES_OK && !args->nthreads) res = RES_BAD_ARG;
+ break;
+ case 'V': res = parse_grid_definition(args, optarg); break;
+ case 'v': args->verbose = 1; break;
+ default: res = RES_BAD_ARG; break;
+ }
+ if(res != RES_OK) {
+ if(optarg) {
+ fprintf(stderr, "%s: invalid option argument '%s' -- '%c'\n",
+ argv[0], optarg, opt);
+ }
+ goto error;
+ }
+ }
+ if(!args->filename_gas) {
+ fprintf(stderr,
+ "Missing the path of the gas optical properties file -- option '-a'\n");
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ if(args->filename_obj && !args->filename_mtl) {
+ fprintf(stderr,
+ "Missing the path of the file listing the ground materials -- option '-M'\n");
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ if(args->filename_les && !args->filename_mie) {
+ fprintf(stderr,
+ "Missing the path toward the file of the Mie's data -- option '-m'\n");
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ /* Setup default ref temperature if necessary */
+ if(args->ref_temperature <= 0) {
+ switch(args->spectral_type) {
+ case HTRDR_SPECTRAL_LW:
+ args->ref_temperature = HTRDR_DEFAULT_LW_REF_TEMPERATURE;
+ break;
+ case HTRDR_SPECTRAL_SW:
+ args->ref_temperature = HTRDR_SUN_TEMPERATURE;
+ break;
+ case HTRDR_SPECTRAL_SW_CIE_XYZ:
+ args->ref_temperature = -1; /* Unused */
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+ }
+
+exit:
+ return res;
+error:
+ htrdr_args_release(args);
+ goto exit;
+}
+
diff --git a/src/atmosphere/htrdr_atmosphere_args.h.in b/src/atmosphere/htrdr_atmosphere_args.h.in
@@ -0,0 +1,109 @@
+/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019, 2021 CNRS
+ * Copyright (C) 2018, 2019, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_ATMOSPHERE_ARGS_H
+#define HTRDR_ATMOSPHERE_ARGS_H
+
+#include "htrdr_args.h"
+#include <rsys/rsys.h>
+
+struct htrdr_atmosphere_args {
+ const char* filename_gas; /* Path of the gas file */
+ const char* filename_les; /* Path of the HTCP file */
+ const char* filename_mie; /* Path of the Mie properties */
+ const char* filename_obj; /* Path of the ground geometry */
+ const char* filename_mtl; /* Path of the ground materials */
+
+ const char* filename_cache; /* Path of the file to store/restore cached data */
+ const char* filename_output; /* Name of the output file */
+
+ const char* sky_mtl_name; /* Name of the sky material */
+
+ union {
+ struct htrdr_args_camera camera; /* Pinhole Camera */
+ struct htrdr_args_rectangle rectangle; /* Flux map */
+ } sensor;
+ enum htrdr_sensor_type sensor_type;
+
+ struct htrdr_args_image image; /* Output Image */
+ struct htrdr_args_spectral spectral; /* Spectral domain */
+
+ double sun_azimuth; /* In degrees */
+ double sun_elevation; /* In degrees */
+
+ /* Parameters of the volumetric acceleration data structures */
+ double optical_thickness; /* Threshold used during octree building */
+ unsigned grid_max_definition[3]; /* Maximum definition of the grid */
+
+ int repeat_clouds; /* Make the clouds infinite in X and Y */
+ int repeat_ground; /* Make the ground infinite in X and Y */
+
+ /* Miscellaneous parameters */
+ unsigned nthreads; /* Hint on the number of threads to use */
+ int force_overwriting;
+ int dump_volumetric_acceleration_structure;
+ int verbose; /* Verbosity level */
+ int quit; /* Stop the command */
+};
+
+#define HTRDR_ATMOSPHERE_ARGS_DEFAULT__ { \
+ NULL, /* Gas filename */ \
+ NULL, /* LES filename */ \
+ NULL, /* Mie filename */ \
+ NULL, /* Obj filename */ \
+ NULL, /* Mtl filename */ \
+ \
+ NULL, /* Cache filename */ \
+ NULL, /* Output filename */ \
+ \
+ @HTRDR_ARGS_DEFAULT_SKY_MTL_NAME@, /* Sky mtl name */ \
+ \
+ {HTRDR_ARGS_CAMERA_DEFAULT__}, /* Sensor */ \
+ HTRDR_SENSOR_CAMERA, /* Sensor type */ \
+ \
+ HTRDR_ARGS_IMAGE_DEFAULT__, /* Image */ \
+ HTRDR_ARGS_SPECTRAL_DEFAULT__, /* Spectral */ \
+ \
+ 0, /* Sun azimuth */ \
+ 90, /* Sun elevation */ \
+ \
+ @HTRDR_ARGS_ATMOPSHERE_DEFAULT_OPTICAL_THICKNESS_THRESHOLD@, \
+ {UINT_MAX, UINT_MAX, UINT_MAX}, /* Maximum definition of the grid */ \
+ \
+ 0, /* Repeat clouds */ \
+ 0, /* Repeat ground */ \
+ \
+ UINT_MAX, /* #threads */ \
+ 0, /* Force overwriting */ \
+ 0, /* dump volumetric acceleration structure */ \
+ 0, /* Verbose flag */ \
+ 0 /* Stop the command */ \
+}
+static const struct htrdr_atmosphere_args HTRDR_ATMOSPHERE_ARGS_DEFAULT =
+ HTRDR_ATMOSPHERE_ARGS_DEFAULT__;
+
+extern LOCAL_SYM res_T
+htrdr_atmosphere_args_init
+ (struct htrdr_atmosphere_args* args,
+ int argc,
+ char** argv);
+
+extern LOCAL_SYM res_T
+htrdr_atmosphere_args_release
+ (struct htrdr_atmosphere_args* args);
+
+#endif /* HTRDR_ATMOSPHERE_ARGS_H */
diff --git a/src/atmosphere/htrdr_atmosphere_c.h b/src/atmosphere/htrdr_atmosphere_c.h
@@ -0,0 +1,148 @@
+/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019, 2021 CNRS
+ * Copyright (C) 2018, 2019, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_ATMOSPHERE_C_H
+#define HTRDR_ATMOSPHERE_C_H
+
+#include "htrdr_accum.h"
+
+#include <rsys/ref_count.h>
+#include <rsys/rsys.h>
+#include <rsys/string.h>
+
+/* Define the radiance component */
+enum atmosphere_radiance_cpnt_flag {
+ ATMOSPHERE_RADIANCE_DIRECT = BIT(0),
+ ATMOSPHERE_RADIANCE_DIFFUSE = BIT(1),
+ ATMOSPHERE_RADIANCE_ALL =
+ ATMOSPHERE_RADIANCE_DIRECT
+ | ATMOSPHERE_RADIANCE_DIFFUSE
+};
+
+struct atmosphere_pixel_xwave {
+ struct htrdr_estimate radiance; /* In W/m^2/sr */
+ struct htrdr_estimate radiance_temperature; /* In K */
+ struct htrdr_accum time; /* In microseconds */
+};
+#define ATMOSPHERE_PIXEL_XWAVE_NULL__ { \
+ HTRDR_ESTIMATE_NULL__, /* Radiance */ \
+ HTRDR_ESTIMATE_NULL__, /* Radiance temperature */ \
+ HTRDR_ACCUM_NULL /* Time */ \
+}
+static const struct atmosphere_pixel_xwave ATMOSPHERE_PIXEL_XWAVE_NULL =
+ ATMOSPHERE_PIXEL_XWAVE_NULL__;
+
+struct atmosphere_pixel_flux {
+ struct htrdr_accum flux;
+ struct htrdr_accum time;
+};
+#define ATMOSPHERE_PIXEL_FLUX_NULL__ { \
+ HTRDR_ACCUM_NULL, \
+ HTRDR_ACCUM_NULL \
+}
+static const struct atmosphere_pixel_flux ATMOSPHERE_PIXEL_FLUX_NULL =
+ ATMOSPHERE_PIXEL_FLUX_NULL__;
+
+struct atmosphere_pixel_image {
+ struct htrdr_estimate X; /* In W/m^2/sr */
+ struct htrdr_estimate Y; /* In W/m^2/sr */
+ struct htrdr_estimate Z; /* In W/m^2/sr */
+ struct htrdr_accum time; /* In microseconds */
+};
+#define ATMOSPHERE_PIXEL_IMAGE_NULL__ { \
+ HTRDR_ESTIMATE_NULL__, /* X */ \
+ HTRDR_ESTIMATE_NULL__, /* Y */ \
+ HTRDR_ESTIMATE_NULL__, /* Z */ \
+ HTRDR_ACCUM_NULL /* Time */ \
+}
+static const struct atmosphere_pixel_image ATMOSPHERE_PIXEL_IMAGE_NULL =
+ ATMOSPHERE_PIXEL_IMAGE_NULL__;
+
+/* Forward declarations */
+struct htsky;
+struct htrdr;
+struct htrdr_atmosphere_args;
+struct htrdr_buffer;
+struct htrdr_cie_xyz;
+struct htrdr_materials;
+struct s3d_device;
+
+struct htrdr_atmosphere {
+ struct s3d_device* s3d;
+
+ struct htrdr_atmosphere_ground* ground;
+ struct htrdr_atmosphere_sun* sun;
+ struct htrdr_materials* mats;
+ struct htrdr_cie_xyz* cie;
+ struct htrdr_ran_wlen* ran_wlen;
+
+ struct htrdr_sensor sensor;
+ struct htrdr_buffer* buf;
+
+ struct htsky* sky;
+ const char* sky_mtl_name;
+ enum htrdr_spectral_type spectral_type;
+ double wlen_range_m[2]; /* Integration range in *meters* */
+ double ref_temperature; /* Reference temperature in Kelvin */
+
+ size_t spp; /* #samples per pixel */
+ size_t width; /* Image width */
+ size_t height; /* Image height */
+
+ FILE* output;
+ struct str output_name;
+
+ unsigned grid_max_definition[3]; /* Max definition of the acceleration grids */
+ unsigned nthreads; /* #threads of the process */
+ int dump_volumetric_acceleration_structure; /* Dump octrees */
+ int verbose; /* Verbosity level */
+
+ ref_T ref;
+ struct htrdr* htrdr;
+};
+
+extern LOCAL_SYM res_T
+atmosphere_draw_map
+ (struct htrdr_atmosphere* cmd);
+
+/* Return the shortwave radiance in W/m^2/sr/m */
+extern LOCAL_SYM res_T
+atmosphere_compute_radiance_sw
+ (struct htrdr_atmosphere* cmd,
+ const size_t ithread,
+ struct ssp_rng* rng,
+ const int cpnt_mask, /* Combination of enum atmosphere_radiance_cpnt_flag */
+ const double pos_in[3],
+ const double dir_in[3],
+ const double wlen, /* In nanometer */
+ const size_t iband,
+ const size_t iquad);
+
+/* Return the longwave radiance in W/m^2/sr/m */
+extern LOCAL_SYM res_T
+atmosphere_compute_radiance_lw
+ (struct htrdr_atmosphere* cmd,
+ const size_t ithread,
+ struct ssp_rng* rng,
+ const double pos_in[3],
+ const double dir_in[3],
+ const double wlen, /* In nanometer */
+ const size_t iband,
+ const size_t iquad);
+
+#endif /* HTRDR_ATMOSPHERE_C_H */
+
diff --git a/src/htrdr_compute_radiance_lw.c b/src/atmosphere/htrdr_atmosphere_compute_radiance_lw.c
diff --git a/src/atmosphere/htrdr_atmosphere_compute_radiance_sw.c b/src/atmosphere/htrdr_atmosphere_compute_radiance_sw.c
@@ -0,0 +1,504 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr.h"
+#include "htrdr_c.h"
+#include "htrdr_interface.h"
+#include "htrdr_ground.h"
+#include "htrdr_solve.h"
+#include "htrdr_sun.h"
+
+#include <high_tune/htsky.h>
+
+#include <star/s3d.h>
+#include <star/ssf.h>
+#include <star/ssp.h>
+#include <star/svx.h>
+
+#include <rsys/double2.h>
+#include <rsys/float2.h>
+#include <rsys/float3.h>
+
+struct scattering_context {
+ struct ssp_rng* rng;
+ const struct htsky* sky;
+ size_t iband; /* Index of the spectral band */
+ size_t iquad; /* Index of the quadrature point into the band */
+
+ double Ts; /* Sampled optical thickness */
+ double traversal_dst; /* Distance traversed along the ray */
+};
+static const struct scattering_context SCATTERING_CONTEXT_NULL = {
+ NULL, NULL, 0, 0, 0, 0
+};
+
+struct transmissivity_context {
+ struct ssp_rng* rng;
+ const struct htsky* sky;
+ size_t iband; /* Index of the spectral */
+ size_t iquad; /* Index of the quadrature point into the band */
+
+ double Ts; /* Sampled optical thickness */
+ double Tmin; /* Minimal optical thickness */
+ double traversal_dst; /* Distance traversed along the ray */
+
+ enum htsky_property prop;
+};
+static const struct transmissivity_context TRANSMISSION_CONTEXT_NULL = {
+ NULL, NULL, 0, 0, 0, 0, 0, 0
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static int
+scattering_hit_filter
+ (const struct svx_hit* hit,
+ const double org[3],
+ const double dir[3],
+ const double range[2],
+ void* context)
+{
+ struct scattering_context* ctx = context;
+ double ks_max;
+ int pursue_traversal = 1;
+ ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range);
+ (void)range;
+
+ ks_max = htsky_fetch_svx_voxel_property(ctx->sky, HTSKY_Ks,
+ HTSKY_SVX_MAX, HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, &hit->voxel);
+
+ ctx->traversal_dst = hit->distance[0];
+
+ /* Iterate until a collision occurs into the voxel or until the ray
+ * does not collide the voxel */
+ for(;;) {
+ /* Compute tau for the current leaf */
+ const double vox_dst = hit->distance[1] - ctx->traversal_dst;
+ const double T = vox_dst * ks_max;
+
+ /* A collision occurs behind `vox_dst' */
+ if(ctx->Ts > T) {
+ ctx->Ts -= T;
+ ctx->traversal_dst = hit->distance[1];
+ pursue_traversal = 1;
+ break;
+
+ /* A real/null collision occurs before `vox_dst' */
+ } else {
+ double pos[3];
+ double proba;
+ double ks;
+ const double collision_dst = ctx->Ts / ks_max;
+
+ /* Compute the traversed distance up to the challenged collision */
+ ctx->traversal_dst += collision_dst;
+ ASSERT(ctx->traversal_dst >= hit->distance[0]);
+ ASSERT(ctx->traversal_dst <= hit->distance[1]);
+
+ /* Stop the ray whenever the traversal distance without any scattering
+ * event is too high. It means the maximum scattering coefficient has a
+ * very small value, and the returned radiance is null. This can only
+ * happen when the voxel has a [quasi] infinite length in the propagation
+ * direction. */
+ if(ctx->traversal_dst > 1e9) break;
+
+ /* Compute the world space position where a collision may occur */
+ pos[0] = org[0] + ctx->traversal_dst * dir[0];
+ pos[1] = org[1] + ctx->traversal_dst * dir[1];
+ pos[2] = org[2] + ctx->traversal_dst * dir[2];
+
+ ks = htsky_fetch_raw_property(ctx->sky, HTSKY_Ks,
+ HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX);
+
+ /* Handle the case that ks_max is not *really* the max */
+ proba = ks / ks_max;
+
+ if(ssp_rng_canonical(ctx->rng) < proba) {/* Collide <=> real scattering */
+ pursue_traversal = 0;
+ break;
+ } else { /* Null collision */
+ ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */
+ }
+ }
+ }
+ return pursue_traversal;
+}
+
+static int
+transmissivity_hit_filter
+ (const struct svx_hit* hit,
+ const double org[3],
+ const double dir[3],
+ const double range[2],
+ void* context)
+{
+ struct transmissivity_context* ctx = context;
+ int comp_mask = HTSKY_CPNT_MASK_ALL;
+ double k_max;
+ double k_min;
+ int pursue_traversal = 1;
+ ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range);
+ (void)range;
+
+ k_min = htsky_fetch_svx_voxel_property(ctx->sky, ctx->prop,
+ HTSKY_SVX_MIN, comp_mask, ctx->iband, ctx->iquad, &hit->voxel);
+ k_max = htsky_fetch_svx_voxel_property(ctx->sky, ctx->prop,
+ HTSKY_SVX_MAX, comp_mask, ctx->iband, ctx->iquad, &hit->voxel);
+ ASSERT(k_min <= k_max);
+
+ ctx->Tmin += (hit->distance[1] - hit->distance[0]) * k_min;
+ ctx->traversal_dst = hit->distance[0];
+
+ /* Iterate until a collision occurs into the voxel or until the ray
+ * does not collide the voxel */
+ for(;;) {
+ const double vox_dst = hit->distance[1] - ctx->traversal_dst;
+ const double Tdif = vox_dst * (k_max-k_min);
+
+ /* A collision occurs behind `vox_dst' */
+ if(ctx->Ts > Tdif) {
+ ctx->Ts -= Tdif;
+ ctx->traversal_dst = hit->distance[1];
+ pursue_traversal = 1;
+ break;
+
+ /* A real/null collision occurs before `vox_dst' */
+ } else {
+ double x[3];
+ double k;
+ double proba;
+ double collision_dst = ctx->Ts / (k_max - k_min);
+
+ /* Compute the traversed distance up to the challenged collision */
+ ctx->traversal_dst += collision_dst;
+ ASSERT(ctx->traversal_dst >= hit->distance[0]);
+ ASSERT(ctx->traversal_dst <= hit->distance[1]);
+
+ /* Compute the world space position where a collision may occur */
+ x[0] = org[0] + ctx->traversal_dst * dir[0];
+ x[1] = org[1] + ctx->traversal_dst * dir[1];
+ x[2] = org[2] + ctx->traversal_dst * dir[2];
+
+ k = htsky_fetch_raw_property(ctx->sky, ctx->prop,
+ comp_mask, ctx->iband, ctx->iquad, x, k_min, k_max);
+ ASSERT(k >= k_min && k <= k_max);
+
+ proba = (k - k_min) / (k_max - k_min);
+
+ if(ssp_rng_canonical(ctx->rng) < proba) { /* Collide */
+ pursue_traversal = 0;
+ break;
+ } else { /* Null collision */
+ ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */
+ }
+ }
+ }
+ return pursue_traversal;
+}
+
+static double
+transmissivity
+ (struct htrdr* htrdr,
+ struct ssp_rng* rng,
+ const enum htsky_property prop,
+ const size_t iband,
+ const size_t iquad,
+ const double pos[3],
+ const double dir[3],
+ const double range[2])
+{
+ struct svx_hit svx_hit;
+ struct transmissivity_context transmissivity_ctx = TRANSMISSION_CONTEXT_NULL;
+
+ ASSERT(htrdr && rng && pos && dir && range);
+
+ transmissivity_ctx.rng = rng;
+ transmissivity_ctx.sky = htrdr->sky;
+ transmissivity_ctx.iband = iband;
+ transmissivity_ctx.iquad = iquad;
+ transmissivity_ctx.Ts = ssp_ran_exp(rng, 1); /* Sample an optical thickness */
+ transmissivity_ctx.prop = prop;
+
+ /* Compute the transmissivity */
+ HTSKY(trace_ray(htrdr->sky, pos, dir, range, NULL,
+ transmissivity_hit_filter, &transmissivity_ctx, iband, iquad, &svx_hit));
+
+ if(SVX_HIT_NONE(&svx_hit)) {
+ return transmissivity_ctx.Tmin ? exp(-transmissivity_ctx.Tmin) : 1.0;
+ } else {
+ return 0;
+ }
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+double
+atmosphere_compute_radiance_sw
+ (struct htrdr_atmosphere* cmd,
+ const size_t ithread,
+ struct ssp_rng* rng,
+ const int cpnt_mask, /* Combination of enum htrdr_radiance_cpnt_flag */
+ const double pos_in[3],
+ const double dir_in[3],
+ const double wlen, /* In nanometer */
+ const size_t iband,
+ const size_t iquad)
+{
+ struct s3d_hit s3d_hit = S3D_HIT_NULL;
+ struct s3d_hit s3d_hit_tmp = S3D_HIT_NULL;
+ struct s3d_hit s3d_hit_prev = S3D_HIT_NULL;
+ struct svx_hit svx_hit = SVX_HIT_NULL;
+ struct ssf_phase* phase_hg = NULL;
+ struct ssf_phase* phase_rayleigh = NULL;
+
+ double pos[3];
+ double dir[3];
+ double range[2];
+ double pos_next[3];
+ double dir_next[3];
+ double band_bounds[2]; /* In nanometers */
+
+ double R;
+ double r; /* Random number */
+ double wo[3]; /* -dir */
+ double pdf;
+ double Tr; /* Overall transmissivity */
+ double Tr_abs; /* Absorption transmissivity */
+ double L_sun; /* Sun radiance in W.m^-2.sr^-1 */
+ double sun_dir[3];
+ double ksi = 1; /* Throughput */
+ double w = 0; /* MC weight */
+ double g = 0; /* Asymmetry parameter of the HG phase function */
+
+ ASSERT(cmd && rng && pos_in && dir_in);
+ ASSERT(cmd->spectral_type == HTRDR_SPECTRAL_SW
+ || cmd->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ);
+
+ /* Create the Henyey-Greenstein phase function */
+ CHK(RES_OK == ssf_phase_create
+ (htrdr_get_lifo_allocator(cmd->htrdr, ithread),
+ &ssf_phase_hg,
+ &phase_hg));
+
+ /* Create the Rayleigh phase function */
+ CHK(RES_OK == ssf_phase_create
+ (htrdr_get_lifo_allocator(cmd->htrdr, ithread),
+ &ssf_phase_rayleigh,
+ &phase_rayleigh));
+
+ /* Setup the phase function for this wavelength */
+ g = htsky_fetch_per_wavelength_particle_phase_function_asymmetry_parameter
+ (cmd->sky, wlen);
+ SSF(phase_hg_setup(phase_hg, g));
+
+ /* Fetch sun properties. Note that the sun spectral data are defined by bands
+ * that, actually are the same of the SW spectral bands defined in the
+ * default "ecrad_opt_prot.txt" file provided by the HTGOP project. */
+ htsky_get_spectral_band_bounds(cmd->sky, iband, band_bounds);
+ ASSERT(band_bounds[0] <= wlen && wlen <= band_bounds[1]);
+ L_sun = htrdr_atmosphere_sun_get_radiance(cmd->sun, wlen);
+ d3_set(pos, pos_in);
+ d3_set(dir, dir_in);
+
+ if((cpnt_mask & HTRDR_RADIANCE_DIRECT) /* Handle direct contribation */
+ && htrdr_atmosphere_sun_is_dir_in_solar_cone(cmd->sun, dir)) {
+ /* Check that the ray is not occluded along the submitted range */
+ d2(range, 0, FLT_MAX);
+ HTRDR(ground_trace_ray(cmd->ground, pos, dir, range, NULL, &s3d_hit_tmp));
+ if(!S3D_HIT_NONE(&s3d_hit_tmp)) {
+ Tr = 0;
+ } else {
+ Tr = transmissivity
+ (cmd, rng, HTSKY_Kext, iband, iquad , pos, dir, range);
+ w = L_sun * Tr;
+ }
+ }
+
+ if((cpnt_mask & HTRDR_RADIANCE_DIFFUSE) == 0)
+ goto exit; /* Discard diffuse contribution */
+
+ /* Radiative random walk */
+ for(;;) {
+ struct scattering_context scattering_ctx = SCATTERING_CONTEXT_NULL;
+ struct ssf_bsdf* bsdf = NULL;
+ struct ssf_phase* phase;
+ double N[3];
+ double bounce_reflectivity = 1;
+ double sun_dir_pdf;
+ int surface_scattering = 0; /* Define if hit a surface */
+ int bsdf_type = 0;
+
+ /* Find the first intersection with a surface */
+ d2(range, 0, DBL_MAX);
+ HTRDR(ground_trace_ray
+ (cmd->ground, pos, dir, range, &s3d_hit_prev, &s3d_hit));
+
+ /* Sample an optical thickness */
+ scattering_ctx.Ts = ssp_ran_exp(rng, 1);
+
+ /* Setup the remaining scattering context fields */
+ scattering_ctx.rng = rng;
+ scattering_ctx.sky = cmd->sky;
+ scattering_ctx.iband = iband;
+ scattering_ctx.iquad = iquad;
+
+ /* Define if a scattering event occurs */
+ d2(range, 0, s3d_hit.distance);
+ HTSKY(trace_ray(cmd->sky, pos, dir, range, NULL,
+ scattering_hit_filter, &scattering_ctx, iband, iquad, &svx_hit));
+
+ /* No scattering and no surface reflection. Stop the radiative random walk */
+ if(S3D_HIT_NONE(&s3d_hit) && SVX_HIT_NONE(&svx_hit)) {
+ break;
+ }
+ ASSERT(SVX_HIT_NONE(&svx_hit)
+ || ( svx_hit.distance[0] <= scattering_ctx.traversal_dst
+ && svx_hit.distance[1] >= scattering_ctx.traversal_dst));
+
+ /* Negate the incoming dir to match the convention of the SSF library */
+ d3_minus(wo, dir);
+
+ /* Define if the scattering occurs at a surface */
+ surface_scattering = SVX_HIT_NONE(&svx_hit);
+
+ /* Compute the new position */
+ pos_next[0] = pos[0] + dir[0]*scattering_ctx.traversal_dst;
+ pos_next[1] = pos[1] + dir[1]*scattering_ctx.traversal_dst;
+ pos_next[2] = pos[2] + dir[2]*scattering_ctx.traversal_dst;
+
+ /* Define the previous hit surface used to avoid self hit */
+ s3d_hit_prev = surface_scattering ? s3d_hit : S3D_HIT_NULL;
+
+ /* Define the absorption transmissivity from the current position to the
+ * next position */
+ d2(range, 0, scattering_ctx.traversal_dst);
+ Tr_abs = transmissivity
+ (cmd, rng, HTSKY_Ka, iband, iquad, pos, dir, range);
+ if(Tr_abs <= 0) break;
+
+ /* Sample the scattering direction */
+ if(surface_scattering) { /* Scattering at a surface */
+ struct htrdr_interface interf = HTRDR_INTERFACE_NULL;
+ const struct htrdr_mtl* mtl = NULL;
+
+ /* Fetch the hit interface materal and build its BSDF */
+ htrdr_ground_get_interface(cmd->ground, &s3d_hit, &interf);
+ mtl = htrdr_interface_fetch_hit_mtl(&interf, dir, &s3d_hit);
+ HTRDR(mtl_create_bsdf(cmd, mtl, ithread, wlen, rng, &bsdf));
+
+ /* Revert the normal if necessary to match the SSF convention */
+ d3_normalize(N, d3_set_f3(N, s3d_hit.normal));
+ if(d3_dot(N, wo) < 0) d3_minus(N, N);
+
+ /* Sample scattering direction */
+ bounce_reflectivity = ssf_bsdf_sample
+ (bsdf, rng, wo, N, dir_next, &bsdf_type, &pdf);
+ if(!(bsdf_type & SSF_REFLECTION)) { /* Handle only reflections */
+ bounce_reflectivity = 0;
+ }
+
+ } else { /* Scattering in a volume */
+ double ks_particle; /* Scattering coefficient of the particles */
+ double ks_gas; /* Scattering coefficient of the gaz */
+ double ks; /* Overall scattering coefficient */
+
+ ks_gas = htsky_fetch_raw_property(cmd->sky, HTSKY_Ks,
+ HTSKY_CPNT_FLAG_GAS, iband, iquad, pos_next, -DBL_MAX, DBL_MAX);
+ ks_particle = htsky_fetch_raw_property(cmd->sky, HTSKY_Ks,
+ HTSKY_CPNT_FLAG_PARTICLES, iband, iquad, pos_next, -DBL_MAX, DBL_MAX);
+ ks = ks_particle + ks_gas;
+
+ r = ssp_rng_canonical(rng);
+ if(r < ks_gas / ks) { /* Gas scattering */
+ phase = phase_rayleigh;
+ } else { /* Cloud scattering */
+ phase = phase_hg;
+ }
+
+ /* Sample scattering direction */
+ ssf_phase_sample(phase, rng, wo, dir_next, NULL);
+ ssf_phase_ref_get(phase);
+ }
+
+ /* Sample the direction of the direct contribution */
+ if(surface_scattering && (bsdf_type & SSF_SPECULAR)) {
+ if(!htrdr_atmosphere_sun_is_dir_in_solar_cone(cmd->sun, dir_next)) {
+ R = 0; /* No direct lightning */
+ } else {
+ sun_dir[0] = dir_next[0];
+ sun_dir[1] = dir_next[1];
+ sun_dir[2] = dir_next[2];
+ R = d3_dot(N, sun_dir)<0/* Below the ground*/ ? 0 : bounce_reflectivity;
+ }
+ sun_dir_pdf = 1.0;
+ } else {
+ /* Sample a sun direction */
+ sun_dir_pdf = htrdr_sun_sample_direction(cmd->sun, rng, sun_dir);
+ if(surface_scattering) {
+ R = d3_dot(N, sun_dir) < 0/* Below the ground */
+ ? 0 : ssf_bsdf_eval(bsdf, wo, N, sun_dir) * d3_dot(N, sun_dir);
+ } else {
+ R = ssf_phase_eval(phase, wo, sun_dir);
+ }
+ }
+
+ /* The direct contribution to the scattering point is not null so we need
+ * to compute the transmissivity from sun to scatt pt */
+ if(R <= 0) {
+ Tr = 0;
+ } else {
+ /* Check that the sun is visible from the new position */
+ d2(range, 0, FLT_MAX);
+ HTRDR(ground_trace_ray
+ (cmd->ground, pos_next, sun_dir, range, &s3d_hit_prev, &s3d_hit_tmp));
+
+ /* Compute the sun transmissivity */
+ if(!S3D_HIT_NONE(&s3d_hit_tmp)) {
+ Tr = 0;
+ } else {
+ Tr = transmissivity
+ (cmd, rng, HTSKY_Kext, iband, iquad, pos_next, sun_dir, range);
+ }
+ }
+
+ /* Release the scattering function */
+ if(surface_scattering) {
+ SSF(bsdf_ref_put(bsdf));
+ } else {
+ SSF(phase_ref_put(phase));
+ }
+
+ /* Update the MC weight */
+ ksi *= Tr_abs;
+ w += ksi * L_sun * Tr * R / sun_dir_pdf;
+
+ /* Russian roulette wrt surface scattering */
+ if(surface_scattering && ssp_rng_canonical(rng) >= bounce_reflectivity)
+ break;
+
+ /* Setup the next random walk state */
+ d3_set(pos, pos_next);
+ d3_set(dir, dir_next);
+ }
+
+exit:
+ SSF(phase_ref_put(phase_hg));
+ SSF(phase_ref_put(phase_rayleigh));
+ return w;
+}
+
diff --git a/src/atmosphere/htrdr_atmosphere_draw_map.c b/src/atmosphere/htrdr_atmosphere_draw_map.c
@@ -0,0 +1,492 @@
+/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019, 2021 CNRS
+ * Copyright (C) 2018, 2019, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr_draw_map.h"
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static res_T
+sample_rectangle_ray
+ (struct htrdr_atmosphere* cmd,
+ struct htrdr_rectangle* rect,
+ const size_t ipix[2],
+ const double pix_sz[2],
+ struct ssp_rng* rng,
+ double ray_org[3],
+ double ray_dir[3])
+{
+ struct s3d_hit hit = S3D_HIT_NULL;
+ double pix_samp[2];
+ const double up_dir[3] = {0,0,1};
+ const double range[2] = {0, DBL_MAX};
+ double normal[3];
+ ASSERT(cmd && rect && ipix && pix_sz && rng && ray_org && ray_dir);
+
+ /* Sample a position into the pixel, in the normalized image plane */
+ pix_samp[0] = ((double)ipix[0] + ssp_rng_canonical(rng)) * pix_sz[0];
+ pix_samp[1] = ((double)ipix[1] + ssp_rng_canonical(rng)) * pix_sz[1];
+
+ /* Retrieve the world space position of pix_samp */
+ htrdr_rectangle_sample_pos(rect, pix_samp, ray_org);
+
+ /* Check that `ray_org' is not included into a geometry */
+ HTRDR(ground_trace_ray(cmd->ground, ray_org, up_dir, range, NULL, &hit));
+
+ /* Up direction is occluded. Check if the sample must be rejected, i.e. does it
+ * lies inside a geometry? */
+ if(!S3D_HIT_NONE(&hit)) {
+ struct htrdr_interface interf = HTRDR_INTERFACE_NULL;
+ const struct htrdr_mtl* mtl = NULL;
+ float N[3]; /* Normalized normal of the hit */
+ float wi[3];
+ float cos_wi_N;
+
+ /* Compute the cosine between the up direction and the hit normal */
+ f3_set_d3(wi, up_dir);
+ f3_normalize(N, hit.normal);
+ cos_wi_N = f3_dot(wi, N);
+
+ /* Fetch the hit interface and retrieve the material into which the ray was
+ * traced */
+ htrdr_ground_get_interface(cmd->ground, &hit, &interf);
+ mtl = cos_wi_N < 0 ? &interf.mtl_front : &interf.mtl_back;
+
+ /* Reject the sample if the incident direction do not travel into the sky */
+ if(strcmp(mtl->name, cmd->sky_mtl_name) != 0) return RES_BAD_OP;
+ }
+
+ /* Sample a ray direction */
+ htrdr_rectangle_get_normal(rect, normal);
+ ssp_ran_hemisphere_cos(rng, normal, ray_dir, NULL);
+
+ return RES_OK;
+}
+
+static void
+draw_pixel_image
+ (struct htrdr* htrdr,
+ struct htrdr_draw_pixel_args* args,
+ void* data)
+{
+ struct htrdr_accum XYZ[3]; /* X, Y, and Z */
+ struct htrdr_accum time;
+ struct htrdr_atmosphere* cmd;
+ struct htrdr_pixel_image* pixel = data;
+ size_t ichannel;
+ ASSERT(htrdr && htrdr_draw_pixel_args_check(args) && data);
+
+ cmd = args->context;
+ ASSERT(cmd);
+ ASSERT(cmd->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ);
+ ASSERT(cmd->sensor.type == HTRDR_SENSOR_CAMERA);
+
+ /* Reset accumulators */
+ XYZ[0] = HTRDR_ACCUM_NULL;
+ XYZ[1] = HTRDR_ACCUM_NULL;
+ XYZ[2] = HTRDR_ACCUM_NULL;
+ time = HTRDR_ACCUM_NULL;
+
+ FOR_EACH(ichannel, 0, 3) {
+ size_t isamp;
+
+ FOR_EACH(isamp, 0, args->spp) {
+ struct time t0, t1;
+ double pix_samp[2];
+ double ray_org[3];
+ double ray_dir[3];
+ double weight;
+ double r0, r1, r2;
+ double wlen; /* Sampled wavelength into the spectral band */
+ double pdf;
+ size_t iband; /* Sampled spectral band */
+ size_t iquad; /* Sampled quadrature point into the spectral band */
+ double usec;
+
+ /* Begin the registration of the time spent to in the realisation */
+ time_current(&t0);
+
+ /* Sample a position into the pixel, in the normalized image plane */
+ pix_samp[0] = (double)args->pixel_coord[0] + ssp_rng_canonical(args->rng);
+ pix_samp[1] = (double)args->pixel_coord[1] + ssp_rng_canonical(args->rng);
+ pix_samp[0] *= args->pixel_normalized_size[0];
+ pix_samp[1] *= args->pixel_normalized_size[1];
+
+ /* Generate a ray starting from the pinhole camera and passing through the
+ * pixel sample */
+ htrdr_camera_ray(cmd->sensor.camera, pix_samp, ray_org, ray_dir);
+
+ r0 = ssp_rng_canonical(args->rng);
+ r1 = ssp_rng_canonical(args->rng);
+ r2 = ssp_rng_canonical(args->rng);
+
+ /* Sample a spectral band and a quadrature point */
+ switch(ichannel) {
+ case 0: wlen = htrdr_cie_xyz_sample_X(cmd->cie, r0, r1, &pdf); break;
+ case 1: wlen = htrdr_cie_xyz_sample_Y(cmd->cie, r0, r1, &pdf); break;
+ case 2: wlen = htrdr_cie_xyz_sample_Z(cmd->cie, r0, r1, &pdf); break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+
+ iband = htsky_find_spectral_band(cmd->sky, wlen);
+ iquad = htsky_spectral_band_sample_quadrature(cmd->sky, r2, iband);
+
+ /* Compute the radiance in W/m^2/sr/m */
+ weight = atmosphere_compute_radiance_sw(cmd, args->ithread, args->rng,
+ HTRDR_RADIANCE_ALL, ray_org, ray_dir, wlen, iband, iquad);
+ ASSERT(weight >= 0);
+
+ pdf *= 1.e9; /* Transform the pdf from nm^-1 to m^-1 */
+ weight /= pdf; /* In W/m^2/sr */
+
+ /* End the registration of the per realisation time */
+ time_sub(&t0, time_current(&t1), &t0);
+ usec = (double)time_val(&t0, TIME_NSEC) * 0.001;
+
+ /* Update the pixel accumulator of the current channel */
+ XYZ[ichannel].sum_weights += weight;
+ XYZ[ichannel].sum_weights_sqr += weight*weight;
+ XYZ[ichannel].nweights += 1;
+
+ /* Update the pixel accumulator of per realisation time */
+ time.sum_weights += usec;
+ time.sum_weights_sqr += usec*usec;
+ time.nweights += 1;
+ }
+ }
+
+ /* Flush pixel data */
+ htrdr_accum_get_estimation(XYZ+0, &pixel->X);
+ htrdr_accum_get_estimation(XYZ+1, &pixel->Y);
+ htrdr_accum_get_estimation(XYZ+2, &pixel->Z);
+ pixel->time = time;
+}
+
+static void
+draw_pixel_flux
+ (struct htrdr* htrdr,
+ const struct htrdr_draw_pixel_args* args,
+ void* data)
+{
+ struct htrdr_accum flux;
+ struct htrdr_accum time;
+ struct htrdr_atmosphere* cmd;
+ struct htrdr_pixel_flux* pixel = data;
+ size_t isamp;
+ ASSERT(htrdr && htrdr_draw_pixel_args_check(args) && data);
+
+ cmd = args->context;
+ ASSERT(cmd);
+ ASSERT(cmd->sensor.type == HTRDR_SENSOR_RECTANGLE);
+ ASSERT(cmd->spectral_type == HTRDR_SPECTRAL_LW
+ || cmd->spectral_type == HTRDR_SPECTRAL_SW);
+
+ /* Reset the pixel accumulators */
+ flux = HTRDR_ACCUM_NULL;
+ time = HTRDR_ACCUM_NULL;
+
+ FOR_EACH(isamp, 0, spp) {
+ struct time t0, t1;
+ double ray_org[3];
+ double ray_dir[3];
+ double weight;
+ double r0, r1, r2;
+ double wlen;
+ size_t iband;
+ size_t iquad;
+ double usec;
+ double band_pdf;
+ res_T res = RES_OK;
+
+ /* Begin the registration of the time spent in the realisation */
+ time_current(&t0);
+
+ res = sample_rectangle_ray(cmd, htrdr->sensor.rectangle, args->pixel_coord,
+ args->pixel_normalized_size, args->rng, ray_org, ray_dir);
+ if(res != RES_OK) continue; /* Reject the current sample */
+
+ r0 = ssp_rng_canonical(rng);
+ r1 = ssp_rng_canonical(rng);
+ r2 = ssp_rng_canonical(rng);
+
+ /* Sample a wavelength */
+ wlen = htrdr_ran_wlen_sample(cmd->ran_wlen, r0, r1, &band_pdf);
+
+ /* Select the associated band and sample a quadrature point */
+ iband = htsky_find_spectral_band(cmd->sky, wlen);
+ iquad = htsky_spectral_band_sample_quadrature(cmd->sky, r2, iband);
+
+ if(cmd->spectral_type == HTRDR_SPECTRAL_LW) {
+ weight = atmosphere_compute_radiance_lw(cmd, args->ithread, args->rng,
+ ray_org, ray_dir, wlen, iband, iquad);
+ weight *= PI / band_pdf; /* Transform weight from W/m^2/sr/m to W/m^2 */
+ } else {
+ double sun_dir[3];
+ double N[3];
+ double L_direct;
+ double L_diffuse;
+ double cos_N_sun_dir;
+ double sun_solid_angle;
+ ASSERT(cmd->spectral_type == HTRDR_SPECTRAL_SW);
+
+ /* Compute direct contribution if necessary */
+ htrdr_sun_sample_direction(cmd->sun, rng, sun_dir);
+ htrdr_rectangle_get_normal(cmd->sensor.rectangle, N);
+ cos_N_sun_dir = d3_dot(N, sun_dir);
+
+ if(cos_N_sun_dir <= 0) {
+ L_direct = 0;
+ } else {
+ L_direct = atmosphere_compute_radiance_sw(cmd, args->ithread,
+ args->rng, HTRDR_RADIANCE_DIRECT, ray_org, sun_dir, wlen, iband,
+ iquad);
+ }
+
+ /* Compute diffuse contribution */
+ L_diffuse = atmosphere_compute_radiance_sw(cmd, args->ithread, args->rng,
+ HTRDR_RADIANCE_DIFFUSE, ray_org, ray_dir, wlen, iband, iquad);
+
+ sun_solid_angle = htrdr_sun_get_solid_angle(cmd->sun);
+
+ /* Compute the weight in W/m^2/m */
+ weight = cos_N_sun_dir * sun_solid_angle * L_direct + PI * L_diffuse;
+
+ /* Importance sampling: correct weight with pdf */
+ weight /= band_pdf; /* In W/m^2 */
+ }
+
+ /* End the registration of the per realisation time */
+ time_sub(&t0, time_current(&t1), &t0);
+ usec = (double)time_val(&t0, TIME_NSEC) * 0.001;
+
+ /* Update the pixel accumulator of the flux */
+ flux.sum_weights += weight;
+ flux.sum_weights_sqr += weight*weight;
+ flux.nweights += 1;
+
+ /* Update the pixel accumulator of per realisation time */
+ time.sum_weights += usec;
+ time.sum_weights_sqr += usec*usec;
+ time.nweights += 1;
+ }
+
+ /* Save the per realisation integration time */
+ pixel->flux = flux;
+ pixel->time = time;
+}
+
+static void
+draw_pixel_xwave
+ (struct htrdr* htrdr,
+ const struct htrdr_draw_pixel_args* args,
+ void* data)
+{
+ struct htrdr_accum radiance;
+ struct htrdr_accum time;
+ struct htrdr_pixel_xwave* pixel = data;
+ size_t isamp;
+ double temp_min, temp_max;
+ ASSERT(htrdr && htrdr_draw_pixel_args_check(args) && data);
+
+ cmd = args->context;
+ ASSERT(cmd->sensor.type == HTRDR_SENSOR_CAMERA);
+ ASSERT(cmd->spectral_type == HTRDR_SPECTRAL_LW
+ || cmd->spectral_type == HTRDR_SPECTRAL_SW);
+
+ /* Reset the pixel accumulators */
+ radiance = HTRDR_ACCUM_NULL;
+ time = HTRDR_ACCUM_NULL;
+
+ FOR_EACH(isamp, 0, spp) {
+ struct time t0, t1;
+ double pix_samp[2];
+ double ray_org[3];
+ double ray_dir[3];
+ double weight;
+ double r0, r1, r2;
+ double wlen;
+ size_t iband;
+ size_t iquad;
+ double usec;
+ double band_pdf;
+ res_T res = RES_OK;
+
+ /* Begin the registration of the time spent in the realisation */
+ time_current(&t0);
+
+ /* Sample a position into the pixel, in the normalized image plane */
+ pix_samp[0] = (double)args->pixel_coord[0] + ssp_rng_canonical(args->rng);
+ pix_samp[1] = (double)args->pixel_coord[1] + ssp_rng_canonical(args->rng);
+ pix_samp[0] *= args->pixel_normalized_size[0];
+ pix_samp[1] *= args->pixel_normalized_size[1];
+
+ /* Generate a ray starting from the pinhole camera and passing through the
+ * pixel sample */
+ htrdr_camera_ray(cmd->sensor.camera, pix_samp, ray_org, ray_dir);
+
+ r0 = ssp_rng_canonical(rng);
+ r1 = ssp_rng_canonical(rng);
+ r2 = ssp_rng_canonical(rng);
+
+ /* Sample a wavelength */
+ wlen = htrdr_ran_wlen_sample(cmd->ran_wlen, r0, r1, &band_pdf);
+
+ /* Select the associated band and sample a quadrature point */
+ iband = htsky_find_spectral_band(cmd->sky, wlen);
+ iquad = htsky_spectral_band_sample_quadrature(cmd->sky, r2, iband);
+
+ /* Compute the spectral radiance in W/m^2/sr/m */
+ switch(htrdr->spectral_type) {
+ case HTRDR_SPECTRAL_LW:
+ weight = atmosphere_compute_radiance_lw(cmd, args->ithread, args->rng,
+ ray_org, ray_dir, wlen, iband, iquad);
+ break;
+ case HTRDR_SPECTRAL_SW:
+ weight = atmosphere_compute_radiance_sw(cmd, args->ithread, args->rng,
+ HTRDR_RADIANCE_ALL, ray_org, ray_dir, wlen, iband, iquad);
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+ ASSERT(weight >= 0);
+ /* Importance sampling: correct weight with pdf */
+ weight /= band_pdf; /* In W/m^2/sr */
+
+ /* End the registration of the per realisation time */
+ time_sub(&t0, time_current(&t1), &t0);
+ usec = (double)time_val(&t0, TIME_NSEC) * 0.001;
+
+ /* Update the pixel accumulator of the current channel */
+ radiance.sum_weights += weight;
+ radiance.sum_weights_sqr += weight*weight;
+ radiance.nweights += 1;
+
+ /* Update the pixel accumulator of per realisation time */
+ time.sum_weights += usec;
+ time.sum_weights_sqr += usec*usec;
+ time.nweights += 1;
+ }
+
+ /* Compute the estimation of the pixel radiance */
+ htrdr_accum_get_estimation(&radiance, &pixel->radiance);
+
+ /* Save the per realisation integration time */
+ pixel->time = time;
+
+ /* Compute the brightness_temperature of the pixel and estimate its standard
+ * error if the sources were in the medium (<=> longwave) */
+ if(htrdr->spectral_type == HTRDR_SPECTRAL_LW) {
+ const double wlen_min = cmd->wlen_range_m[0];
+ const double wlen_max = cmd->wlen_range_m[1];
+ pixel->radiance_temperature.E = htrdr_radiance_temperature
+ (cmd->htrdr, wlen_min, wlen_max, pixel->radiance.E);
+ temp_min = htrdr_radiance_temperature
+ (cmd->htrdr, wlen_min, wlen_max, pixel->radiance.E - pixel->radiance.SE);
+ temp_max = htrdr_radiance_temperature
+ (cmd->htrdr, wlen_min, wlen_max, pixel->radiance.E + pixel->radiance.SE);
+ pixel->radiance_temperature.SE = temp_max - temp_min;
+ }
+}
+
+static INLINE void
+setup_draw_map_args_rectangle
+ (const struct htrdr_atmosphere* cmd,
+ struct htrdr_draw_map_args* args)
+{
+ ASSERT(cmd && cmd->sensor.type == HTRDR_SENSOR_RECTANGLE && args);
+ *args = HTRDR_DRAW_MAP_ARGS_NULL;
+ args->draw_pixel = draw_pixel_flux;
+ args->spp = cmd->spp;
+ args->context = cmd;
+}
+
+static INLINE void
+setup_draw_map_args_camera
+ (const struct htrdr_atmosphere* cmd,
+ struct htrdr_draw_map_args* args)
+{
+ ASSERT(cmd && cmd->sensor.type == HTRDR_SENSOR_CAMERA && args);
+
+ *args = HTRDR_DRAW_MAP_ARGS_NULL;
+ args->spp = cmd->spp;
+ args->context = cmd;
+
+ switch(cmd->spectral_type) {
+ case HTRDR_SPECTRAL_LW:
+ case HTRDR_SPECTRAL_SW:
+ args->draw_pixel = draw_pixel_xwave;
+ break;
+ case HTRDR_SPECTRAL_SW_CIE_XYZ:
+ args->draw_pixel = draw_pixel_image;
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+atmosphere_draw_map(struct htrdr_atmosphere* cmd)
+{
+ struct htrdr_draw_map_args args = HTRDR_DRAW_MAP_ARGS_NULL;
+ struct htrdr_accum path_time_acc = HTRDR_ACCUM_NULL;
+ struct htrdr_accum flux_acc = HTRDR_ACCUM_NULL;
+ struct htrdr_estimate path_time;
+ struct htrdr_estimate flux;
+ ASSERT(cmd);
+
+ args.spp = cmd->spp;
+
+ switch(cmd->sensor.type) {
+ case HTRDR_SENSOR_RECTANGLE:
+ setup_draw_map_args_rectangle(cmd, &args);
+ break;
+ case HTRDR_SENSOR_CAMERA:
+ setup_draw_map_args_camera(cmd, &args);
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+
+ res = htrdr_draw_map(cmd->htrdr, &args, cmd->buf);
+ if(res != RES_OK) goto error;
+
+ /* No more to do on non master processes */
+ if(htrdr_mpi_rank(cmd->htrdr) != 0) goto exit;
+
+ /* Write buffer to output */
+ res = dump_buffer(cmd->htrdr, cmd->buf, &path_time_acc, &flux_acc,
+ str_cget(&cmd->output_name), cmd->output);
+ if(res != RES_OK) goto error;
+
+ htrdr_accum_get_estimation(&path_time_acc, &path_time);
+ htrdr_log(cmd->htrdr,
+ "Time per radiative path (in micro seconds): %g +/- %g\n",
+ path_time.E, path_time.SE);
+
+ if(htrdr->sensor.type == HTRDR_SENSOR_RECTANGLE) {
+ htrdr_accum_get_estimation(&flux_acc, &flux);
+ htrdr_log(cmd->htrdr,
+ "Radiative flux density (in W/(external m^2)): %g +/- %g\n",
+ flux.E, flux.SE);
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
diff --git a/src/atmosphere/htrdr_atmosphere_ground.c b/src/atmosphere/htrdr_atmosphere_ground.c
@@ -0,0 +1,757 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define _POSIX_C_SOURCE 200112L /* strtok_r support */
+
+#include "htrdr.h"
+#include "htrdr_interface.h"
+#include "htrdr_atmosphere_ground.h"
+#include "htrdr_materials.h"
+#include "htrdr_slab.h"
+
+#include <aw.h>
+#include <rsys/clock_time.h>
+#include <rsys/cstr.h>
+#include <rsys/dynamic_array_double.h>
+#include <rsys/dynamic_array_size_t.h>
+#include <rsys/double2.h>
+#include <rsys/double3.h>
+#include <rsys/float2.h>
+#include <rsys/float3.h>
+#include <rsys/hash_table.h>
+
+#include <star/s3d.h>
+
+#include <string.h> /* strtok_r */
+
+/* Define the hash table that maps an Obj vertex id to its position into the
+ * vertex buffer */
+#define HTABLE_NAME vertex
+#define HTABLE_KEY size_t /* Obj vertex id */
+#define HTABLE_DATA size_t
+#include <rsys/hash_table.h>
+
+/* Define the hash table that maps the Star-3D shape id to its interface */
+#define HTABLE_NAME interface
+#define HTABLE_KEY unsigned /* Star-3D shape id */
+#define HTABLE_DATA struct htrdr_interface
+#include <rsys/hash_table.h>
+
+struct mesh {
+ const struct darray_double* positions;
+ const struct darray_size_t* indices;
+};
+static const struct mesh MESH_NULL;
+
+struct ray_context {
+ float range[2];
+ struct s3d_hit hit_prev;
+ int id[2];
+};
+#define RAY_CONTEXT_NULL__ {{0,INF}, S3D_HIT_NULL__, {0,0}}
+static const struct ray_context RAY_CONTEXT_NULL = RAY_CONTEXT_NULL__;
+
+struct trace_ground_context {
+ struct s3d_scene_view* view;
+ struct ray_context context;
+ struct s3d_hit* hit;
+};
+static const struct trace_ground_context TRACE_GROUND_CONTEXT_NULL = {
+ NULL, RAY_CONTEXT_NULL__, NULL
+};
+
+struct htrdr_atmosphere_ground {
+ struct s3d_scene_view* view;
+ float lower[3]; /* Ground lower bound */
+ float upper[3]; /* Ground upper bound */
+ int repeat; /* Make the ground infinite in X and Y */
+
+ struct htable_interface interfaces; /* Map a Star3D shape to its interface */
+
+ struct htrdr* htrdr;
+ ref_T ref;
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+/* Check that `hit' roughly lies on an edge. For triangular primitives, a
+ * simple but approximative way is to test that its position have at least one
+ * barycentric coordinate roughly equal to 0 or 1. */
+static FINLINE int
+hit_on_edge(const struct s3d_hit* hit)
+{
+ const float on_edge_eps = 1.e-4f;
+ float w;
+ ASSERT(hit && !S3D_HIT_NONE(hit));
+ w = 1.f - hit->uv[0] - hit->uv[1];
+ return eq_epsf(hit->uv[0], 0.f, on_edge_eps)
+ || eq_epsf(hit->uv[0], 1.f, on_edge_eps)
+ || eq_epsf(hit->uv[1], 0.f, on_edge_eps)
+ || eq_epsf(hit->uv[1], 1.f, on_edge_eps)
+ || eq_epsf(w, 0.f, on_edge_eps)
+ || eq_epsf(w, 1.f, on_edge_eps);
+}
+
+static int
+ground_filter
+ (const struct s3d_hit* hit,
+ const float ray_org[3],
+ const float ray_dir[3],
+ void* ray_data,
+ void* filter_data)
+{
+ const struct ray_context* ray_ctx = ray_data;
+ (void)ray_org, (void)ray_dir, (void)filter_data;
+
+ if(!ray_ctx) return 0;
+
+ if(S3D_PRIMITIVE_EQ(&hit->prim, &ray_ctx->hit_prev.prim)) return 1;
+
+ if(!S3D_HIT_NONE(&ray_ctx->hit_prev) && eq_epsf(hit->distance, 0, 1.e-1f)) {
+ /* If the targeted point is near of the origin, check that it lies on an
+ * edge/vertex shared by the 2 primitives. */
+ return hit_on_edge(&ray_ctx->hit_prev) && hit_on_edge(hit);
+ }
+
+ return hit->distance <= ray_ctx->range[0]
+ || hit->distance >= ray_ctx->range[1];
+}
+
+static INLINE res_T
+trace_ground
+ (const double org[3],
+ const double dir[3],
+ const double range[2],
+ void* context,
+ int* hit)
+{
+ struct trace_ground_context* ctx = context;
+ float ray_org[3];
+ float ray_dir[3];
+ float ray_range[2];
+ res_T res = RES_OK;
+ ASSERT(org && dir && range && context && hit);
+
+ f3_set_d3(ray_org, org);
+ f3_set_d3(ray_dir, dir);
+ f2_set_d2(ray_range, range);
+
+ res = s3d_scene_view_trace_ray
+ (ctx->view, ray_org, ray_dir, ray_range, &ctx->context, ctx->hit);
+ if(res != RES_OK) return res;
+
+ *hit = !S3D_HIT_NONE(ctx->hit);
+ return RES_OK;
+}
+
+static res_T
+parse_shape_interface
+ (struct htrdr* htrdr,
+ const char* name,
+ struct htrdr_interface* interf)
+{
+ struct str str;
+ char* mtl_name0 = NULL;
+ char* mtl_name1 = NULL;
+ char* mtl_name2 = NULL;
+ char* mtl_name_front = NULL;
+ char* mtl_name_thin = NULL;
+ char* mtl_name_back = NULL;
+ char* tk_ctx = NULL;
+ int has_front = 0;
+ int has_thin = 0;
+ int has_back = 0;
+ res_T res = RES_OK;
+ ASSERT(htrdr && name && interf);
+
+ str_init(htrdr->allocator, &str);
+
+ /* Locally copy the string to parse */
+ res = str_set(&str, name);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr,
+ "Could not locally copy the shape material string `%s' -- %s.\n",
+ name, res_to_cstr(res));
+ goto error;
+ }
+
+ /* Reset the interface */
+ memset(interf, 0, sizeof(*interf));
+
+ /* Parse the name of the front/back/thin materials */
+ mtl_name0 = strtok_r(str_get(&str), ":", &tk_ctx);
+ mtl_name1 = strtok_r(NULL, ":", &tk_ctx);
+ mtl_name2 = strtok_r(NULL, ":", &tk_ctx);
+ ASSERT(mtl_name0); /* This can't be NULL */
+ mtl_name_front = mtl_name0;
+ if(mtl_name2) {
+ mtl_name_thin = mtl_name1;
+ mtl_name_back = mtl_name2;
+ } else {
+ mtl_name_thin = NULL;
+ mtl_name_back = mtl_name1;
+ }
+
+ if(!mtl_name_back) {
+ htrdr_log_err(htrdr,
+ "The back material name is missing `%s'.\n", name);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ /* Fetch the interface material if any */
+ if(mtl_name_thin) {
+ has_thin = htrdr_materials_find_mtl
+ (htrdr->mats, mtl_name_thin, &interf->mtl_thin);
+ if(!has_thin) {
+ htrdr_log_err(htrdr,
+ "Invalid interface `%s'. The interface material `%s' is unknown.\n",
+ name, mtl_name_thin);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ }
+
+ /* Fetch the front material */
+ has_front = htrdr_materials_find_mtl
+ (htrdr->mats, mtl_name_front, &interf->mtl_front);
+ if(!has_front) {
+ htrdr_log_err(htrdr,
+ "Invalid interface `%s'. The front material `%s' is unknown.\n",
+ name, mtl_name_front);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ /* Fetch the back material */
+ has_back = htrdr_materials_find_mtl
+ (htrdr->mats, mtl_name_back, &interf->mtl_back);
+ if(!has_back) {
+ htrdr_log_err(htrdr,
+ "Invalid interface `%s'. The back material `%s' is unknown.\n",
+ name, mtl_name_back);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+exit:
+ str_release(&str);
+ return res;
+error:
+ *interf = HTRDR_INTERFACE_NULL;
+ goto exit;
+}
+
+static res_T
+setup_mesh
+ (struct htrdr* htrdr,
+ const char* filename,
+ struct aw_obj* obj,
+ struct aw_obj_named_group* mtl,
+ struct darray_double* positions,
+ struct darray_size_t* indices,
+ struct htable_vertex* vertices) /* Scratch data structure */
+{
+ size_t iface;
+ res_T res = RES_OK;
+ ASSERT(htrdr && filename && obj && mtl && positions && indices && vertices);
+
+ darray_double_clear(positions);
+ darray_size_t_clear(indices);
+ htable_vertex_clear(vertices);
+
+ FOR_EACH(iface, mtl->face_id, mtl->face_id+mtl->faces_count) {
+ struct aw_obj_face face;
+ size_t ivertex;
+
+ AW(obj_get_face(obj, iface, &face));
+ if(face.vertices_count != 3) {
+ htrdr_log_err(htrdr,
+ "The obj `%s' has non-triangulated polygons "
+ "while currently only triangular meshes are supported.\n",
+ filename);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ FOR_EACH(ivertex, 0, face.vertices_count) {
+ struct aw_obj_vertex vertex;
+ size_t id;
+ size_t* pid;
+
+ AW(obj_get_vertex(obj, face.vertex_id + ivertex, &vertex));
+ pid = htable_vertex_find(vertices, &vertex.position_id);
+ if(pid) {
+ id = *pid;
+ } else {
+ struct aw_obj_vertex_data vdata;
+
+ id = darray_double_size_get(positions) / 3;
+
+ res = darray_double_resize(positions, id*3 + 3);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr,
+ "Could not locally copy the vertex position -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ AW(obj_get_vertex_data(obj, &vertex, &vdata));
+ darray_double_data_get(positions)[id*3+0] = vdata.position[0];
+ darray_double_data_get(positions)[id*3+1] = vdata.position[1];
+ darray_double_data_get(positions)[id*3+2] = vdata.position[2];
+
+ res = htable_vertex_set(vertices, &vertex.position_id, &id);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr,
+ "Could not register the vertex position -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+ }
+
+ res = darray_size_t_push_back(indices, &id);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr,
+ "Could not locally copy the face index -- %s\n",
+ res_to_cstr(res));
+ goto error;
+ }
+ }
+ }
+exit:
+ return res;
+error:
+ darray_double_clear(positions);
+ darray_size_t_clear(indices);
+ htable_vertex_clear(vertices);
+ goto exit;
+}
+
+static void
+get_position(const unsigned ivert, float position[3], void* ctx)
+{
+ const struct mesh* mesh = ctx;
+ const double* pos = NULL;
+ ASSERT(mesh);
+ ASSERT(ivert < darray_double_size_get(mesh->positions) / 3);
+ pos = darray_double_cdata_get(mesh->positions) + ivert*3;
+ position[0] = (float)pos[0];
+ position[1] = (float)pos[1];
+ position[2] = (float)pos[2];
+}
+
+static void
+get_indices(const unsigned itri, unsigned indices[3], void* ctx)
+{
+ const struct mesh* mesh = ctx;
+ const size_t* ids = NULL;
+ ASSERT(mesh);
+ ASSERT(itri < darray_size_t_size_get(mesh->indices) / 3);
+ ids = darray_size_t_cdata_get(mesh->indices) + itri*3;
+ indices[0] = (unsigned)ids[0];
+ indices[1] = (unsigned)ids[1];
+ indices[2] = (unsigned)ids[2];
+}
+
+static res_T
+create_s3d_shape
+ (struct htrdr* htrdr,
+ const struct darray_double* positions,
+ const struct darray_size_t* indices,
+ struct s3d_shape** out_shape)
+{
+ struct s3d_shape* shape = NULL;
+ struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL;
+ struct mesh mesh = MESH_NULL;
+ res_T res = RES_OK;
+ ASSERT(htrdr && positions && indices && out_shape);
+ ASSERT(darray_double_size_get(positions) != 0);
+ ASSERT(darray_size_t_size_get(indices) != 0);
+ ASSERT(darray_double_size_get(positions)%3 == 0);
+ ASSERT(darray_size_t_size_get(indices)%3 == 0);
+
+ mesh.positions = positions;
+ mesh.indices = indices;
+
+ res = s3d_shape_create_mesh(htrdr->s3d, &shape);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr, "Error creating a Star-3D shape -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ vdata.usage = S3D_POSITION;
+ vdata.type = S3D_FLOAT3;
+ vdata.get = get_position;
+
+ res = s3d_mesh_setup_indexed_vertices
+ (shape, (unsigned int)(darray_size_t_size_get(indices)/3), get_indices,
+ (unsigned int)(darray_double_size_get(positions)/3), &vdata, 1, &mesh);
+ if(res != RES_OK){
+ htrdr_log_err(htrdr, "Could not setup the Star-3D shape -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ res = s3d_mesh_set_hit_filter_function(shape, ground_filter, NULL);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr,
+ "Could not setup the Star-3D hit filter function of the ground geometry "
+ "-- %s.\n", res_to_cstr(res));
+ goto error;
+ }
+
+exit:
+ *out_shape = shape;
+ return res;
+error:
+ if(shape) {
+ S3D(shape_ref_put(shape));
+ shape = NULL;
+ }
+ goto exit;
+}
+
+static res_T
+setup_ground(struct htrdr_atmosphere_ground* ground, const char* obj_filename)
+{
+ struct aw_obj_desc desc;
+ struct htable_vertex vertices;
+ struct darray_double positions;
+ struct darray_size_t indices;
+ struct aw_obj* obj = NULL;
+ struct s3d_shape* shape = NULL;
+ struct s3d_scene* scene = NULL;
+ size_t iusemtl;
+
+ res_T res = RES_OK;
+ ASSERT(obj_filename);
+
+ htable_vertex_init(ground->htrdr->allocator, &vertices);
+ darray_double_init(ground->htrdr->allocator, &positions);
+ darray_size_t_init(ground->htrdr->allocator, &indices);
+
+ res = aw_obj_create(&ground->htrdr->logger, ground->htrdr->allocator,
+ ground->htrdr->verbose, &obj);
+ if(res != RES_OK) {
+ htrdr_log_err(ground->htrdr, "Could not create the obj loader -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ res = s3d_scene_create(ground->htrdr->s3d, &scene);
+ if(res != RES_OK) {
+ htrdr_log_err(ground->htrdr, "Could not create the Star-3D scene -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ /* Load the geometry data */
+ res = aw_obj_load(obj, obj_filename);
+ if(res != RES_OK) goto error;
+
+ /* Fetch the descriptor of the loaded geometry */
+ AW(obj_get_desc(obj, &desc));
+
+ if(desc.usemtls_count == 0) {
+ htrdr_log_err(ground->htrdr, "The obj `%s' has no material.\n", obj_filename);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ /* Setup the geometry */
+ FOR_EACH(iusemtl, 0, desc.usemtls_count) {
+ struct aw_obj_named_group mtl;
+ struct htrdr_interface interf;
+ unsigned ishape;
+
+ AW(obj_get_mtl(obj, iusemtl , &mtl));
+
+ res = parse_shape_interface(ground->htrdr, mtl.name, &interf);
+ if(res != RES_OK) goto error;
+
+ res = setup_mesh
+ (ground->htrdr, obj_filename, obj, &mtl, &positions, &indices, &vertices);
+ if(res != RES_OK) goto error;
+
+ res = create_s3d_shape(ground->htrdr, &positions, &indices, &shape);
+ if(res != RES_OK) goto error;
+
+ S3D(shape_get_id(shape, &ishape));
+ res = htable_interface_set(&ground->interfaces, &ishape, &interf);
+ if(res != RES_OK) {
+ htrdr_log_err(ground->htrdr,
+ "Could not map the Star-3D shape to its Star-Materials -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ res = s3d_scene_attach_shape(scene, shape);
+ if(res != RES_OK) {
+ htrdr_log_err(ground->htrdr,
+ "Could not attach a Star-3D shape to the Star-3D scene -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ S3D(shape_ref_put(shape));
+ shape = NULL;
+ }
+
+ res = s3d_scene_view_create(scene, S3D_GET_PRIMITIVE|S3D_TRACE, &ground->view);
+ if(res != RES_OK) {
+ htrdr_log_err(ground->htrdr,
+ "Could not create the Star-3D scene view -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ res = s3d_scene_view_get_aabb(ground->view, ground->lower, ground->upper);
+ if(res != RES_OK) {
+ htrdr_log_err(ground->htrdr,
+ "Could not get the bounding box of the geometry -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+exit:
+ if(obj) AW(obj_ref_put(obj));
+ if(shape) S3D(shape_ref_put(shape));
+ if(scene) S3D(scene_ref_put(scene));
+ htable_vertex_release(&vertices);
+ darray_double_release(&positions);
+ darray_size_t_release(&indices);
+ return res;
+error:
+ goto exit;
+}
+
+static void
+release_ground(ref_T* ref)
+{
+ struct htrdr_atmosphere_ground* ground;
+ struct htrdr* htrdr;
+ ASSERT(ref);
+ ground = CONTAINER_OF(ref, struct htrdr_atmosphere_ground, ref);
+ if(ground->view) S3D(scene_view_ref_put(ground->view));
+ htable_interface_release(&ground->interfaces);
+ htrdr = ground->htrdr;
+ MEM_RM(ground->htrdr->allocator, ground);
+ htrdr_ref_put(htrdr);
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_atmosphere_ground_create
+ (struct htrdr* htrdr,
+ const char* obj_filename, /* May be NULL */
+ const int repeat_ground, /* Infinitely repeat the ground in X and Y */
+ struct htrdr_atmosphere_ground** out_ground)
+{
+ char buf[128];
+ struct htrdr_atmosphere_ground* ground = NULL;
+ struct time t0, t1;
+ res_T res = RES_OK;
+ ASSERT(htrdr && out_ground);
+
+ ground = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*ground));
+ if(!ground) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "%s: could not allocate the ground data structure -- %s.\n",
+ FUNC_NAME, res_to_cstr(res));
+ goto error;
+ }
+ ref_init(&ground->ref);
+ ground->repeat = repeat_ground;
+ f3_splat(ground->lower, (float)INF);
+ f3_splat(ground->upper,-(float)INF);
+ htable_interface_init(ground->htrdr->allocator, &ground->interfaces);
+ htrdr_ref_get(htrdr);
+ ground->htrdr = htrdr;
+
+ if(!obj_filename) goto exit;
+
+ htrdr_log(ground->htrdr, "Loading ground geometry from `%s'.\n",obj_filename);
+ time_current(&t0);
+ res = setup_ground(ground, obj_filename);
+ if(res != RES_OK) goto error;
+ time_sub(&t0, time_current(&t1), &t0);
+ time_dump(&t0, TIME_ALL, NULL, buf, sizeof(buf));
+ htrdr_log(ground->htrdr, "Setup ground in %s\n", buf);
+
+exit:
+ *out_ground = ground;
+ return res;
+error:
+ if(ground) {
+ htrdr_atmosphere_ground_ref_put(ground);
+ ground = NULL;
+ }
+ goto exit;
+}
+
+void
+htrdr_atmosphere_ground_ref_get(struct htrdr_atmosphere_ground* ground)
+{
+ ASSERT(ground);
+ ref_get(&ground->ref);
+}
+
+void
+htrdr_atmosphere_ground_ref_put(struct htrdr_atmosphere_ground* ground)
+{
+ ASSERT(ground);
+ ref_put(&ground->ref, release_ground);
+}
+
+void
+htrdr_atmosphere_ground_get_interface
+ (struct htrdr_atmosphere_ground* ground,
+ const struct s3d_hit* hit,
+ struct htrdr_interface* out_interface)
+{
+ struct htrdr_interface* interf = NULL;
+ ASSERT(ground && hit && out_interface);
+
+ interf = htable_interface_find(&ground->interfaces, &hit->prim.geom_id);
+ ASSERT(interf);
+
+ *out_interface = *interf;
+}
+
+res_T
+htrdr_atmosphere_ground_trace_ray
+ (struct htrdr_atmosphere_ground* ground,
+ const double org[3],
+ const double dir[3], /* Must be normalized */
+ const double range[2],
+ const struct s3d_hit* prev_hit,
+ struct s3d_hit* hit)
+{
+ res_T res = RES_OK;
+ ASSERT(ground && org && dir && range && hit);
+
+ if(!ground->view) { /* No ground geometry */
+ *hit = S3D_HIT_NULL;
+ goto exit;
+ }
+
+ if(!ground->repeat) {
+ struct ray_context ray_ctx = RAY_CONTEXT_NULL;
+ float ray_org[3];
+ float ray_dir[3];
+
+ f3_set_d3(ray_org, org);
+ f3_set_d3(ray_dir, dir);
+ f2_set_d2(ray_ctx.range, range);
+ ray_ctx.hit_prev = prev_hit ? *prev_hit : S3D_HIT_NULL;
+
+ res = s3d_scene_view_trace_ray
+ (ground->view, ray_org, ray_dir, ray_ctx.range, &ray_ctx, hit);
+ if(res != RES_OK) {
+ htrdr_log_err(ground->htrdr,
+ "%s: could not trace the ray against the ground geometry -- %s.\n",
+ FUNC_NAME, res_to_cstr(res));
+ goto error;
+ }
+ } else {
+ struct trace_ground_context slab_ctx = TRACE_GROUND_CONTEXT_NULL;
+ double low[3], upp[3];
+
+ *hit = S3D_HIT_NULL;
+ slab_ctx.view = ground->view;
+ slab_ctx.context.range[0] = (float)range[0];
+ slab_ctx.context.range[1] = (float)range[1];
+ slab_ctx.context.hit_prev = prev_hit ? *prev_hit : S3D_HIT_NULL;
+ slab_ctx.hit = hit;
+
+ d3_set_f3(low, ground->lower);
+ d3_set_f3(upp, ground->upper);
+
+ res = htrdr_slab_trace_ray(ground->htrdr, org, dir, range, low, upp,
+ trace_ground, 32, &slab_ctx);
+ if(res != RES_OK) goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+res_T
+htrdr_atmosphere_ground_find_closest_point
+ (struct htrdr_atmosphere_ground* ground,
+ const double pos[3],
+ const double radius,
+ struct s3d_hit* hit)
+{
+ float query_pos[3];
+ float query_radius;
+ float ground_sz[3];
+ res_T res = RES_OK;
+ ASSERT(ground && pos && hit);
+
+ if(!ground->view) { /* No ground geometry */
+ *hit = S3D_HIT_NULL;
+ goto exit;
+ }
+
+ query_radius = (float)radius;
+ f3_set_d3(query_pos, pos);
+
+ if(ground->repeat) {
+ float translation[2] = {0, 0};
+ int64_t xy[2];
+ ground_sz[0] = ground->upper[0] - ground->lower[0];
+ ground_sz[1] = ground->upper[1] - ground->lower[1];
+ ground_sz[2] = ground->upper[2] - ground->lower[2];
+
+ /* Define the 2D index of the current ground instance. (0,0) is the index
+ * of the non instantiated ground */
+ xy[0] = (int64_t)floor((query_pos[0] - ground->lower[0]) / ground_sz[0]);
+ xy[1] = (int64_t)floor((query_pos[1] - ground->lower[1]) / ground_sz[1]);
+
+ /* Define the translation along the X and Y axis from world space to local
+ * ground geometry space */
+ translation[0] = -(float)xy[0] * ground_sz[0];
+ translation[1] = -(float)xy[1] * ground_sz[1];
+
+ /* Transform the query pos in local ground geometry space */
+ query_pos[0] += translation[0];
+ query_pos[1] += translation[1];
+ }
+
+ /* Closest point query */
+ res = s3d_scene_view_closest_point
+ (ground->view, query_pos, query_radius, NULL, hit);
+ if(res != RES_OK) {
+ htrdr_log_err(ground->htrdr,
+ "%s: could not query the closest point to the ground geometry -- %s.\n",
+ FUNC_NAME, res_to_cstr(res));
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
diff --git a/src/atmosphere/htrdr_atmosphere_ground.h b/src/atmosphere/htrdr_atmosphere_ground.h
@@ -0,0 +1,78 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_ATMOSPHERE_GROUND_H
+#define HTRDR_ATMOSPHERE_GROUND_H
+
+#include <rsys/rsys.h>
+
+/* Forward declarations */
+struct htrdr;
+struct htrdr_atmosphere_ground;
+struct htrdr_interface;
+struct s3d_hit;
+struct ssf_bsdf;
+
+extern LOCAL_SYM res_T
+htrdr_atmosphere_ground_create
+ (struct htrdr* htrdr,
+ const char* obj_filename, /* May be NULL <=> No ground geometry */
+ const int repeat_ground, /* Infinitely repeat the ground in X and Y */
+ struct htrdr_atmosphere_ground** ground);
+
+extern LOCAL_SYM void
+htrdr_atmosphere_ground_ref_get
+ (struct htrdr_atmosphere_ground* ground);
+
+extern LOCAL_SYM void
+htrdr_atmosphere_ground_ref_put
+ (struct htrdr_atmosphere_ground* ground);
+
+extern LOCAL_SYM void
+htrdr_atmosphere_ground_get_interface
+ (struct htrdr_atmosphere_ground* ground,
+ const struct s3d_hit* hit,
+ struct htrdr_interface* interface);
+
+extern LOCAL_SYM res_T
+htrdr_atmosphere_ground_create_bsdf
+ (struct htrdr_atmosphere_ground* ground,
+ const size_t ithread,
+ const double wavelength,
+ const double pos[3],
+ const double dir[3], /* Incoming ray */
+ const struct s3d_hit* hit,
+ struct htrdr_interface* interf, /* NULL <=> do not return the interface */
+ struct ssf_bsdf** bsdf);
+
+extern LOCAL_SYM res_T
+htrdr_atmosphere_ground_trace_ray
+ (struct htrdr_atmosphere_ground* ground,
+ const double ray_origin[3],
+ const double ray_direction[3], /* Must be normalized */
+ const double ray_range[2],
+ const struct s3d_hit* prev_hit,/* Previous hit. Avoid self hit. May be NULL*/
+ struct s3d_hit* hit);
+
+extern LOCAL_SYM res_T
+htrdr_atmosphere_ground_find_closest_point
+ (struct htrdr_atmosphere_ground* ground,
+ const double position[3],
+ const double radius,
+ struct s3d_hit* hit);
+
+#endif /* HTRDR_ATMOSPHERE_GROUND_H */
+
diff --git a/src/atmosphere/htrdr_atmosphere_main.c b/src/atmosphere/htrdr_atmosphere_main.c
@@ -0,0 +1,71 @@
+/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019, 2021 CNRS
+ * Copyright (C) 2018, 2019, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr.h"
+#include "htrdr_atmosphere.h"
+#include "htrdr_atmosphere_args.h"
+
+int
+main(int argc, char** argv)
+{
+ struct htrdr_args htrdr_args = HTRDR_ARGS_DEFAULT;
+ struct htrdr_atmosphere_args cmd_args = HTRDR_ATMOSPHERE_ARGS_DEFAULT;
+ struct htrdr* htrdr = NULL;
+ struct htrdr_atmosphere* cmd = NULL;
+ size_t memsz;
+ int err = 0;
+ res_T res = RES_OK;
+
+ res = htrdr_mpi_init(argc, argv);
+ if(res != RES_OK) goto error;
+
+ res = htrdr_atmosphere_args_init(&cmd.args, argc, argv);
+ if(res != RES_OK) goto error;
+ if(args.quit) goto exit;
+
+ htrdr_args.nthreads = cmd_args.nthreads;
+ htrdr_args.verbose = cmd_args.verbose;
+ res = htrdr_create(NULL, &htrdr_args, &htrdr);
+ if(res != RES_OK) goto error;
+
+ if(cmd_args.dump_volumetric_acceleration_structure
+ && htrdr_get_mpi_rank(htrdr) != 0) {
+ goto exit; /* Nothing to do except for the master process */
+ }
+
+ res = htrdr_atmosphere_create(htrdr, &cmd_args, &cmd);
+ if(res != RES_OK) goto error;
+
+ res = htrdr_atmosphere_run(cmd);
+ if(res != RES_OK) goto error;
+
+exit:
+ htrdr_mpi_finalize();
+ htrdr_atmosphere_args_release(&cmd_args);
+ if(htrdr) htrdr_ref_put(htrdr);
+ if(cmd) htrdr_atmosphere_ref_put(cmd);
+
+ /* Check memory leaks */
+ if((memsz = mem_allocated_size()) != 0) {
+ fprintf(stderr, "Memory leaks: %lu Bytes\n", (unsigned long)memsz);
+ err = -1;
+ }
+ return err;
+error:
+ err = - 1;
+ goto exit;
+}
diff --git a/src/atmosphere/htrdr_atmosphere_sun.c b/src/atmosphere/htrdr_atmosphere_sun.c
@@ -0,0 +1,156 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr_atmosphere_c.h"
+#include "htrdr_atmosphere_sun.h"
+
+#include <rsys/algorithm.h>
+#include <rsys/double33.h>
+#include <rsys/ref_count.h>
+#include <rsys/math.h>
+
+#include <star/ssp.h>
+
+struct htrdr_atmosphere_sun {
+ double half_angle; /* In radian */
+ double cos_half_angle;
+ double solid_angle; /* In sr; solid_angle = 2*PI*(1 - cos(half_angle)) */
+ double frame[9];
+ double temperature; /* In K */
+
+ ref_T ref;
+ struct htrdr* htrdr;
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static void
+release_sun(ref_T* ref)
+{
+ struct htrdr_atmosphere_sun* sun;
+ struct htrdr* htrdr;
+ ASSERT(ref);
+ sun = CONTAINER_OF(ref, struct htrdr_atmosphere_sun, ref);
+ htrdr = sun->htrdr;
+ MEM_RM(htrdr_allocator(htrdr), sun);
+ htrdr_ref_put(htrdr);
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_atmosphere_sun_create
+ (struct htrdr* htrdr,
+ struct htrdr_atmosphere_sun** out_sun)
+{
+ const double main_dir[3] = {0, 0, 1}; /* Default main sun direction */
+ struct htrdr_atmosphere_sun* sun = NULL;
+ res_T res = RES_OK;
+ ASSERT(htrdr && out_sun);
+
+ sun = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*sun));
+ if(!sun) {
+ htrdr_log_err(htrdr, "could not allocate the sun data structure.\n");
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ ref_init(&sun->ref);
+ sun->half_angle = 4.6524e-3;
+ sun->temperature = 5778;
+ sun->cos_half_angle = cos(sun->half_angle);
+ sun->solid_angle = 2*PI*(1-sun->cos_half_angle);
+ d33_basis(sun->frame, main_dir);
+ htrdr_ref_get(htrdr);
+ sun->htrdr = htrdr;
+
+exit:
+ *out_sun = sun;
+ return res;
+error:
+ if(sun) {
+ htrdr_atmosphere_sun_ref_put(sun);
+ sun = NULL;
+ }
+ goto exit;
+}
+
+void
+htrdr_atmosphere_sun_ref_get(struct htrdr_atmosphere_sun* sun)
+{
+ ASSERT(sun);
+ ref_get(&sun->ref);
+}
+
+void
+htrdr_atmosphere_sun_ref_put(struct htrdr_atmosphere_sun* sun)
+{
+ ASSERT(sun);
+ ref_put(&sun->ref, release_sun);
+}
+
+void
+htrdr_atmosphere_sun_set_direction
+ (struct htrdr_atmosphere_sun* sun,
+ const double dir[3])
+{
+ ASSERT(sun && dir && d3_is_normalized(dir));
+ d33_basis(sun->frame, dir);
+}
+
+double
+htrdr_atmosphere_sun_sample_direction
+ (struct htrdr_atmosphere_sun* sun,
+ struct ssp_rng* rng,
+ double dir[3])
+{
+ ASSERT(sun && rng && dir);
+ ssp_ran_sphere_cap_uniform_local(rng, sun->cos_half_angle, dir, NULL);
+ d33_muld3(dir, sun->frame, dir);
+ return 1.0 / htrdr_atmosphere_sun_get_solid_angle(sun);
+}
+
+double
+htrdr_atmosphere_sun_get_solid_angle(const struct htrdr_atmosphere_sun* sun)
+{
+ ASSERT(sun);
+ return sun->solid_angle;
+}
+
+double
+htrdr_atmosphere_sun_get_radiance
+ (const struct htrdr_atmosphere_sun* sun,
+ const double wlen/*In nm*/)
+{
+ return htrdr_planck_monochromatic
+ (wlen*1.e-9/*From nm to m*/, sun->temperature);
+}
+
+int
+htrdr_atmosphere_sun_is_dir_in_solar_cone
+ (const struct htrdr_atmosphere_sun* sun,
+ const double dir[3])
+{
+ const double* main_dir;
+ double dot;
+ ASSERT(sun && dir && d3_is_normalized(dir));
+ ASSERT(d3_is_normalized(sun->frame + 6));
+ main_dir = sun->frame + 6;
+ dot = d3_dot(dir, main_dir);
+ return dot >= sun->cos_half_angle;
+}
+
diff --git a/src/atmosphere/htrdr_atmosphere_sun.h b/src/atmosphere/htrdr_atmosphere_sun.h
@@ -0,0 +1,67 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_ATMOSPHERE_SUN_H
+#define HTRDR_ATMOSPHERE_SUN_H
+
+#include <rsys/rsys.h>
+
+/* Forward declaration */
+struct htrdr;
+struct htrdr_atmosphere_sun;
+struct ssp_rng;
+
+extern LOCAL_SYM res_T
+htrdr_atmosphere_sun_create
+ (struct htrdr_atmosphere* sun,
+ struct htrdr_atmosphere_sun** out_sun);
+
+extern LOCAL_SYM void
+htrdr_atmosphere_sun_ref_get
+ (struct htrdr_atmosphere_sun* sun);
+
+extern LOCAL_SYM void
+htrdr_atmosphere_sun_ref_put
+ (struct htrdr_atmosphere_sun* sun);
+
+/* Setup the direction *toward* the sun "center" */
+extern LOCAL_SYM void
+htrdr_atmosphere_sun_set_direction
+ (struct htrdr_atmosphere_sun* sun,
+ const double direction[3]); /* Must be normalized */
+
+/* Return a pdf of the sampled dir */
+extern LOCAL_SYM double
+htrdr_atmosphere_sun_sample_direction
+ (struct htrdr_atmosphere_sun* sun,
+ struct ssp_rng* rng,
+ double dir[3]);
+
+extern LOCAL_SYM double
+htrdr_atmosphere_sun_get_solid_angle
+ (const struct htrdr_atmosphere_sun* sun);
+
+extern LOCAL_SYM double /* W/m^2/sr/m */
+htrdr_atmosphere_sun_get_radiance
+ (const struct htrdr_atmosphere_sun* sun,
+ const double wavelength);
+
+extern LOCAL_SYM int
+htrdr_atmosphere_sun_is_dir_in_solar_cone
+ (const struct htrdr_atmosphere_sun* sun,
+ const double dir[3]);
+
+#endif /* HTRDR_ATMOSPHERE_SUN_H */
diff --git a/src/core/htrdr.c b/src/core/htrdr.c
@@ -0,0 +1,659 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define _POSIX_C_SOURCE 200809L /* stat.st_time support */
+
+#include "htrdr.h"
+#include "htrdr_c.h"
+#include "htrdr_args.h"
+#include "htrdr_log.h"
+#include "htrdr_version.h"
+
+#include <rsys/cstr.h>
+#include <rsys/mem_allocator.h>
+#include <rsys/str.h>
+
+#include "high_tune/htsky.h"
+
+#include <star/s3d.h>
+#include <star/ssf.h>
+
+#include <errno.h>
+#include <fcntl.h> /* open */
+#include <libgen.h> /* basename */
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/time.h> /* timespec */
+#include <sys/stat.h> /* S_IRUSR & S_IWUSR */
+
+#include <omp.h>
+#include <mpi.h>
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static const char*
+mpi_thread_support_string(const int val)
+{
+ switch(val) {
+ case MPI_THREAD_SINGLE: return "MPI_THREAD_SINGLE";
+ case MPI_THREAD_FUNNELED: return "MPI_THREAD_FUNNELED";
+ case MPI_THREAD_SERIALIZED: return "MPI_THREAD_SERIALIZED";
+ case MPI_THREAD_MULTIPLE: return "MPI_THREAD_MULTIPLE";
+ default: FATAL("Unreachable code.\n"); break;
+ }
+}
+
+static void
+release_mpi(struct htrdr* htrdr)
+{
+ ASSERT(htrdr);
+ if(htrdr->mpi_working_procs) {
+ MEM_RM(htrdr->allocator, htrdr->mpi_working_procs);
+ htrdr->mpi_working_procs = NULL;
+ }
+ if(htrdr->mpi_progress_octree) {
+ MEM_RM(htrdr->allocator, htrdr->mpi_progress_octree);
+ htrdr->mpi_progress_octree = NULL;
+ }
+ if(htrdr->mpi_progress_render) {
+ MEM_RM(htrdr->allocator, htrdr->mpi_progress_render);
+ htrdr->mpi_progress_render = NULL;
+ }
+ if(htrdr->mpi_err_str) {
+ MEM_RM(htrdr->allocator, htrdr->mpi_err_str);
+ htrdr->mpi_err_str = NULL;
+ }
+ if(htrdr->mpi_mutex) {
+ mutex_destroy(htrdr->mpi_mutex);
+ htrdr->mpi_mutex = NULL;
+ }
+}
+
+static res_T
+mpi_print_proc_info(struct htrdr* htrdr)
+{
+ char proc_name[MPI_MAX_PROCESSOR_NAME];
+ int proc_name_len;
+ char* proc_names = NULL;
+ uint32_t* proc_nthreads = NULL;
+ uint32_t nthreads = 0;
+ int iproc;
+ res_T res = RES_OK;
+ ASSERT(htrdr);
+
+ if(htrdr->mpi_rank == 0) {
+ proc_names = MEM_CALLOC(htrdr->allocator, (size_t)htrdr->mpi_nprocs,
+ MPI_MAX_PROCESSOR_NAME*sizeof(*proc_names));
+ if(!proc_names) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "could not allocate the temporary memory for MPI process names -- "
+ "%s.\n", res_to_cstr(res));
+ goto error;
+ }
+
+ proc_nthreads = MEM_CALLOC(htrdr->allocator, (size_t)htrdr->mpi_nprocs,
+ sizeof(*proc_nthreads));
+ if(!proc_nthreads) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "could not allocate the temporary memory for the #threads of the MPI "
+ "processes -- %s.\n", res_to_cstr(res));
+ goto error;
+ }
+ }
+
+ /* Gather process name */
+ MPI(Get_processor_name(proc_name, &proc_name_len));
+ MPI(Gather(proc_name, MPI_MAX_PROCESSOR_NAME, MPI_CHAR, proc_names,
+ MPI_MAX_PROCESSOR_NAME, MPI_CHAR, 0, MPI_COMM_WORLD));
+
+ /* Gather process #threads */
+ nthreads = (uint32_t)htrdr->nthreads;
+ MPI(Gather(&nthreads, 1, MPI_UINT32_T, proc_nthreads, 1, MPI_UINT32_T, 0,
+ MPI_COMM_WORLD));
+
+ if(htrdr->mpi_rank == 0) {
+ FOR_EACH(iproc, 0, htrdr->mpi_nprocs) {
+ htrdr_log(htrdr, "Process %d -- %s; #threads: %u\n",
+ iproc, proc_names + iproc*MPI_MAX_PROCESSOR_NAME, proc_nthreads[iproc]);
+ }
+ }
+
+exit:
+ if(proc_names) MEM_RM(htrdr->allocator, proc_names);
+ if(proc_nthreads) MEM_RM(htrdr->allocator, proc_nthreads);
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+init_mpi(struct htrdr* htrdr)
+{
+ size_t n;
+ int err;
+ res_T res = RES_OK;
+ ASSERT(htrdr);
+
+ htrdr->mpi_err_str = MEM_CALLOC
+ (htrdr->allocator, htrdr->nthreads, MPI_MAX_ERROR_STRING);
+ if(!htrdr->mpi_err_str) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "could not allocate the MPI error strings -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ err = MPI_Comm_rank(MPI_COMM_WORLD, &htrdr->mpi_rank);
+ if(err != MPI_SUCCESS) {
+ htrdr_log_err(htrdr,
+ "could not determine the MPI rank of the calling process -- %s.\n",
+ htrdr_mpi_error_string(htrdr, err));
+ res = RES_UNKNOWN_ERR;
+ goto error;
+ }
+
+ err = MPI_Comm_size(MPI_COMM_WORLD, &htrdr->mpi_nprocs);
+ if(err != MPI_SUCCESS) {
+ htrdr_log_err(htrdr,
+ "could retrieve the size of the MPI group -- %s.\n",
+ htrdr_mpi_error_string(htrdr, err));
+ res = RES_UNKNOWN_ERR;
+ goto error;
+ }
+
+ htrdr->mpi_working_procs = MEM_CALLOC(htrdr->allocator,
+ (size_t)htrdr->mpi_nprocs, sizeof(*htrdr->mpi_working_procs));
+ if(!htrdr->mpi_working_procs) {
+ htrdr_log_err(htrdr,
+ "could not allocate the list of working processes.\n");
+ res = RES_MEM_ERR;
+ goto error;
+ }
+
+ /* Initialy, all the processes are working */
+ htrdr->mpi_nworking_procs = (size_t)htrdr->mpi_nprocs;
+ memset(htrdr->mpi_working_procs, 0xFF,
+ htrdr->mpi_nworking_procs*sizeof(*htrdr->mpi_working_procs));
+
+ /* Allocate #processes progress statuses on the master process and only 1
+ * progress status on the other ones: the master process will gather the
+ * status of the other processes to report their progression. */
+ n = (size_t)(htrdr->mpi_rank == 0 ? htrdr->mpi_nprocs : 1);
+
+ htrdr->mpi_progress_octree = MEM_CALLOC
+ (htrdr->allocator, n, sizeof(*htrdr->mpi_progress_octree));
+ if(!htrdr->mpi_progress_octree) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "could not allocate the progress state of the octree building -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ htrdr->mpi_progress_render = MEM_CALLOC
+ (htrdr->allocator, n, sizeof(*htrdr->mpi_progress_render));
+ if(!htrdr->mpi_progress_render) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "could not allocate the progress state of the scene rendering -- %s.\n",
+ res_to_cstr(res));
+ goto error;
+ }
+
+ htrdr->mpi_mutex = mutex_create();
+ if(!htrdr->mpi_mutex) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "could not create the mutex to protect MPI calls from concurrent "
+ "threads -- %s.\n", res_to_cstr(res));
+ goto error;
+ }
+
+ mpi_print_proc_info(htrdr);
+
+exit:
+ return res;
+error:
+ release_mpi(htrdr);
+ goto exit;
+}
+
+void
+release_htrdr(ref_T* ref)
+{
+ struct htrdr* htrdr = CONTAINER_OF(ref, struct htrdr, ref);
+ ASSERT(ref);
+
+ release_mpi(htrdr);
+ if(htrdr->lifo_allocators) {
+ size_t i;
+ FOR_EACH(i, 0, htrdr->nthreads) {
+ mem_shutdown_lifo_allocator(&htrdr->lifo_allocators[i]);
+ }
+ MEM_RM(htrdr->allocator, htrdr->lifo_allocators);
+ }
+ str_release(&htrdr->output_name);
+ logger_release(&htrdr->logger);
+
+ MEM_RM(htrdr->allocator, htrdr);
+}
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+res_T
+htrdr_mpi_init(int argc, char** argv)
+{
+ char str[MPI_MAX_ERROR_STRING];
+ int len;
+ int err = 0;
+ res_T res = RES_OK;
+
+ err = MPI_Init_thread(&argc, &argv, MPI_THREAD_SERIALIZED, &thread_support);
+ if(err != MPI_SUCCESS) {
+ MPI_Error_string(err, str, &len);
+ fprintf(stderr, "Error initializing MPI -- %s.\n", str);
+ res = RES_UNKNOWN_ERR;
+ goto error;
+ }
+
+ if(thread_support != MPI_THREAD_SERIALIZED) {
+ fprintf(stderr, "The provided MPI implementation does not support "
+ "serialized API calls from multiple threads. Provided thread support: "
+ "%s.\n", mpi_thread_support_string(thread_support));
+ res = RES_BAD_OP;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+res_T
+htrdr_mpi_finalize(void)
+{
+ MPI_Finalize();
+}
+
+res_T
+htrdr_create
+ (struct mem_allocator* mem_allocator,
+ const struct htrdr_args* args,
+ struct htrdr** out_htrdr)
+{
+ struct mem_allocator* allocator = NULL;
+ struct htrdr* htrdr = NULL;
+ size_t ithread;
+ int nthreads_max;
+ res_T res = RES_OK;
+ ASSERT(args && htrdr);
+
+ allocator = mem_allocator ? mem_allocator : &mem_default_allocator;
+ htrdr = MEM_CALLOC(allocator, 0, sizeof(*htrdr));
+ if(htrdr) {
+ fprintf(stderr, HTRDR_LOG_ERROR_PREFIX
+ "Could not allocate the htrdr data structure.\n");
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ htrdr->allocator = allocator;
+ ref_init(&htrdr->ref);
+ nthreads_max = MMAX(omp_get_max_threads(), omp_get_num_procs());
+ htrdr->verbose = args->verbose;
+ htrdr->nthreads = MMIN(args->nthreads, (unsigned)nthreads_max);
+
+ logger_init(htrdr->allocator, &htrdr->logger);
+ logger_set_stream(&htrdr->logger, LOG_OUTPUT, print_out, NULL);
+ logger_set_stream(&htrdr->logger, LOG_ERROR, print_err, NULL);
+ logger_set_stream(&htrdr->logger, LOG_WARNING, print_warn, NULL);
+
+ res = init_mpi(htrdr);
+ if(res != RES_OK) goto error;
+
+ htrdr->lifo_allocators = MEM_CALLOC
+ (htrdr->allocator, htrdr->nthreads, sizeof(*htrdr->lifo_allocators));
+ if(!htrdr->lifo_allocators) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "%s: could not allocate the list of per thread LIFO allocator -- %s.\n",
+ FUNC_NAME, res_to_cstr(res));
+ goto error;
+ }
+
+ FOR_EACH(ithread, 0, htrdr->nthreads) {
+ res = mem_init_lifo_allocator
+ (&htrdr->lifo_allocators[ithread], htrdr->allocator, 16384);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr,
+ "%s: could not initialise the LIFO allocator of the thread %lu -- %s.\n",
+ FUNC_NAME, (unsigned long)ithread, res_to_cstr(res));
+ goto error;
+ }
+ }
+
+exit:
+ *out_htrdr = htrdr;
+ return res;
+error:
+ if(htrdr) {
+ htrdr_ref_put(htrdr);
+ htrdr = NULL;
+ }
+ goto exit;
+}
+
+void
+htrdr_ref_get(struct htrdr* htrdr)
+{
+ ASSERT(htrdr);
+ ref_get(&htrdr->ref);
+}
+
+void
+htrdr_ref_put(struct htrdr* htrdr)
+{
+ ASSERT(htrdr);
+ ref_put(&htrdr->ref, release_htrdr);
+}
+
+size_t
+htrdr_get_threads_count(const struct htrdr* htrdr)
+{
+ ASSERT(htrdr);
+ return htrdr->nthreads;
+}
+
+size_t
+htrdr_get_procs_count(const struct htrdr* htrdr)
+{
+ ASSERT(htrdr);
+ return htrdr->mpi_nprocs;
+}
+
+int
+htrdr_get_mpi_rank(const struct htrdr* htrdr)
+{
+ ASSERT(htrdr);
+ return htrdr->mpi_rank;
+}
+
+struct mem_allocator*
+htrdr_get_allocator(const struct htrdr* htrdr)
+{
+ ASSERT(htrdr);
+ return htrdr->allocator;
+}
+
+struct mem_allocator*
+htrdr_get_lifo_allocator(const struct htrdr* htrdr, const unsigned ithread)
+{
+ ASSERT(htrdr && ithread < htrdr_get_threads_count(htrdr));
+ return htrdr->lifo_allocators[ithread];
+}
+
+const char*
+htrdr_mpi_error_string(struct htrdr* htrdr, const int mpi_err)
+{
+ const int ithread = omp_get_thread_num();
+ char* str;
+ int strlen_err;
+ int err;
+ ASSERT(htrdr && (size_t)ithread < htrdr->nthreads);
+ str = htrdr->mpi_err_str + ithread*MPI_MAX_ERROR_STRING;
+ err = MPI_Error_string(mpi_err, str, &strlen_err);
+ return err == MPI_SUCCESS ? str : "Invalid MPI error";
+}
+
+res_T
+htrdr_open_output_stream
+ (struct htrdr* htrdr,
+ const char* filename,
+ const int read,
+ int force_overwrite,
+ FILE** out_fp)
+{
+ FILE* fp = NULL;
+ int fd = -1;
+ const char* mode;
+ res_T res = RES_OK;
+ ASSERT(htrdr && filename && out_fp);
+
+ mode = read ? "w+" : "w";
+
+ if(force_overwrite) {
+ fp = fopen(filename, mode);
+ if(!fp) {
+ htrdr_log_err(htrdr, "could not open the output file `%s'.\n", filename);
+ goto error;
+ }
+ } else {
+ const int access_flags = read ? O_RDWR : O_WRONLY;
+ fd = open(filename, O_CREAT|O_EXCL|O_TRUNC|access_flags, S_IRUSR|S_IWUSR);
+ if(fd >= 0) {
+ fp = fdopen(fd, mode);
+ if(fp == NULL) {
+ htrdr_log_err(htrdr, "could not open the output file `%s'.\n", filename);
+ goto error;
+ }
+ } else if(errno == EEXIST) {
+ htrdr_log_err(htrdr, "the output file `%s' already exists. \n",
+ filename);
+ goto error;
+ } else {
+ htrdr_log_err(htrdr,
+ "unexpected error while opening the output file `%s'.\n", filename);
+ goto error;
+ }
+ }
+exit:
+ *out_fp = fp;
+ return res;
+error:
+ res = RES_IO_ERR;
+ if(fp) {
+ CHK(fclose(fp) == 0);
+ fp = NULL;
+ } else if(fd >= 0) {
+ CHK(close(fd) == 0);
+ }
+ goto exit;
+}
+
+
+void
+htrdr_fprintf(struct htrdr* htrdr, FILE* stream, const char* msg, ...)
+{
+ ASSERT(htrdr && msg);
+ if(htrdr->mpi_rank == 0) {
+ va_list vargs_list;
+ va_start(vargs_list, msg);
+ vfprintf(stream, msg, vargs_list);
+ va_end(vargs_list);
+ }
+}
+
+void
+htrdr_fflush(struct htrdr* htrdr, FILE* stream)
+{
+ ASSERT(htrdr);
+ if(htrdr->mpi_rank == 0) {
+ fflush(stream);
+ }
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+double
+compute_sky_min_band_len
+ (struct htsky* sky,
+ const double range[2])
+{
+ double min_band_len = DBL_MAX;
+ size_t nbands;
+ ASSERT(sky && range && range[0] <= range[1]);
+
+ nbands = htsky_get_spectral_bands_count(sky);
+
+ if(eq_eps(range[0], range[1], 1.e-6)) {
+ ASSERT(nbands == 1);
+ min_band_len = 0;
+ } else {
+ size_t i = 0;
+
+ /* Compute the length of the current band clamped to the submitted range */
+ FOR_EACH(i, 0, nbands) {
+ const size_t iband = htsky_get_spectral_band_id(sky, i);
+ double wlens[2];
+ HTSKY(get_spectral_band_bounds(sky, iband, wlens));
+
+ /* Adjust band boundaries to the submitted range */
+ wlens[0] = MMAX(wlens[0], range[0]);
+ wlens[1] = MMIN(wlens[1], range[1]);
+
+ min_band_len = MMIN(wlens[1] - wlens[0], min_band_len);
+ }
+ }
+ return min_band_len;
+}
+
+void
+send_mpi_progress
+ (struct htrdr* htrdr, const enum htrdr_mpi_message msg, int32_t percent)
+{
+ ASSERT(htrdr);
+ ASSERT(msg == HTRDR_MPI_PROGRESS_RENDERING);
+ (void)htrdr;
+ mutex_lock(htrdr->mpi_mutex);
+ MPI(Send(&percent, 1, MPI_INT32_T, 0, msg, MPI_COMM_WORLD));
+ mutex_unlock(htrdr->mpi_mutex);
+}
+
+void
+fetch_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg)
+{
+ struct timespec t;
+ int32_t* progress = NULL;
+ int iproc;
+ ASSERT(htrdr && htrdr->mpi_rank == 0);
+
+ t.tv_sec = 0;
+ t.tv_nsec = 10000000; /* 10ms */
+
+ switch(msg) {
+ case HTRDR_MPI_PROGRESS_RENDERING:
+ progress = htrdr->mpi_progress_render;
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+
+ FOR_EACH(iproc, 1, htrdr->mpi_nprocs) {
+ /* Flush the last sent percentage of the process `iproc' */
+ for(;;) {
+ MPI_Request req;
+ int flag;
+ int complete;
+
+ mutex_lock(htrdr->mpi_mutex);
+ MPI(Iprobe(iproc, msg, MPI_COMM_WORLD, &flag, MPI_STATUS_IGNORE));
+ mutex_unlock(htrdr->mpi_mutex);
+
+ if(flag == 0) break; /* No more message */
+
+ mutex_lock(htrdr->mpi_mutex);
+ MPI(Irecv(&progress[iproc], 1, MPI_INT32_T, iproc, msg, MPI_COMM_WORLD, &req));
+ mutex_unlock(htrdr->mpi_mutex);
+ for(;;) {
+ mutex_lock(htrdr->mpi_mutex);
+ MPI(Test(&req, &complete, MPI_STATUS_IGNORE));
+ mutex_unlock(htrdr->mpi_mutex);
+ if(complete) break;
+ nanosleep(&t, NULL);
+ }
+ }
+ }
+}
+
+void
+print_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg)
+{
+ ASSERT(htrdr && htrdr->mpi_rank == 0);
+
+ if(htrdr->mpi_nprocs == 1) {
+ switch(msg) {
+ case HTRDR_MPI_PROGRESS_RENDERING:
+ htrdr_fprintf(htrdr, stderr, "\033[2K\rRendering: %3d%%",
+ htrdr->mpi_progress_render[0]);
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+ htrdr_fflush(htrdr, stderr);
+ } else {
+ int iproc;
+ FOR_EACH(iproc, 0, htrdr->mpi_nprocs) {
+ switch(msg) {
+ case HTRDR_MPI_PROGRESS_RENDERING:
+ htrdr_fprintf(htrdr, stderr,
+ "\033[2K\rProcess %d -- rendering: %3d%%%c",
+ iproc, htrdr->mpi_progress_render[iproc],
+ iproc == htrdr->mpi_nprocs - 1 ? '\r' : '\n');
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+ }
+ }
+}
+
+void
+clear_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg)
+{
+ ASSERT(htrdr);
+ (void)msg;
+ if(htrdr->mpi_nprocs > 1) {
+ htrdr_fprintf(htrdr, stderr, "\033[%dA", htrdr->mpi_nprocs-1);
+ }
+}
+
+int
+total_mpi_progress(const struct htrdr* htrdr, const enum htrdr_mpi_message msg)
+{
+ const int* progress = NULL;
+ int total = 0;
+ int iproc;
+ ASSERT(htrdr && htrdr->mpi_rank == 0);
+
+ switch(msg) {
+ case HTRDR_MPI_PROGRESS_RENDERING:
+ progress = htrdr->mpi_progress_render;
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+
+ FOR_EACH(iproc, 0, htrdr->mpi_nprocs) {
+ total += progress[iproc];
+ }
+ total = total / htrdr->mpi_nprocs;
+ return total;
+}
+
diff --git a/src/core/htrdr.h b/src/core/htrdr.h
@@ -0,0 +1,171 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_H
+#define HTRDR_H
+
+#include <rsys/rsys.h>
+#include <stdio.h>
+
+/* Library symbol management */
+#if defined(HTRDR_SHARED_BUILD) /* Build shared library */
+ #define HTRDR_API extern EXPORT_SYM
+#elif defined(HTRDR_STATIC) /* Use/build static library */
+ #define HTRDR_API extern LOCAL_SYM
+#else /* Use shared library */
+ #define HTRDR_API extern IMPORT_SYM
+#endif
+
+/* Helper macro that asserts if the invocation of the htrdr function `Func'
+ * returns an error. One should use this macro on htrdr function calls for
+ * which no explicit error checking is performed */
+#ifndef NDEBUG
+ #define HTRDR(Func) ASSERT(htrdr_ ## Func == RES_OK)
+#else
+ #define HTRDR(Func) htrdr_ ## Func
+#endif
+
+/* Forward declarations */
+struct mem_allocator;
+struct mutex;
+
+struct htrdr_args {
+ unsigned nthreads; /* #threads of the process */
+ int verbose; /* Verbosity level */
+};
+#define HTRDR_ARGS_DEFAULT__ { (unsigned)~0, 1 }
+static const struct htrdr_args HTRDR_ARGS_DEFAULT = HTRDR_ARGS_DEFAULT__;
+
+/* Forward declaration */
+struct htrdr;
+
+static INLINE void
+htrdr_fprint_copyright(const char* cmd, FILE* stream)
+{
+ (void)cmd;
+ fprintf(stream,
+"Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> <contact@meso-star.com>.\n"
+"Copyright (C) 2018, 2019, 2021 CNRS.\n"
+"Copyright (C) 2018, 2019 Université Paul Sabatier.\n");
+}
+
+static INLINE void
+htrdr_fprint_license(const char* cmd, FILE* stream)
+{
+ ASSERT(cmd);
+ fprintf(stream,
+"%s is free software released under the GNU GPL license, version\n"
+"3 or later. You are free to change or redistribute it under certain\n"
+"conditions <http://gnu.org/licenses/gpl.html>.\n", cmd);
+}
+
+BEGIN_DECLS
+
+/* Initialize the MPI execution environment. Must be called priorly to any MPI
+ * invocation, e.g. at the beginning of the main function */
+HTRDR_API res_T
+htrdr_mpi_init
+ (int argc,
+ char** argv);
+
+/* Terminate the MPI execution environment */
+HTRDR_API res_T
+htrdr_mpi_finalize
+ (void);
+
+/*******************************************************************************
+ * HTRDR api
+ ******************************************************************************/
+HTRDR_API res_T
+htrdr_create
+ (struct mem_allocator* allocator,
+ const struct htrdr_args* args,
+ struct htrdr** htrdr);
+
+HTRDR_API void
+htrdr_ref_get
+ (struct htrdr* htrdr);
+
+HTRDR_API void
+htrdr_ref_put
+ (struct htrdr* htrdr);
+
+HTRDR_API void
+htrdr_draw_map
+ (struct htrdr* htrdr,
+ const struct htrdr_draw_map_args* args,
+ struct htrdr_buffer* buffer);
+
+/* Return the number of threads used by the process */
+HTRDR_API size_t
+htrdr_get_threads_count
+ (const struct htrdr* htrdr);
+
+/* Return the number of running processes for the current htrdr instance */
+HTRDR_API size_t
+htrdr_get_procs_count
+ (const struct
+
+HTRDR_API int
+htrdr_get_mpi_rank
+ (const struct htrdr* htrdr);
+
+HTRDR_API struct mem_allocator*
+htrdr_get_allocator
+ (const struct htrdr* htrdr);
+
+HTRDR_API struct mem_allocator*
+htrdr_get_lifo_allocator
+ (const struct htrdr* htrdr,
+ const size_t ithread);
+
+HTRDR_API res_T
+htrdr_open_output_stream
+ (struct htrdr* htrdr,
+ const char* filename,
+ const int read,
+ int force_overwrite,
+ FILE** out_fp);
+
+/* TODO do not expose publicly this function(?) */
+HTRDR_API const char*
+htrdr_mpi_error_string
+ (struct htrdr* htrdr,
+ const int mpi_err);
+
+/* TODO replace them by regular log message */
+HTRDR_API void
+htrdr_fprintf
+ (struct htrdr* htrdr,
+ FILE* stream,
+ const char* msg,
+ ...)
+#ifdef COMPILER_GCC
+ __attribute((format(printf, 3, 4)))
+#endif
+ ;
+
+/* TODO remove this */
+HTRDR_API void
+htrdr_fflush
+ (struct htrdr* htrdr,
+ FILE* stream);
+
+
+END_DECLS
+
+#endif /* HTRDR_H */
+
diff --git a/src/core/htrdr_accum.h b/src/core/htrdr_accum.h
@@ -0,0 +1,62 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_ACCUM_H
+#define HTRDR_ACCUM_H
+
+#include <rsys/rsys.h>
+
+/* Monte carlo accumulator */
+struct htrdr_accum {
+ double sum_weights; /* Sum of Monte-Carlo weights */
+ double sum_weights_sqr; /* Sum of Monte-Carlo square weights */
+ size_t nweights; /* #accumlated weights */
+ size_t nfailures; /* #failures */
+};
+#define HTRDR_ACCUM_NULL__ {0, 0, 0, 0}
+static const struct htrdr_accum HTRDR_ACCUM_NULL = HTRDR_ACCUM_NULL__;
+
+/* Monte carlo estimate */
+struct htrdr_estimate {
+ double E; /* Expected value */
+ double SE; /* Standard error */
+};
+#define HTRDR_ESTIMATE_NULL__ {0, 0}
+static const struct htrdr_estimate HTRDR_ESTIMATE_NULL = HTRDR_ESTIMATE_NULL__;
+
+static FINLINE void
+htrdr_accum_get_estimation
+ (const struct htrdr_accum* acc,
+ struct htrdr_estimate* estimate)
+{
+ ASSERT(acc && estimate);
+
+ if(!acc->nweights) {
+ estimate->E = 0;
+ estimate->SE = 0;
+ } else {
+ const double N = (double)acc->nweights;
+ double E, V, SE;
+ E = acc->sum_weights / N;
+ V = MMAX(acc->sum_weights_sqr / N - E*E, 0);
+ SE = sqrt(V/N);
+
+ estimate->E = E;
+ estimate->SE = SE;
+ }
+}
+
+#endif /* HTRDR_SOLVE_H */
diff --git a/src/core/htrdr_args.c b/src/core/htrdr_args.c
@@ -0,0 +1,419 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define _POSIX_C_SOURCE 2 /* strtok_r support */
+
+#include "htrdr.h"
+
+#include "htrdr_args.h"
+#include "htrdr_version.h"
+
+#include <rsys/cstr.h>
+#include <rsys/double3.h>
+
+#include <getopt.h>
+#include <string.h>
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static INLINE res_T
+parse_doubleX(const char* str, double* val, const size_t sz)
+{
+ size_t len;
+ res_T res = RES_OK;
+ ASSERT(str && val);
+ res = cstr_to_list_double(str, ',', val, &len, sz);
+ if(res == RES_OK && len != sz) res = RES_BAD_ARG;
+ return res;
+}
+
+static INLINE res_T
+parse_definition(const char* str, unsigned val[2])
+{
+ size_t len;
+ res_T res = RES_OK;
+ ASSERT(str && val);
+ res = cstr_to_list_uint(str, 'x', val, &len, 2);
+ if(res != RES_OK) return res;
+ if(len != 2) return RES_BAD_ARG;
+ if(val[0] > 16384 || val[1] > 16384) return RES_BAD_ARG;
+ return RES_OK;
+}
+
+static res_T
+parse_fov(const char* str, double* out_fov)
+{
+ double fov;
+ res_T res = RES_OK;
+ ASSERT(str && out_fov);
+
+ res = cstr_to_double(str, &fov);
+ if(res != RES_OK) {
+ fprintf(stderr, "Invalid field of view `%s'.\n", str);
+ return RES_BAD_ARG;
+ }
+ if(fov <= 0 || fov >= 180) {
+ fprintf(stderr, "The field of view %g is not in [30, 120].\n", fov);
+ return RES_BAD_ARG;
+ }
+ *out_fov = fov;
+ return RES_OK;
+}
+
+static res_T
+parse_image_parameter(struct htrdr_args_image* img, const char* str)
+{
+ char buf[128];
+ char* key;
+ char* val;
+ char* ctx;
+ res_T res = RES_OK;
+ ASSERT(str && img);
+
+ if(strlen(str) >= sizeof(buf) -1/*NULL char*/) {
+ fprintf(stderr,
+ "Could not duplicate the image option string `%s'.\n", str);
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ strncpy(buf, str, sizeof(buf));
+
+ key = strtok_r(buf, "=", &ctx);
+ val = strtok_r(NULL, "", &ctx);
+
+ if(!val) {
+ fprintf(stderr, "Missing a value to the image option `%s'.\n", key);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ #define PARSE(Name, Func) \
+ res = Func; \
+ if(res != RES_OK) { \
+ fprintf(stderr, "Invalid image "Name" `%s'.\n", val); \
+ goto error; \
+ } (void)0
+ if(!strcmp(key, "def")) {
+ PARSE("definition", parse_definition(val, img->definition));
+ } else if(!strcmp(key, "spp")) {
+ PARSE("#samples per pixel", cstr_to_uint(val, &img->spp));
+ } else {
+ fprintf(stderr, "Invalid image parameter `%s'.\n", key);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ #undef PARSE
+
+ if(!img->definition[0] || !img->definition[1]) {
+ fprintf(stderr, "The image definition cannot be null.n");
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ if(!img->spp) {
+ fprintf(stderr, "The number of samples per pixel cannot be null.\n");
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+parse_camera_parameter(struct htrdr_args_camera* cam, const char* str)
+{
+ char buf[128];
+ char* key;
+ char* val;
+ char* ctx;
+ res_T res = RES_OK;
+ ASSERT(cam);
+
+ if(strlen(str) >= sizeof(buf) -1/*NULL char*/) {
+ fprintf(stderr,
+ "Could not duplicate the camera option string `%s'.\n", str);
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ strncpy(buf, str, sizeof(buf));
+
+ key = strtok_r(buf, "=", &ctx);
+ val = strtok_r(NULL, "", &ctx);
+
+ if(!val) {
+ fprintf(stderr, "Missing value to the camera option `%s'.\n", key);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ #define PARSE(Name, Func) { \
+ if(RES_OK != (res = Func)) { \
+ fprintf(stderr, "Invalid camera "Name" `%s'.\n", val); \
+ goto error; \
+ } \
+ } (void)0
+ if(!strcmp(key, "pos")) {
+ PARSE("position", parse_doubleX(val, cam->position, 3));
+ } else if(!strcmp(key, "tgt")) {
+ PARSE("target", parse_doubleX(val, cam->target, 3));
+ } else if(!strcmp(key, "up")) {
+ PARSE("up vector", parse_doubleX(val, cam->up, 3));
+ } else if(!strcmp(key, "fov")) {
+ PARSE("field-of-view", parse_fov(val, cam->fov_y));
+ } else {
+ fprintf(stderr, "Invalid camera parameter `%s'.\n", key);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ #undef PARSE
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+parse_rectangle_parameter(struct htrdr_args_rectangle* rect, const char* str)
+{
+ char buf[128];
+ char* key;
+ char* val;
+ char* ctx;
+ res_T res = RES_OK;
+ ASSERT(rect);
+
+ if(strlen(str) >= sizeof(buf) -1/*NULL char*/) {
+ fprintf(stderr,
+ "Could not duplicate the rectangle option string `%s'.\n", str);
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ strncpy(buf, str, sizeof(buf));
+
+ /* pos=0,0,10.1; key <- pos, val <- 0,0,10 */
+ key = strtok_r(buf, "=", &ctx);
+ val = strtok_r(NULL, "", &ctx);
+
+ if(!val) {
+ fprintf(stderr, "Missing value to the rectangle option `%s'.\n", key);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ #define PARSE(Name, Func) { \
+ if(RES_OK != (res = Func)) { \
+ fprintf(stderr, "Invalid rectangle "Name" `%s'.\n", val); \
+ goto error; \
+ } \
+ } (void)0
+ if(!strcmp(key, "pos")) {
+ PARSE("position", parse_doubleX(val, rect->position, 3));
+ } else if(!strcmp(key, "tgt")) {
+ PARSE("target", parse_doubleX(val, rect->target, 3));
+ } else if(!strcmp(key, "up")) {
+ PARSE("up vector", parse_doubleX(val, rect->up, 3));
+ } else if(!strcmp(key, "sz")) {
+ PARSE("size", parse_doubleX(val, rect->size, 2));
+ } else {
+ fprintf(stderr, "Invalid rectangle parameter `%s'.\n", key);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ #undef PARSE
+exit:
+ return res;
+error:
+ goto EXIT;
+}
+
+static res_T
+parse_spectral_range(const char* str, double wlen_range[2])
+{
+ double range[2];
+ size_t len;
+ res_T res = RES_OK;
+ ASSERT(wlen_range && str);
+
+ res = cstr_to_list_double(str, ',', range, &len, 2);
+ if(res == RES_OK && len != 2) res = RES_BAD_ARG;
+ if(res == RES_OK && range[0] > range[1]) res = RES_BAD_ARG;
+ if(res != RES_OK) {
+ fprintf(stderr, "Invalid spectral range `%s'.\n", str);
+ goto error;
+ }
+ wlen_range[0] = range[0];
+ wlen_range[1] = range[1];
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+parse_spectral_parameter(struct htrdr_args_spectral* args, const char* str)
+{
+ char buf[128];
+ char* key;
+ char* val;
+ char* ctx;
+ res_T res = RES_OK;
+ ASSERT(args);
+
+ if(strlen(str) >= sizeof(buf) -1/*NULL char*/) {
+ fprintf(stderr,
+ "Could not duplicate the spectral option string `%s'.\n", str);
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ strncpy(buf, str, sizeof(buf));
+
+ key = strtok_r(buf, "=", &ctx);
+ val = strtok_r(NULL, "", &ctx);
+
+ if(!strcmp(key, "cie_xyz")) {
+ args->spectral_type = HTRDR_SPECTRAL_SW_CIE_XYZ;
+ args->wlen_range[0] = HTRDR_CIE_XYZ_RANGE_DEFAULT[0];
+ args->wlen_range[1] = HTRDR_CIE_XYZ_RANGE_DEFAULT[1];
+ } else {
+ if(!val) {
+ fprintf(stderr, "Missing value to the spectral option `%s'.\n", key);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ if(!strcmp(key, "sw")) {
+ args->spectral_type = HTRDR_SPECTRAL_SW;
+ res = parse_spectral_range(val, args->wlen_range);
+ if(res != RES_OK) goto error;
+ } else if(!strcmp(key, "lw")) {
+ args->spectral_type = HTRDR_SPECTRAL_LW;
+ res = parse_spectral_range(val, args->wlen_range);
+ if(res != RES_OK) goto error;
+ } else if(!strcmp(key, "Tref")) {
+ res = cstr_to_double(val, &args->ref_temperature);
+ if(res == RES_OK && args->ref_temperature < 0) res = RES_BAD_ARG;
+ if(res != RES_OK) {
+ fprintf(stderr, "Invalid reference temperature Tref=%s.\n", val);
+ goto error;
+ }
+ } else {
+ fprintf(stderr, "Invalid spectral parameter `%s'.\n", key);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+parse_multiple_parameters
+ (struct htrdr_args* args,
+ const char* str,
+ res_T (*parse_parameter)(struct htrdr_args* args, const char* str))
+{
+ char buf[512];
+ char* tk;
+ char* ctx;
+ res_T res = RES_OK;
+ ASSERT(args && str);
+
+ if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) {
+ fprintf(stderr, "Could not duplicate the option string `%s'.\n", str);
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ strncpy(buf, str, sizeof(buf));
+
+ tk = strtok_r(buf, ":", &ctx);
+ do {
+ res = parse_parameter(args, tk);
+ if(res != RES_OK) goto error;
+ tk = strtok_r(NULL, ":", &ctx);
+ } while(tk);
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_args_init(struct htrdr_args* args, int argc, char** argv)
+{
+ res_T res = RES_OK;
+ ASSERT(args && argc && argv);
+ *args = HTRDR_ARGS_DEFAULT;
+
+ /* Atmosphere mode */
+ if(!strcmp(argv[1], "atmosphere")) {
+ args->mode_type = HTRDR_ATMOSPHERE;
+ res = parse_atmosphere_options(args, argc, argv);
+ if(res != RES_OK) goto error;
+
+ /* Combustion mode */
+ } else if(!strcmp(argv[1], "combustion")) {
+ args->mode_type = HTRDR_COMBUSTION;
+ res = parse_combustion_options(args, argc, argv);
+ if(res != RES_OK) goto error;
+
+ /* Version */
+ } else if(!strcmp(argv[1], "--version")) {
+ printf("%s version %d.%d.%d\n",
+ argv[0],
+ HTRDR_VERSION_MAJOR,
+ HTRDR_VERSION_MINOR,
+ HTRDR_VERSION_PATCH);
+ args->quit = 1;
+ goto exit;
+
+ /* Help */
+ } else if(!strcmp(argv[1], "--help")) {
+ print_usage(argv[0]);
+ args->quit = 1;
+ goto exit;
+
+ /* Fallback */
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", argv[1]);
+ print_usage(argv[0]);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+exit:
+ return res;
+error:
+ htrdr_args_release(args);
+ goto exit;
+}
+
+void
+htrdr_args_release(struct htrdr_args* args)
+{
+ ASSERT(args);
+ *args = HTRDR_ARGS_DEFAULT;
+}
+
diff --git a/src/core/htrdr_args.h b/src/core/htrdr_args.h
@@ -0,0 +1,106 @@
+/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019, 2021 CNRS
+ * Copyright (C) 2018, 2019, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_ARGS_H
+#define HTRDR_ARGS_H
+
+#include "htrdr_cie_xyz.h"
+
+#include <rsys/rsys.h>
+
+/* Arguments of a pinhole camera sensor */
+struct htrdr_args_camera {
+ double position[3]; /* Focal point */
+ double target[3]; /* Targeted position */
+ double up[3]; /* Up vector of the camera */
+ double fov_y; /* Vertical field of view of the camera, in degrees */
+};
+#define HTRDR_ARGS_CAMERA_DEFAULT__ { \
+ {@HTRDR_ARGS_DEFAULT_CAMERA_POS@}, /* position */ \
+ {@HTRDR_ARGS_DEFAULT_CAMERA_TGT@}, /* target */ \
+ {@HTRDR_ARGS_DEFAULT_CAMERA_UP@}, /* Camera up */ \
+ @HTRDR_ARGS_DEFAULT_CAMERA_FOV@, /* Vertical field of view */ \
+}
+static const struct htrdr_args_camera HTRDR_ARGS_CAMERA_DEFAULT =
+ HTRDR_ARGS_CAMERA_DEFAULT__;
+
+/* Arguments of a rectangular sensor */
+struct htrdr_args_rectangle {
+ double position[3]; /* Center of the renctangle */
+ double target[3]; /* Targeted point (rectangle.normal = target - position) */
+ double up[3]; /* Up vector of the rectangle */
+ double size[2]; /* Plane size */
+};
+#define HTRDR_ARGS_RECTANGLE_DEFAULT__ { \
+ {@HTRDR_ARGS_DEFAULT_RECTANGLE_POS@}, /* Rectangle center */ \
+ {@HTRDR_ARGS_DEFAULT_RECTANGLE_TGT@}, /* Rectangle target */ \
+ {@HTRDR_ARGS_DEFAULT_RECTANGLE_UP@}, /* Rectangle up */ \
+ {@HTRDR_ARGS_DEFAULT_RECTANGLE_SZ@}, /* Rectangle size */ \
+}
+static const struct htrdr_args_rectangle HTRDR_ARGS_RECTANGLE_DEFAULT =
+ HTRDR_ARGS_RECTANGLE_DEFAULT__;
+
+/* Arguments of an image */
+struct htrdr_args_image {
+ unsigned definition[2]; /* #pixels in X and Y */
+ unsigned spp; /* #samples per pixel */
+};
+#define HTRDR_ARGS_IMAGE_DEFAULT__ { \
+ {@HTRDR_ARGS_DEFAULT_IMG_WIDTH@, @HTRDR_ARGS_DEFAULT_IMG_HEIGHT@}, \
+ @HTRDR_ARGS_DEFAULT_IMG_SPP@ \
+}
+static const struct htrdr_args_image HTRDR_ARGS_IMAGE_DEFAULT =
+ HTRDR_ARGS_IMAGE_DEFAULT__;
+
+/* Arguments of the spectral domain */
+struct htrdr_args_spectral {
+ double wlen_range[2]; /* Spectral range of integration in nm */
+ double ref_temperature; /* Planck reference temperature in Kelvin */
+ enum htrdr_spectral_type spectral_type;
+};
+#define HTRDR_ARGS_SPECTRAL_DEFAULT__ { \
+ HTRDR_CIE_XYZ_RANGE_DEFAULT__, /* Spectral range */ \
+ -1, /* Reference temperature */ \
+ HTRDR_SPECTRAL_SW_CIE_XYZ, /* Spectral type */ \
+}
+static const struct htrdr_args_spectral HTRDR_ARGS_SPECTRAL_DEFAULT =
+ HTRDR_ARGS_SPECTRAL_DEFAULT__;
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+HTRDR_API res_T
+htrdr_args_camera_parse
+ (struct htrdr_args_camera* cam,
+ const char* str);
+
+HTRDR_API res_T
+htrdr_args_rectangle_parse
+ (struct htrdr_args_rectangle* rect,
+ const char* str);
+
+HTRDR_API res_T
+htrdr_args_image_parse
+ (struct htrdr_args_image* img,
+ const char* str);
+
+HTRDR_API res_T
+htrdr_args_spectral_parse
+ (struct htrdr_args_spectral* spectral,
+ const char* str);
+
+#endif /* HTRDR_ARGS_H */
diff --git a/src/core/htrdr_buffer.c b/src/core/htrdr_buffer.c
@@ -0,0 +1,170 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr.h"
+#include "htrdr_buffer.h"
+
+#include <rsys/mem_allocator.h>
+#include <rsys/ref_count.h>
+
+struct htrdr_buffer {
+ char* mem;
+
+ size_t width;
+ size_t height;
+ size_t pitch;
+ size_t elmtsz;
+ size_t align;
+
+ struct htrdr* htrdr;
+ ref_T ref;
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static void
+buffer_release(ref_T* ref)
+{
+ struct htrdr_buffer* buf = NULL;
+ struct htrdr* htrdr = NULL;
+ ASSERT(ref);
+ buf = CONTAINER_OF(ref, struct htrdr_buffer, ref);
+ if(buf->mem) MEM_RM(buf->htrdr->allocator, buf->mem);
+ htrdr = buf->htrdr;
+ MEM_RM(htrdr_get_allocator(htrdr), buf);
+ htrdr_ref_put(htrdr);
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_buffer_create
+ (struct htrdr* htrdr,
+ const size_t width,
+ const size_t height,
+ const size_t pitch,
+ const size_t elmtsz,
+ const size_t align,
+ struct htrdr_buffer** out_buf)
+{
+ struct htrdr_buffer* buf = NULL;
+ size_t memsz = 0;
+ res_T res = RES_OK;
+ ASSERT(htrdr && out_buf);
+
+ if(!width || !height) {
+ htrdr_log_err(htrdr, "invalid buffer definition %lux%lu.\n",
+ (unsigned long)width, (unsigned long)height);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ if(pitch < width*elmtsz) {
+ htrdr_log_err(htrdr,
+ "invalid buffer pitch `%lu' wrt the buffer width `%lu'. "
+ "The buffer pitch cannot be less than the buffer width.\n",
+ (unsigned long)pitch, (unsigned long)width);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ if(!elmtsz) {
+ htrdr_log_err(htrdr,
+ "the size of the buffer's elements cannot be null.\n");
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ if(!IS_POW2(align)) {
+ htrdr_log_err(htrdr,
+ "invalid buffer alignment `%lu'. It must be a power of 2.\n",
+ (unsigned long)align);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ buf = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*buf));
+ if(!buf) {
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ ref_init(&buf->ref);
+ buf->width = width;
+ buf->height = height;
+ buf->pitch = pitch;
+ buf->elmtsz = elmtsz;
+ buf->align = align;
+ htrdr_ref_get(htrdr);
+ buf->htrdr = htrdr;
+
+ memsz = buf->pitch * buf->height;
+ buf->mem = MEM_ALLOC_ALIGNED(htrdr->allocator, memsz, align);
+ if(!buf->mem) {
+ res = RES_MEM_ERR;
+ goto error;
+ }
+
+exit:
+ *out_buf = buf;
+ return res;
+error:
+ if(buf) {
+ htrdr_buffer_ref_put(buf);
+ buf = NULL;
+ }
+ goto exit;
+}
+
+void
+htrdr_buffer_ref_get(struct htrdr_buffer* buf)
+{
+ ASSERT(buf);
+ ref_get(&buf->ref);
+}
+
+void
+htrdr_buffer_ref_put(struct htrdr_buffer* buf)
+{
+ ASSERT(buf);
+ ref_put(&buf->ref, buffer_release);
+}
+
+void
+htrdr_buffer_get_layout
+ (const struct htrdr_buffer* buf,
+ struct htrdr_buffer_layout* layout)
+{
+ ASSERT(buf && layout);
+ layout->width = buf->width;
+ layout->height = buf->height;
+ layout->pitch = buf->pitch;
+ layout->elmt_size = buf->elmtsz;
+ layout->alignment = buf->align;
+}
+
+void*
+htrdr_buffer_get_data(struct htrdr_buffer* buf)
+{
+ ASSERT(buf);
+ return buf->mem;
+}
+
+void*
+htrdr_buffer_at(struct htrdr_buffer* buf, const size_t x, const size_t y)
+{
+ ASSERT(buf && x < buf->width && y < buf->height);
+ return buf->mem + y*buf->pitch + x*buf->elmtsz;
+}
+
diff --git a/src/core/htrdr_buffer.h b/src/core/htrdr_buffer.h
@@ -0,0 +1,79 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_BUFFER_H
+#define HTRDR_BUFFER_H
+
+#include "htrdr.h"
+#include <rsys/rsys.h>
+
+/*
+ * Row major ordered 2D buffer
+ */
+
+struct htrdr_buffer_layout {
+ size_t width; /* #elements in X */
+ size_t height; /* #elements in Y */
+ size_t pitch; /* #Bytes between 2 consecutive line */
+ size_t elmt_size; /* Size of an element in the buffer */
+ size_t alignment; /* Alignement of the memory */
+};
+#define HTRDR_BUFFER_LAYOUT_NULL__ {0,0,0,0,0}
+static const struct htrdr_buffer_layout HTRDR_BUFFER_LAYOUT_NULL =
+ HTRDR_BUFFER_LAYOUT_NULL__;
+
+/* Forward declarations */
+struct htrdr;
+struct htrdr_buffer;
+
+BEGIN_DECLS
+
+HTRDR_API res_T
+htrdr_buffer_create
+ (struct htrdr* htrdr,
+ const size_t width,
+ const size_t height,
+ const size_t pitch, /* #Bytes between 2 consecutive line */
+ const size_t elmt_size, /* Size of an element in the buffer */
+ const size_t alignment, /* Alignement of the buffer */
+ struct htrdr_buffer** buf);
+
+HTRDR_API void
+htrdr_buffer_ref_get
+ (struct htrdr_buffer* buf);
+
+HTRDR_API void
+htrdr_buffer_ref_put
+ (struct htrdr_buffer* buf);
+
+HTRDR_API void
+htrdr_buffer_get_layout
+ (const struct htrdr_buffer* buf,
+ struct htrdr_buffer_layout* layout);
+
+HTRDR_API void*
+htrdr_buffer_get_data
+ (struct htrdr_buffer* buf);
+
+HTRDR_API void*
+htrdr_buffer_at
+ (struct htrdr_buffer* buf,
+ const size_t x,
+ const size_t y);
+
+END_DECLS
+
+#endif /* HTRDR_BUFFER_H */
diff --git a/src/core/htrdr_c.h b/src/core/htrdr_c.h
@@ -0,0 +1,177 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_C_H
+#define HTRDR_C_H
+
+#include <rsys/logger.h>
+#include <rsys/ref_count.h>
+#include <rsys/rsys.h>
+
+#ifndef NDEBUG
+ #define MPI(Func) ASSERT(MPI_##Func == MPI_SUCCESS)
+#else
+ #define MPI(Func) MPI_##Func
+#endif
+
+enum htrdr_mpi_message {
+ HTRDR_MPI_PROGRESS_RENDERING,
+ HTRDR_MPI_STEAL_REQUEST,
+ HTRDR_MPI_WORK_STEALING,
+ HTRDR_MPI_TILE_DATA
+};
+
+struct htrdr {
+ unsigned nthreads; /* #threads of the process */
+
+ int mpi_rank; /* Rank of the process in the MPI group */
+ int mpi_nprocs; /* Overall #processes in the MPI group */
+ char* mpi_err_str; /* Temp buffer used to store MPI error string */
+ int8_t* mpi_working_procs; /* Define the rank of active processes */
+ size_t mpi_nworking_procs;
+
+ /* Process progress percentage */
+ int32_t* mpi_progress_octree;
+ int32_t* mpi_progress_render;
+
+ struct mutex* mpi_mutex; /* Protect MPI calls from concurrent threads */
+
+ int verbose;
+
+ struct logger logger;
+ struct mem_allocator* allocator;
+ struct mem_allocator* lifo_allocators; /* Per thread lifo allocator */
+
+ ref_T ref;
+};
+
+/* In nanometer */
+static FINLINE double
+wavenumber_to_wavelength(const double nu/*In cm^-1*/)
+{
+ return 1.e7 / nu;
+}
+
+/* In cm^-1 */
+static FINLINE double
+wavelength_to_wavenumber(const double lambda/*In nanometer*/)
+{
+ return wavenumber_to_wavelength(lambda);
+}
+
+static INLINE uint64_t
+morton3D_encode_u21(const uint32_t u21)
+{
+ uint64_t u64 = u21 & ((1<<21) - 1);
+ ASSERT(u21 <= ((1 << 21) - 1));
+ u64 = (u64 | (u64 << 32)) & 0xFFFF00000000FFFF;
+ u64 = (u64 | (u64 << 16)) & 0x00FF0000FF0000FF;
+ u64 = (u64 | (u64 << 8)) & 0xF00F00F00F00F00F;
+ u64 = (u64 | (u64 << 4)) & 0x30C30C30C30C30C3;
+ u64 = (u64 | (u64 << 2)) & 0x9249249249249249;
+ return u64;
+}
+
+static INLINE uint32_t
+morton3D_decode_u21(const uint64_t u64)
+{
+ uint64_t tmp = (u64 & 0x9249249249249249);
+ tmp = (tmp | (tmp >> 2)) & 0x30C30C30C30C30C3;
+ tmp = (tmp | (tmp >> 4)) & 0xF00F00F00F00F00F;
+ tmp = (tmp | (tmp >> 8)) & 0x00FF0000FF0000FF;
+ tmp = (tmp | (tmp >> 16)) & 0xFFFF00000000FFFF;
+ tmp = (tmp | (tmp >> 32)) & 0x00000000FFFFFFFF;
+ ASSERT(tmp <= ((1<<21)-1));
+ return (uint32_t)tmp;
+}
+
+static INLINE uint64_t
+morton_xyz_encode_u21(const uint32_t xyz[3])
+{
+ return (morton3D_encode_u21(xyz[0]) << 2)
+ | (morton3D_encode_u21(xyz[1]) << 1)
+ | (morton3D_encode_u21(xyz[2]) << 0);
+}
+
+static INLINE void
+morton_xyz_decode_u21(const uint64_t code, uint32_t xyz[3])
+{
+ ASSERT(xyz && code < ((1ull << 63)-1));
+ xyz[0] = (uint32_t)morton3D_decode_u21(code >> 2);
+ xyz[1] = (uint32_t)morton3D_decode_u21(code >> 1);
+ xyz[2] = (uint32_t)morton3D_decode_u21(code >> 0);
+}
+
+/* Return the minimum length in nanometer of the sky spectral bands
+ * clamped to in [range[0], range[1]]. */
+extern LOCAL_SYM double
+compute_sky_min_band_len
+ (struct htsky* sky,
+ const double range[2]);
+
+extern LOCAL_SYM res_T
+open_output_stream
+ (struct htrdr* htrdr,
+ const char* filename,
+ const int read, /* Enable read access */
+ int force_overwrite,
+ FILE** out_fp);
+
+extern LOCAL_SYM void
+send_mpi_progress
+ (struct htrdr* htrdr,
+ const enum htrdr_mpi_message progress,
+ const int32_t percent);
+
+extern LOCAL_SYM void
+fetch_mpi_progress
+ (struct htrdr* htrdr,
+ const enum htrdr_mpi_message progress);
+
+extern LOCAL_SYM void
+print_mpi_progress
+ (struct htrdr* htrdr,
+ const enum htrdr_mpi_message progress);
+
+extern LOCAL_SYM void
+clear_mpi_progress
+ (struct htrdr* htrdr,
+ const enum htrdr_mpi_message progress);
+
+extern int32_t
+total_mpi_progress
+ (const struct htrdr* htrdr,
+ const enum htrdr_mpi_message progress);
+
+static INLINE void
+update_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message progress)
+{
+ ASSERT(htrdr);
+ fetch_mpi_progress(htrdr, progress);
+ clear_mpi_progress(htrdr, progress);
+ print_mpi_progress(htrdr, progress);
+}
+
+static FINLINE int
+cmp_dbl(const void* a, const void* b)
+{
+ const double d0 = *((const double*)a);
+ const double d1 = *((const double*)b);
+ return d0 < d1 ? -1 : (d0 > d1 ? 1 : 0);
+}
+
+#endif /* HTRDR_C_H */
+
diff --git a/src/core/htrdr_camera.c b/src/core/htrdr_camera.c
@@ -0,0 +1,155 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr.h"
+#include "htrdr_camera.h"
+
+#include <rsys/double3.h>
+#include <rsys/mem_allocator.h>
+#include <rsys/ref_count.h>
+
+struct htrdr_camera {
+ /* Orthogonal basis of the camera */
+ double axis_x[3];
+ double axis_y[3];
+ double axis_z[3];
+
+ double position[3];
+
+ ref_T ref;
+ struct htrdr* htrdr;
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static void
+camera_release(ref_T* ref)
+{
+ struct htrdr_camera* cam;
+ struct htrdr* htrdr;
+ ASSERT(ref);
+ cam = CONTAINER_OF(ref, struct htrdr_camera, ref);
+ htrdr = cam->htrdr;
+ MEM_RM(htrdr_get_allocator(htrdr), cam);
+ htrdr_ref_put(htrdr);
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_camera_create
+ (struct htrdr* htrdr,
+ const double position[3],
+ const double target[3],
+ const double up[3],
+ const double proj_ratio,
+ const double fov, /* In radian */
+ struct htrdr_camera** out_cam)
+{
+ double x[3], y[3], z[3];
+ double img_plane_depth;
+ struct htrdr_camera* cam = NULL;
+ res_T res = RES_OK;
+ ASSERT(htrdr && position && target && up && out_cam);
+
+ cam = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*cam));
+ if(!cam) {
+ htrdr_log_err(htrdr, "could not allocate the camera data structure.\n");
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ ref_init(&cam->ref);
+ htrdr_ref_get(htrdr);
+ cam->htrdr = htrdr;
+
+ if(fov <= 0 || fov >= PI) {
+ htrdr_log_err(htrdr, "invalid horizontal camera field of view `%g'\n", fov);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ if(proj_ratio <= 0) {
+ htrdr_log_err(htrdr, "invalid projection ratio `%g'\n", proj_ratio);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ if(d3_normalize(z, d3_sub(z, target, position)) <= 0
+ || d3_normalize(x, d3_cross(x, z, up)) <= 0
+ || d3_normalize(y, d3_cross(y, z, x)) <= 0) {
+ htrdr_log_err(htrdr,
+ "invalid camera point of view:\n"
+ " position = %g %g %g\n"
+ " target = %g %g %g\n"
+ " up = %g %g %g\n",
+ SPLIT3(position), SPLIT3(target), SPLIT3(up));
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ img_plane_depth = 1.0/tan(fov*0.5);
+ d3_muld(cam->axis_x, x, proj_ratio);
+ d3_set(cam->axis_y, y);
+ d3_muld(cam->axis_z, z, img_plane_depth);
+ d3_set(cam->position, position);
+
+exit:
+ *out_cam = cam;
+ return res;
+error:
+ if(cam) {
+ htrdr_camera_ref_put(cam);
+ cam = NULL;
+ }
+ goto exit;
+}
+
+void
+htrdr_camera_ref_get(struct htrdr_camera* cam)
+{
+ ASSERT(cam);
+ ref_get(&cam->ref);
+}
+
+void
+htrdr_camera_ref_put(struct htrdr_camera* cam)
+{
+ ASSERT(cam);
+ ref_put(&cam->ref, camera_release);
+}
+
+void
+htrdr_camera_ray
+ (const struct htrdr_camera* cam,
+ const double sample[2],
+ double ray_org[3],
+ double ray_dir[3])
+{
+ double x[3], y[3], len;
+ (void)len;
+ ASSERT(cam && sample && ray_org && ray_dir);
+ ASSERT(sample[0] >= 0 || sample[0] < 1);
+ ASSERT(sample[1] >= 0 || sample[1] < 1);
+ d3_muld(x, cam->axis_x, sample[0]*2-1);
+ d3_muld(y, cam->axis_y, sample[1]*2-1);
+ d3_add(ray_dir, d3_add(ray_dir, x, y), cam->axis_z);
+ len = d3_normalize(ray_dir, ray_dir);
+ ASSERT(len >= 1.e-6);
+ d3_set(ray_org, cam->position);
+}
+
diff --git a/src/core/htrdr_camera.h b/src/core/htrdr_camera.h
@@ -0,0 +1,57 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_CAMERA_H
+#define HTRDR_CAMERA_H
+
+#include "htrdr.h"
+#include <rsys/rsys.h>
+
+/* Forward declarations */
+struct htrdr;
+struct htrdr_camera;
+
+BEGIN_DECLS
+
+HTRDR_API res_T
+htrdr_camera_create
+ (struct htrdr* htrdr,
+ const double position[3],
+ const double target[3],
+ const double up[3],
+ const double proj_ratio, /* Width / Height */
+ const double fov, /* In radian */
+ struct htrdr_camera** cam);
+
+HTRDR_API void
+htrdr_camera_ref_get
+ (struct htrdr_camera* cam);
+
+HTRDR_API void
+htrdr_camera_ref_put
+ (struct htrdr_camera* cam);
+
+HTRDR_API void
+htrdr_camera_ray
+ (const struct htrdr_camera* cam,
+ const double sample[2], /* In [0, 1[ */
+ double ray_org[3],
+ double ray_dir[3]);
+
+END_DECLS
+
+#endif /* HTRDR_CAMERA_H */
+
diff --git a/src/core/htrdr_cie_xyz.c b/src/core/htrdr_cie_xyz.c
@@ -0,0 +1,399 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define _POSIX_C_SOURCE 200112L /* nextafter */
+
+#include "htrdr.h"
+#include "htrdr_cie_xyz.h"
+
+#include <high_tune/htsky.h>
+
+#include <rsys/algorithm.h>
+#include <rsys/cstr.h>
+#include <rsys/dynamic_array_double.h>
+#include <rsys/mem_allocator.h>
+#include <rsys/ref_count.h>
+
+#include <math.h> /* nextafter */
+
+struct htrdr_cie_xyz {
+ struct darray_double cdf_X;
+ struct darray_double cdf_Y;
+ struct darray_double cdf_Z;
+ double rcp_integral_X;
+ double rcp_integral_Y;
+ double rcp_integral_Z;
+ double range[2]; /* Boundaries of the handled CIE XYZ color space */
+ double band_len; /* Length in nanometers of a band */
+
+ struct htrdr* htrdr;
+ ref_T ref;
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static INLINE double
+trapezoidal_integration
+ (const double lambda_lo, /* Integral lower bound. In nanometer */
+ const double lambda_hi, /* Integral upper bound. In nanometer */
+ double (*f_bar)(const double lambda)) /* Function to integrate */
+{
+ double dlambda;
+ size_t i, n;
+ double integral = 0;
+ ASSERT(lambda_lo <= lambda_hi);
+ ASSERT(lambda_lo > 0);
+
+ n = (size_t)(lambda_hi - lambda_lo) + 1;
+ dlambda = (lambda_hi - lambda_lo) / (double)n;
+
+ FOR_EACH(i, 0, n) {
+ const double lambda1 = lambda_lo + dlambda*(double)(i+0);
+ const double lambda2 = lambda_lo + dlambda*(double)(i+1);
+ const double f1 = f_bar(lambda1);
+ const double f2 = f_bar(lambda2);
+ integral += (f1 + f2)*dlambda*0.5;
+ }
+ return integral;
+}
+
+/* The following 3 functions are used to fit the CIE Xbar, Ybar and Zbar curved
+ * has defined by the 1931 standard. These analytical fits are propsed by C.
+ * Wyman, P. P. Sloan & P. Shirley in "Simple Analytic Approximations to the
+ * CIE XYZ Color Matching Functions" - JCGT 2013. */
+static INLINE double
+fit_x_bar_1931(const double lambda)
+{
+ const double a = (lambda - 442.0) * (lambda < 442.0 ? 0.0624 : 0.0374);
+ const double b = (lambda - 599.8) * (lambda < 599.8 ? 0.0264 : 0.0323);
+ const double c = (lambda - 501.1) * (lambda < 501.1 ? 0.0490 : 0.0382);
+ return 0.362*exp(-0.5*a*a) + 1.056*exp(-0.5f*b*b) - 0.065*exp(-0.5*c*c);
+}
+
+static FINLINE double
+fit_y_bar_1931(const double lambda)
+{
+ const double a = (lambda - 568.8) * (lambda < 568.8 ? 0.0213 : 0.0247);
+ const double b = (lambda - 530.9) * (lambda < 530.9 ? 0.0613 : 0.0322);
+ return 0.821*exp(-0.5*a*a) + 0.286*exp(-0.5*b*b);
+}
+
+static FINLINE double
+fit_z_bar_1931(const double lambda)
+{
+ const double a = (lambda - 437.0) * (lambda < 437.0 ? 0.0845 : 0.0278);
+ const double b = (lambda - 459.0) * (lambda < 459.0 ? 0.0385 : 0.0725);
+ return 1.217*exp(-0.5*a*a) + 0.681*exp(-0.5*b*b);
+}
+
+static INLINE double
+sample_cie_xyz
+ (const struct htrdr_cie_xyz* cie,
+ const double* cdf,
+ const size_t cdf_length,
+ double (*f_bar)(const double lambda), /* Function to integrate */
+ const double r0, /* Canonical number in [0, 1[ */
+ const double r1) /* Canonical number in [0, 1[ */
+{
+ double r0_next = nextafter(r0, DBL_MAX);
+ double* find;
+ double f_min, f_max; /* CIE 1931 value for the band boundaries */
+ double lambda; /* Sampled wavelength */
+ double lambda_min, lambda_max; /* Boundaries of the sampled band */
+ double lambda_1, lambda_2; /* Solutions if the equation to solve */
+ double a, b, c, d; /* Equation parameters */
+ double delta, sqrt_delta;
+ size_t iband; /* Index of the sampled band */
+ ASSERT(cie && cdf && cdf_length);
+ ASSERT(0 <= r0 && r0 < 1);
+ ASSERT(0 <= r1 && r1 < 1);
+
+ /* Use r_next rather than r in order to find the first entry that is not less
+ * than *or equal* to r */
+ find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl);
+ ASSERT(find);
+
+ /* Define and check the sampled band */
+ iband = (size_t)(find - cdf);
+ ASSERT(iband < cdf_length);
+ ASSERT(cdf[iband] > r0 && (!iband || cdf[iband-1] <= r0));
+
+ /* Define the boundaries of the sampled band */
+ lambda_min = cie->range[0] + cie->band_len * (double)iband;
+ lambda_max = lambda_min + cie->band_len;
+
+ /* Define the value of the CIE 1931 function for the boudaries of the sampled
+ * band */
+ f_min = f_bar(lambda_min);
+ f_max = f_bar(lambda_max);
+
+ /* Compute the equation constants */
+ a = 0.5 * (f_max - f_min) / cie->band_len;
+ b = (lambda_max * f_min - lambda_min * f_max) / cie->band_len;
+ c = -lambda_min * f_min + lambda_min*lambda_min * a;
+ d = 0.5 * (f_max + f_min) * cie->band_len;
+
+ delta = b*b - 4*a*(c-d*r1);
+ if(delta < 0 && eq_eps(delta, 0, 1.e-6)) {
+ delta = 0;
+ }
+ ASSERT(delta > 0);
+ sqrt_delta = sqrt(delta);
+
+ /* Compute the roots that solve the equation */
+ lambda_1 = (-b - sqrt_delta) / (2*a);
+ lambda_2 = (-b + sqrt_delta) / (2*a);
+
+ /* Select the solution */
+ if(lambda_min <= lambda_1 && lambda_1 < lambda_max) {
+ lambda = lambda_1;
+ } else if(lambda_min <= lambda_2 && lambda_2 < lambda_max) {
+ lambda = lambda_2;
+ } else {
+ htrdr_log_warn(cie->htrdr,
+ "%s: cannot sample a wavelength in [%g, %g[. The possible wavelengths"
+ "were %g and %g.\n",
+ FUNC_NAME, lambda_min, lambda_max, lambda_1, lambda_2);
+ /* Arbitrarly choose the wavelength at the center of the sampled band */
+ lambda = (lambda_min + lambda_max)*0.5;
+ }
+
+ return lambda;
+}
+
+static res_T
+setup_cie_xyz
+ (struct htrdr_cie_xyz* cie,
+ const char* func_name,
+ const size_t nbands)
+{
+ enum { X, Y, Z }; /* Helper constant */
+ double* pdf[3] = {NULL, NULL, NULL};
+ double* cdf[3] = {NULL, NULL, NULL};
+ double sum[3] = {0, 0, 0};
+ size_t i;
+ res_T res = RES_OK;
+
+ ASSERT(cie && func_name && nbands);
+ ASSERT(cie->range[0] >= HTRDR_CIE_XYZ_RANGE_DEFAULT[0]);
+ ASSERT(cie->range[1] <= HTRDR_CIE_XYZ_RANGE_DEFAULT[1]);
+ ASSERT(cie->range[0] < cie->range[1]);
+
+ /* Allocate and reset the memory space for the tristimulus CDF */
+ #define SETUP_STIMULUS(Stimulus) { \
+ res = darray_double_resize(&cie->cdf_ ## Stimulus, nbands); \
+ if(res != RES_OK) { \
+ htrdr_log_err(cie->htrdr, \
+ "%s: Could not reserve the memory space for the CDF " \
+ "of the "STR(X)" stimulus -- %s.\n", func_name, res_to_cstr(res)); \
+ goto error; \
+ } \
+ cdf[Stimulus] = darray_double_data_get(&cie->cdf_ ## Stimulus); \
+ pdf[Stimulus] = cdf[Stimulus]; \
+ memset(cdf[Stimulus], 0, nbands*sizeof(double)); \
+ } (void)0
+ SETUP_STIMULUS(X);
+ SETUP_STIMULUS(Y);
+ SETUP_STIMULUS(Z);
+ #undef SETUP_STIMULUS
+
+ /* Compute the *unormalized* pdf of the tristimulus */
+ FOR_EACH(i, 0, nbands) {
+ const double lambda_lo = cie->range[0] + (double)i * cie->band_len;
+ const double lambda_hi = MMIN(lambda_lo + cie->band_len, cie->range[1]);
+ ASSERT(lambda_lo <= lambda_hi);
+ ASSERT(lambda_lo >= cie->range[0]);
+ ASSERT(lambda_hi <= cie->range[1]);
+ pdf[X][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_x_bar_1931);
+ pdf[Y][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_y_bar_1931);
+ pdf[Z][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_z_bar_1931);
+ sum[X] += pdf[X][i];
+ sum[Y] += pdf[Y][i];
+ sum[Z] += pdf[Z][i];
+ }
+ #define CHK_SUM(Sum, Range, Fit) \
+ ASSERT(eq_eps(Sum, trapezoidal_integration(Range[0], Range[1], Fit), 1.e-3))
+ CHK_SUM(sum[X], cie->range, fit_x_bar_1931);
+ CHK_SUM(sum[Y], cie->range, fit_y_bar_1931);
+ CHK_SUM(sum[Z], cie->range, fit_z_bar_1931);
+ #undef CHK_SUM
+ cie->rcp_integral_X = 1.0 / sum[X];
+ cie->rcp_integral_Y = 1.0 / sum[Y];
+ cie->rcp_integral_Z = 1.0 / sum[Z];
+
+ FOR_EACH(i, 0, nbands) {
+ /* Normalize the pdf */
+ pdf[X][i] /= sum[X];
+ pdf[Y][i] /= sum[Y];
+ pdf[Z][i] /= sum[Z];
+ /* Setup the cumulative */
+ if(i == 0) {
+ cdf[X][i] = pdf[X][i];
+ cdf[Y][i] = pdf[Y][i];
+ cdf[Z][i] = pdf[Z][i];
+ } else {
+ cdf[X][i] = pdf[X][i] + cdf[X][i-1];
+ cdf[Y][i] = pdf[Y][i] + cdf[Y][i-1];
+ cdf[Z][i] = pdf[Z][i] + cdf[Z][i-1];
+ ASSERT(cdf[X][i] >= cdf[X][i-1]);
+ ASSERT(cdf[Y][i] >= cdf[Y][i-1]);
+ ASSERT(cdf[Z][i] >= cdf[Z][i-1]);
+ }
+ }
+ ASSERT(eq_eps(cdf[X][nbands-1], 1, 1.e-6));
+ ASSERT(eq_eps(cdf[Y][nbands-1], 1, 1.e-6));
+ ASSERT(eq_eps(cdf[Z][nbands-1], 1, 1.e-6));
+
+ /* Handle numerical issue */
+ cdf[X][nbands-1] = 1.0;
+ cdf[Y][nbands-1] = 1.0;
+ cdf[Z][nbands-1] = 1.0;
+
+exit:
+ return res;
+error:
+ darray_double_clear(&cie->cdf_X);
+ darray_double_clear(&cie->cdf_Y);
+ darray_double_clear(&cie->cdf_Z);
+ goto exit;
+}
+
+static void
+release_cie_xyz(ref_T* ref)
+{
+ struct htrdr_cie_xyz* cie = NULL;
+ struct htrdr* htrdr = NULL;
+ ASSERT(ref);
+ cie = CONTAINER_OF(ref, struct htrdr_cie_xyz, ref);
+ darray_double_release(&cie->cdf_X);
+ darray_double_release(&cie->cdf_Y);
+ darray_double_release(&cie->cdf_Z);
+ htrdr = cie->htrdr;
+ MEM_RM(htrdr_get_allocator(cie->htrdr), cie);
+ htrdr_ref_put(htrdr);
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_cie_xyz_create
+ (struct htrdr* htrdr,
+ const double range[2], /* Must be included in [380, 780] nanometers */
+ const size_t bands_count, /* # bands used to discretisze the CIE tristimulus */
+ struct htrdr_cie_xyz** out_cie)
+{
+ struct htrdr_cie_xyz* cie = NULL;
+ double min_band_len = 0;
+ size_t nbands = bands_count;
+ res_T res = RES_OK;
+ ASSERT(htrdr && range && nbands && out_cie);
+
+ cie = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*cie));
+ if(!cie) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "%s: could not allocate the CIE XYZ data structure -- %s.\n",
+ FUNC_NAME, res_to_cstr(res));
+ goto error;
+ }
+ ref_init(&cie->ref);
+ darray_double_init(htrdr->allocator, &cie->cdf_X);
+ darray_double_init(htrdr->allocator, &cie->cdf_Y);
+ darray_double_init(htrdr->allocator, &cie->cdf_Z);
+ cie->range[0] = range[0];
+ cie->range[1] = range[1];
+ htrdr_ref_get(htrdr);
+ cie->htrdr = htrdr;
+
+ min_band_len = compute_sky_min_band_len(cie->htrdr->sky, range);
+ cie->band_len = (range[1] - range[0]) / (double)nbands;
+
+ /* Adjust the band length to ensure that each sky spectral interval is
+ * overlapped by at least one band */
+ if(cie->band_len > min_band_len) {
+ cie->band_len = min_band_len;
+ nbands = (size_t)ceil((range[1] - range[0]) / cie->band_len);
+ printf("%lu\n", nbands);
+ }
+
+ res = setup_cie_xyz(cie, FUNC_NAME, nbands);
+ if(res != RES_OK) goto error;
+
+ htrdr_log(htrdr, "CIE XYZ spectral interval defined on [%g, %g] nanometers.\n",
+ range[0], range[1]);
+
+exit:
+ *out_cie = cie;
+ return res;
+error:
+ if(cie) htrdr_cie_xyz_ref_put(cie);
+ goto exit;
+}
+
+void
+htrdr_cie_xyz_ref_get(struct htrdr_cie_xyz* cie)
+{
+ ASSERT(cie);
+ ref_get(&cie->ref);
+}
+
+void
+htrdr_cie_xyz_ref_put(struct htrdr_cie_xyz* cie)
+{
+ ASSERT(cie);
+ ref_put(&cie->ref, release_cie_xyz);
+}
+
+double
+htrdr_cie_xyz_sample_X
+ (struct htrdr_cie_xyz* cie,
+ const double r0,
+ const double r1,
+ double* pdf)
+{
+ const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_X),
+ darray_double_size_get(&cie->cdf_X), fit_x_bar_1931, r0, r1);
+ if(pdf) *pdf = cie->rcp_integral_X;
+ return wlen;
+}
+
+double
+htrdr_cie_xyz_sample_Y
+ (struct htrdr_cie_xyz* cie,
+ const double r0,
+ const double r1,
+ double* pdf)
+{
+ const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Y),
+ darray_double_size_get(&cie->cdf_Y), fit_y_bar_1931, r0, r1);
+ if(pdf) *pdf = cie->rcp_integral_Y;
+ return wlen;
+}
+
+double
+htrdr_cie_xyz_sample_Z
+ (struct htrdr_cie_xyz* cie,
+ const double r0,
+ const double r1,
+ double* pdf)
+{
+ const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Z),
+ darray_double_size_get(&cie->cdf_Z), fit_z_bar_1931, r0, r1);
+ if(pdf) *pdf = cie->rcp_integral_Z;
+ return wlen;
+}
+
diff --git a/src/core/htrdr_cie_xyz.h b/src/core/htrdr_cie_xyz.h
@@ -0,0 +1,72 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_CIE_XYZ_H
+#define HTRDR_CIE_XYZ_H
+
+#include "htrdr.h"
+#include <rsys/rsys.h>
+
+/* Wavelength boundaries of the CIE XYZ color space in nanometers */
+#define HTRDR_CIE_XYZ_RANGE_DEFAULT__ {380, 780}
+static const double HTRDR_CIE_XYZ_RANGE_DEFAULT[2] =
+ HTRDR_CIE_XYZ_RANGE_DEFAULT__;
+
+/* Forward declarations */
+struct htrdr;
+struct htrdr_cie_xyz;
+
+BEGIN_DECLS
+
+HTRDR_API res_T
+htrdr_cie_xyz_create
+ (struct htrdr* htrdr,
+ const double range[2], /* Must be included in [380, 780] nanometers */
+ const size_t nbands, /* # bands used to discretisze the CIE tristimulus s*/
+ struct htrdr_cie_xyz** cie);
+
+HTRDR_API void
+htrdr_cie_xyz_ref_get
+ (struct htrdr_cie_xyz* cie);
+
+HTRDR_API void
+htrdr_cie_xyz_ref_put
+ (struct htrdr_cie_xyz* cie);
+
+/* Return a wavelength in nanometer */
+HTRDR_API double
+htrdr_cie_xyz_sample_X
+ (struct htrdr_cie_xyz* cie,
+ const double r0, const double r1, /* Canonical numbers in [0, 1[ */
+ double* pdf); /* In nm^-1. May be NULL */
+
+/* Return a wavelength in nanometer */
+HTRDR_API double
+htrdr_cie_xyz_sample_Y
+ (struct htrdr_cie_xyz* cie,
+ const double r0, const double r1, /* Canonical number in [0, 1[ */
+ double* pdf); /* In nm^-1. May be NULL */
+
+/* Return a wavelength in nanometer */
+HTRDR_API double
+htrdr_cie_xyz_sample_Z
+ (struct htrdr_cie_xyz* cie,
+ const double r0, const double r1, /* Canonical number in [0, 1[ */
+ double* pdf); /* In nm^-1. May be NULL */
+
+END_DECLS
+
+#endif /* HTRDR_cie_xyz_H */
+
diff --git a/src/core/htrdr_draw_map.c b/src/core/htrdr_draw_map.c
@@ -0,0 +1,810 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define _POSIX_C_SOURCE 200112L /* nanosleep && nextafter */
+
+#include "htrdr.h"
+#include "htrdr_c.h"
+#include "htrdr_buffer.h"
+#include "htrdr_camera.h"
+#include "htrdr_cie_xyz.h"
+#include "htrdr_draw_map.h"
+#include "htrdr_ran_wlen.h"
+#include "htrdr_rectangle.h"
+#include "htrdr_solve.h"
+#include "htrdr_sun.h"
+
+#include <high_tune/htsky.h>
+
+#include <rsys/algorithm.h>
+#include <rsys/clock_time.h>
+#include <rsys/cstr.h>
+#include <rsys/dynamic_array_u32.h>
+#include <rsys/math.h>
+#include <rsys/mutex.h>
+#include <star/ssp.h>
+
+#include <omp.h>
+#include <mpi.h>
+#include <time.h>
+#include <unistd.h>
+
+#define RNG_SEQUENCE_SIZE 10000
+
+#define TILE_MCODE_NULL UINT32_MAX
+#define TILE_SIZE 32 /* Definition in X & Y of a tile */
+STATIC_ASSERT(IS_POW2(TILE_SIZE), TILE_SIZE_must_be_a_power_of_2);
+
+enum pixel_format {
+ PIXEL_FLUX,
+ PIXEL_IMAGE,
+ PIXEL_XWAVE
+};
+
+union pixel {
+ struct htrdr_pixel_flux flux;
+ struct htrdr_pixel_xwave xwave;
+ struct htrdr_pixel_image image;
+};
+
+/* Tile of row ordered image pixels */
+struct tile {
+ struct list_node node;
+ struct mem_allocator* allocator;
+ ref_T ref;
+
+ struct tile_data {
+ size_t pixsz; /* Sizeof on pixel */
+ size_t pixal; /* Pixel alignment */
+ uint16_t x, y; /* 2D coordinates of the tile in tile space */
+ /* Simulate the flexible array member of the C99 standard */
+ char ALIGN(16) pixels[1/*Dummy element*/];
+ } data;
+};
+
+/* List of tile to compute onto the MPI process. */
+struct proc_work {
+ struct mutex* mutex;
+ struct darray_u32 tiles; /* #tiles to render */
+ size_t itile; /* Next tile to render in the above list of tiles */
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static INLINE int
+check_draw_map_args(const struct htrdr_draw_map_args* args)
+{
+ return args && args->draw_pixel && args->spp;
+}
+
+static INLINE void
+tile_ref_get(struct tile* tile)
+{
+ ASSERT(tile);
+ tile_ref_get(tile);
+}
+
+static INLINE void
+release_tile(ref_T* ref)
+{
+ struct tile* tile = CONTAINER_OF(ref, struct tile, ref);
+ ASSERT(ref);
+ MEM_RM(tile->allocator, tile);
+}
+
+static INLINE void
+tile_ref_put(struct tile* tile)
+{
+ ASSERT(tile);
+ ref_put(&tile->ref, release_tile);
+}
+
+static FINLINE struct tile*
+tile_create
+ (struct mem_allocator* allocator,
+ const size_t pixel_size,
+ const size_t pixel_alignment)
+{
+ struct tile* tile = NULL;
+ const size_t tile_sz = sizeof(*tile) - 1/* rm dummy octet in flexible array */;
+ const size_t buf_sz = TILE_SIZE*TILE_SIZE*pixel_size;
+ ASSERT(allocator);
+ ASSERT(IS_ALIGNED(pixel_size, pixel_alignment));
+ ASSERT(is_pow2(pixel_alignment));
+
+ tile = MEM_ALLOC_ALIGNED(allocator, tile_sz + buf_sz, 16);
+ if(!tile) goto error;
+ ref_init(&tile->ref);
+ list_init(&tile->node);
+ tile->allocator = allocator;
+ tile->data.pixsz = pixel_size;
+ tile->data.pixal = pixel_alignment;
+ tile->data.x = 0;
+ tile->data.y = 0;
+ CHK(IS_ALIGNED(tile->data.pixels, pixel_alignment));
+
+exit:
+ return tile;
+error:
+ if(tile) {
+ tile_ref_put(tile);
+ tile = NULL;
+ }
+ goto exit;
+}
+
+static FINLINE union pixel*
+tile_at
+ (struct tile* tile,
+ const size_t x, /* In tile space */
+ const size_t y) /* In tile space */
+{
+ ASSERT(tile && x < TILE_SIZE && y < TILE_SIZE);
+ return tile->data.pixels + (y*TILE_SIZE + x)*tile->data.pixsz;
+}
+
+static void
+write_tile_data
+ (struct htrdr* htrdr,
+ const struct htrdr_sensor* sensor,
+ struct htrdr_buffer* buf,
+ const struct tile_data* tile_data)
+{
+ struct htrdr_buffer_layout layout = HTRDR_BUFFER_LAYOUT_NULL;
+ size_t icol, irow;
+ size_t irow_tile;
+ size_t ncols_tile, nrows_tile;
+ size_t tile_pitch;
+ char* buf_mem;
+ ASSERT(htrdr && sensor && buf && tile_data);
+ (void)htrdr, (void)sensor;
+
+ htrdr_buffer_get_layout(buf, &layout);
+ buf_mem = htrdr_buffer_get_data(buf);
+ ASSERT(layout.elmt_size == tile_data->pixsz);
+
+ /* Compute the row/column of the tile origin into the buffer */
+ icol = tile_data->x * (size_t)TILE_SIZE;
+ irow = tile_data->y * (size_t)TILE_SIZE;
+
+ /* Define the number of tile row/columns to write into the buffer */
+ ncols_tile = MMIN(icol + TILE_SIZE, layout.width) - icol;
+ nrows_tile = MMIN(irow + TILE_SIZE, layout.height) - irow;
+
+ tile_pitch = TILE_SIZE * tile_data->pixsz;
+
+ /* Copy the row ordered tile data */
+ FOR_EACH(irow_tile, 0, nrows_tile) {
+ char* buf_row = buf_mem + (irow + irow_tile) * layout.pitch;
+ char* dst_tile_row = buf_row + icol * layout.elmt_size;
+ const char* src_tile_row = tile_data->pixels + irow_tile*tile_pitch;
+
+ memcpy(dst_tile_row, src_tile_row, tile_pitch);
+ }
+}
+
+static INLINE void
+proc_work_init(struct mem_allocator* allocator, struct proc_work* work)
+{
+ ASSERT(work);
+ darray_u32_init(allocator, &work->tiles);
+ work->itile = 0;
+ CHK(work->mutex = mutex_create());
+}
+
+static INLINE void
+proc_work_release(struct proc_work* work)
+{
+ darray_u32_release(&work->tiles);
+ mutex_destroy(work->mutex);
+}
+
+static INLINE void
+proc_work_reset(struct proc_work* work)
+{
+ ASSERT(work);
+ mutex_lock(work->mutex);
+ darray_u32_clear(&work->tiles);
+ work->itile = 0;
+ mutex_unlock(work->mutex);
+}
+
+static INLINE void
+proc_work_add_tile(struct proc_work* work, const uint32_t mcode)
+{
+ mutex_lock(work->mutex);
+ CHK(darray_u32_push_back(&work->tiles, &mcode) == RES_OK);
+ mutex_unlock(work->mutex);
+}
+
+static INLINE uint32_t
+proc_work_get_tile(struct proc_work* work)
+{
+ uint32_t mcode;
+ ASSERT(work);
+ mutex_lock(work->mutex);
+ if(work->itile >= darray_u32_size_get(&work->tiles)) {
+ mcode = TILE_MCODE_NULL;
+ } else {
+ mcode = darray_u32_cdata_get(&work->tiles)[work->itile];
+ ++work->itile;
+ }
+ mutex_unlock(work->mutex);
+ return mcode;
+}
+
+static INLINE size_t
+proc_work_get_ntiles(struct proc_work* work)
+{
+ size_t sz = 0;
+ ASSERT(work);
+ mutex_lock(work->mutex);
+ sz = darray_u32_size_get(&work->tiles);
+ mutex_unlock(work->mutex);
+ return sz;
+}
+
+static void
+mpi_wait_for_request(struct htrdr* htrdr, MPI_Request* req)
+{
+ ASSERT(htrdr && req);
+
+ /* Wait for process synchronisation */
+ for(;;) {
+ struct timespec t;
+ int complete;
+ t.tv_sec = 0;
+ t.tv_nsec = 10000000; /* 10ms */
+
+ mutex_lock(htrdr->mpi_mutex);
+ MPI(Test(req, &complete, MPI_STATUS_IGNORE));
+ mutex_unlock(htrdr->mpi_mutex);
+ if(complete) break;
+
+ nanosleep(&t, NULL);
+ }
+}
+
+static void
+mpi_probe_thieves
+ (struct htrdr* htrdr,
+ struct proc_work* work,
+ ATOMIC* probe_thieves)
+{
+ uint32_t tiles[UINT8_MAX];
+ struct timespec t;
+ ASSERT(htrdr && work && probe_thieves);
+
+ if(htrdr->mpi_nprocs == 1) /* The process is alone. No thief is possible */
+ return;
+
+ t.tv_sec = 0;
+
+ /* Protect MPI calls of multiple invocations from concurrent threads */
+ #define P_MPI(Func) { \
+ mutex_lock(htrdr->mpi_mutex); \
+ MPI(Func); \
+ mutex_unlock(htrdr->mpi_mutex); \
+ } (void)0
+
+ while(ATOMIC_GET(probe_thieves)) {
+ MPI_Status status;
+ size_t itile;
+ int msg;
+
+ /* Probe if a steal request was submitted by any processes */
+ P_MPI(Iprobe(MPI_ANY_SOURCE, HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD, &msg,
+ &status));
+
+ if(msg) { /* A steal request was posted */
+ MPI_Request req;
+ uint8_t ntiles_to_steal;
+
+ /* Asynchronously receive the steal request */
+ P_MPI(Irecv(&ntiles_to_steal, 1, MPI_UINT8_T, status.MPI_SOURCE,
+ HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD, &req));
+
+ /* Wait for the completion of the steal request */
+ mpi_wait_for_request(htrdr, &req);
+
+ /* Thief some tiles */
+ FOR_EACH(itile, 0, ntiles_to_steal) {
+ tiles[itile] = proc_work_get_tile(work);
+ }
+ P_MPI(Send(&tiles, ntiles_to_steal, MPI_UINT32_T, status.MPI_SOURCE,
+ HTRDR_MPI_WORK_STEALING, MPI_COMM_WORLD));
+ }
+ t.tv_nsec = 500000000; /* 500ms */
+ nanosleep(&t, NULL);
+ }
+ #undef P_MPI
+}
+
+static int
+mpi_sample_working_process(struct htrdr* htrdr, struct ssp_rng* rng)
+{
+ int iproc, i;
+ int dst_rank;
+ ASSERT(htrdr && rng && htrdr->mpi_nworking_procs);
+
+ /* Sample the index of the 1st active process */
+ iproc = (int)(ssp_rng_canonical(rng) * (double)htrdr->mpi_nworking_procs);
+
+ /* Find the rank of the sampled active process. Use a simple linear search
+ * since the overall number of processes should be quite low; at most few
+ * dozens. */
+ i = 0;
+ FOR_EACH(dst_rank, 0, htrdr->mpi_nprocs) {
+ if(htrdr->mpi_working_procs[dst_rank] == 0) continue; /* Inactive process */
+ if(i == iproc) break; /* The rank of the sampled process is found */
+ ++i;
+ }
+ ASSERT(dst_rank < htrdr->mpi_nprocs);
+ return dst_rank;
+}
+
+/* Return the number of stolen tiles */
+static size_t
+mpi_steal_work
+ (struct htrdr* htrdr,
+ struct ssp_rng* rng,
+ struct proc_work* work)
+{
+ MPI_Request req;
+ size_t itile;
+ size_t nthieves = 0;
+ uint32_t tiles[UINT8_MAX]; /* Morton code of the stolen tile */
+ int proc_to_steal; /* Process to steal */
+ uint8_t ntiles_to_steal = MMIN((uint8_t)(htrdr->nthreads*2), 16);
+ ASSERT(htrdr && rng && work && htrdr->nthreads < UINT8_MAX);
+
+ /* Protect MPI calls of multiple invocations from concurrent threads */
+ #define P_MPI(Func) { \
+ mutex_lock(htrdr->mpi_mutex); \
+ MPI(Func); \
+ mutex_unlock(htrdr->mpi_mutex); \
+ } (void)0
+
+ /* No more working process => nohting to steal */
+ if(!htrdr->mpi_nworking_procs) return 0;
+
+ /* Sample a process to steal */
+ proc_to_steal = mpi_sample_working_process(htrdr, rng);
+
+ /* Send a steal request to the sampled process and wait for a response */
+ P_MPI(Send(&ntiles_to_steal, 1, MPI_UINT8_T, proc_to_steal,
+ HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD));
+
+ /* Receive the stolen tile from the sampled process */
+ P_MPI(Irecv(tiles, ntiles_to_steal, MPI_UINT32_T, proc_to_steal,
+ HTRDR_MPI_WORK_STEALING, MPI_COMM_WORLD, &req));
+
+ mpi_wait_for_request(htrdr, &req);
+
+ FOR_EACH(itile, 0, ntiles_to_steal) {
+ if(tiles[itile] == TILE_MCODE_NULL) {
+ ASSERT(htrdr->mpi_working_procs[proc_to_steal] != 0);
+ htrdr->mpi_working_procs[proc_to_steal] = 0;
+ htrdr->mpi_nworking_procs--;
+ break;
+ }
+ proc_work_add_tile(work, tiles[itile]);
+ ++nthieves;
+ }
+ #undef P_MPI
+ return nthieves;
+}
+
+static res_T
+mpi_gather_tiles
+ (struct htrdr* htrdr,
+ const struct htrdr_sensor* sensor,
+ struct htrdr_buffer* buf,
+ const size_t ntiles,
+ struct list_node* tiles)
+{
+ /* Compute the size of the tile_data */
+ struct htrdr_buffer_layout layout = HTRDR_BUFFER_LAYOUT_NULL;
+ size_t msg_sz = 0;
+ struct list_node* node = NULL;
+ struct tile* tile = NULL;
+ res_T res = RES_OK;
+ ASSERT(htrdr && tiles);
+ ASSERT(htrdr->mpi_rank != 0 || buf);
+ (void)ntiles;
+
+ /* Compute the size of the tile data */
+ htrdr_buffer_get_layout(buf, &layout);
+ msg_sz =
+ sizeof(struct tile_data)
+ + TILE_SIZE*TILE_SIZE*layout.elmt_size
+ - 1/* Dummy octet of the flexible array */;
+ ASSERT(msg_sz <= INT_MAX);
+
+ if(htrdr->mpi_rank != 0) { /* Non master process */
+ /* Send the computed tile to the master process */
+ LIST_FOR_EACH(node, tiles) {
+ struct tile* t = CONTAINER_OF(node, struct tile, node);
+ MPI(Send(&t->data, (int)msg_sz, MPI_CHAR, 0,
+ HTRDR_MPI_TILE_DATA, MPI_COMM_WORLD));
+ }
+ } else { /* Master process */
+ size_t itile = 0;
+
+ LIST_FOR_EACH(node, tiles) {
+ struct tile* t = CONTAINER_OF(node, struct tile, node);
+ write_tile_data(htrdr, sensor, buf, &t->data);
+ ++itile;
+ }
+
+ if(itile != ntiles) {
+ enum pixel_format pixfmt;
+ ASSERT(htrdr->mpi_nprocs > 1);
+
+ /* Create a temporary tile to receive the tile data computed by the
+ * concurrent MPI processes */
+ pixfmt = spectral_type_to_pixfmt(htrdr->spectral_type);
+ tile = tile_create(htrdr->allocator, layout.elmt_size, layout.alignment);
+ if(!tile) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "could not allocate the temporary tile used to gather the process "
+ "output data -- %s.\n", res_to_cstr(res));
+ goto error;
+ }
+
+ /* Receive the tile data of the concurrent MPI processes */
+ FOR_EACH(itile, itile, ntiles) {
+ MPI(Recv(&tile->data, (int)msg_sz, MPI_CHAR, MPI_ANY_SOURCE,
+ HTRDR_MPI_TILE_DATA, MPI_COMM_WORLD, MPI_STATUS_IGNORE));
+ write_tile_data(htrdr, sensor, buf, &tile->data);
+ }
+ }
+ }
+
+exit:
+ if(tile) tile_ref_put(tile);
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+draw_tile
+ (struct htrdr* htrdr,
+ const struct htrdr_draw_map_args* args,
+ const size_t ithread,
+ const int64_t tile_mcode, /* For debug only */
+ const size_t tile_org[2], /* Origin of the tile in pixel space */
+ const size_t tile_sz[2], /* Definition of the tile */
+ const double pix_sz[2], /* Size of a pixel in the normalized image plane */
+ struct ssp_rng* rng,
+ struct tile* tile)
+{
+ struct htrdr_draw_pixel_args pix_args = HTRDR_DRAW_PIXEL_ARGS_NULL;
+ size_t npixels;
+ size_t mcode; /* Morton code of tile pixel */
+ ASSERT(htrdr && tile_org && tile_sz && pix_sz && sensor && spp && tile);
+ (void)tile_mcode;
+
+ /* Adjust the #pixels to process them wrt a morton order */
+ npixels = round_up_pow2(MMAX(tile_sz[0], tile_sz[1]));
+ npixels *= npixels;
+
+ /* Setup the shared pixel arguments */
+ pix_args.pixel_normalized_size[0] = pix_sz[0];
+ pix_args.pixel_normalized_size[1] = pix_sz[1];
+ pix_args.rng = rng;
+ pix_args.spp = args->spp;
+ pix_args.ithread = ithread;
+ pix_args.context = args->context;
+
+ FOR_EACH(mcode, 0, npixels) {
+ union pixel* pixel;
+ size_t ipix_tile[2]; /* Pixel coord in the tile */
+ size_t ipix[2]; /* Pixel coord in the buffer */
+
+ morton_xy_decode_u16(mcode, ipix_tile);
+ if(ipix_tile[0] >= tile_sz[0] || ipix_tile[1] >= tile_sz[1])
+ continue; /* Pixel is out of tile */
+
+ /* Fetch the pixel */
+ pixel = tile_at(tile, ipix_tile[0], ipix_tile[1]);
+
+ /* Compute the pixel coordinate */
+ pix_args.pixel_coord[0] = tile_org[0] + ipix_tile[0];
+ pix_args.pixel_coord[1] = tile_org[1] + ipix_tile[1];
+
+ /* Invoque the draw pixel functor */
+ args->draw_pixel(htrdr, &pix_args, pixel.data);
+ }
+ return RES_OK;
+}
+
+static res_T
+draw_map
+ (struct htrdr* htrdr,
+ const struct htrdr_draw_map_args* args,
+ const struct htrdr_buffer_layout* buf_layout,
+ const size_t ntiles_x,
+ const size_t ntiles_y,
+ const size_t ntiles_adjusted,
+ const double pix_sz[2], /* Pixel size in the normalized image plane */
+ struct proc_work* work,
+ struct list_node* tiles)
+{
+ struct ssp_rng* rng_proc = NULL;
+ size_t nthreads = 0;
+ size_t nthieves = 0;
+ size_t proc_ntiles = 0;
+ ATOMIC nsolved_tiles = 0;
+ ATOMIC res = RES_OK;
+ ASSERT(htrdr && check_draw_map_args(args) && buf_layout && work && tiles);
+ ASSERT(ntiles_x && ntiles_y && ntiles_adjusted >= ntiles_x*ntiles_y);
+ ASSERT(pix_sz && pix_sz[0] > 0 && pix_sz[1] > 0);
+
+ res = ssp_rng_create(htrdr->allocator, &ssp_rng_mt19937_64, &rng_proc);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr, "could not create the RNG used to sample a process "
+ "to steal -- %s.\n", res_to_cstr((res_T)res));
+ goto error;
+ }
+
+ proc_ntiles = proc_work_get_ntiles(work);
+ nthreads = MMIN(htrdr->nthreads, proc_ntiles);
+
+ /* The process is not considered as a working process for himself */
+ htrdr->mpi_working_procs[htrdr->mpi_rank] = 0;
+ --htrdr->mpi_nworking_procs;
+
+ omp_set_num_threads((int)nthreads);
+ #pragma omp parallel
+ for(;;) {
+ const int ithread = omp_get_thread_num();
+ struct ssp_rng_proxy* rng_proxy = NULL;
+ struct ssp_rng* rng;
+ struct tile* tile;
+ uint32_t mcode = TILE_MCODE_NULL;
+ uint16_t tile_org[2];
+ size_t tile_sz[2];
+ size_t n;
+ res_T res_local = RES_OK;
+ int32_t pcent;
+
+ /* Get a tile to draw */
+ #pragma omp critical
+ {
+ mcode = proc_work_get_tile(work);
+ if(mcode == TILE_MCODE_NULL) { /* No more work on this process */
+ /* Try to steal works to concurrent processes */
+ proc_work_reset(work);
+ nthieves = mpi_steal_work(htrdr, rng_proc, work);
+ if(nthieves != 0) {
+ mcode = proc_work_get_tile(work);
+ }
+ }
+ }
+ if(mcode == TILE_MCODE_NULL) break; /* No more work */
+
+ /* Decode the morton code to retrieve the tile index */
+ morton_xy_decode_u16(mcode, tile_org);
+ ASSERT(tile_org[0] < ntiles_x && tile_org[1] < ntiles_y);
+
+ /* Create the tile */
+ tile = tile_create
+ (htrdr->allocator, buf_layout->elmt_size, buf_layout->alignment);
+ if(!tile) {
+ ATOMIC_SET(&res, RES_MEM_ERR);
+ htrdr_log_err(htrdr,
+ "could not allocate the memory space of the tile (%lu, %lu) -- %s.\n",
+ (unsigned long)tile_org[0], (unsigned long)tile_org[1],
+ res_to_cstr((res_T)ATOMIC_GET(&res)));
+ break;
+ }
+
+ /* Register the tile */
+ #pragma omp critical
+ list_add_tail(tiles, &tile->node);
+
+ tile->data.x = (uint16_t)tile_org[0];
+ tile->data.y = (uint16_t)tile_org[1];
+
+ /* Define the tile origin in pixel space */
+ tile_org[0] *= TILE_SIZE;
+ tile_org[1] *= TILE_SIZE;
+
+ /* Compute the size of the tile clamped by the borders of the buffer */
+ tile_sz[0] = MMIN(TILE_SIZE, buf_layout->width - tile_org[0]);
+ tile_sz[1] = MMIN(TILE_SIZE, buf_layout->height - tile_org[1]);
+
+ /* Create a proxy RNG for the current tile. This proxy is used for the
+ * current thread only and thus it has to manage only one RNG. This proxy
+ * is initialised in order to ensure that an unique and predictable set of
+ * random numbers is used for the current tile. */
+ SSP(rng_proxy_create2
+ (&htrdr->lifo_allocators[ithread],
+ &ssp_rng_threefry,
+ RNG_SEQUENCE_SIZE * (size_t)mcode, /* Offset */
+ RNG_SEQUENCE_SIZE, /* Size */
+ RNG_SEQUENCE_SIZE * (size_t)ntiles_adjusted, /* Pitch */
+ 1, &rng_proxy));
+ SSP(rng_proxy_create_rng(rng_proxy, 0, &rng));
+
+ /* Launch the tile rendering */
+ res_local = draw_tile(htrdr, args, (size_t)ithread, mcode,
+ tile_org, tile_sz, pix_sz, rng, tile);
+
+ SSP(rng_proxy_ref_put(rng_proxy));
+ SSP(rng_ref_put(rng));
+
+ if(res_local != RES_OK) {
+ ATOMIC_SET(&res, res_local);
+ break;
+ }
+
+ /* Update the progress status */
+ n = (size_t)ATOMIC_INCR(&nsolved_tiles);
+ pcent = (int32_t)((double)n * 100.0 / (double)proc_ntiles + 0.5/*round*/);
+
+ #pragma omp critical
+ if(pcent > htrdr->mpi_progress_render[0]) {
+ htrdr->mpi_progress_render[0] = pcent;
+ if(htrdr->mpi_rank == 0) {
+ update_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING);
+ } else { /* Send the progress percentage to the master process */
+ send_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING, pcent);
+ }
+ }
+ }
+
+ if(ATOMIC_GET(&res) != RES_OK) goto error;
+
+ /* Asynchronously wait for processes completion. Use an asynchronous barrier to
+ * avoid a dead lock with the `mpi_probe_thieves' thread that requires also
+ * the `mpi_mutex'. */
+ {
+ MPI_Request req;
+
+ mutex_lock(htrdr->mpi_mutex);
+ MPI(Ibarrier(MPI_COMM_WORLD, &req));
+ mutex_unlock(htrdr->mpi_mutex);
+
+ mpi_wait_for_request(htrdr, &req);
+ }
+
+exit:
+ if(rng_proc) SSP(rng_ref_put(rng_proc));
+ return (res_T)res;
+error:
+ goto exit;
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_draw_map
+ (struct htrdr* htrdr,
+ struct htrdr_draw_map_args* args,
+ struct htrdr_buffer* buf)
+{
+ char strbuf[128];
+ struct time t0, t1;
+ struct list_node tiles;
+ size_t ntiles_x, ntiles_y, ntiles, ntiles_adjusted;
+ size_t itile;
+ struct proc_work work;
+ struct htrdr_buffer_layout layout = HTRDR_BUFFER_LAYOUT_NULL;
+ size_t proc_ntiles_adjusted;
+ double pix_sz[2];
+
+ ATOMIC probe_thieves = 1;
+ ATOMIC res = RES_OK;
+ ASSERT(htrdr && check_draw_map_args(args));
+ ASSERT(htrdr->mpi_rank != 0 || buf);
+
+ htrdr_buffer_get_layout(buf, &layout);
+
+ list_init(&tiles);
+ proc_work_init(htrdr->allocator, &work);
+
+ /* Compute the overall number of tiles */
+ ntiles_x = (width + (TILE_SIZE-1)/*ceil*/)/TILE_SIZE;
+ ntiles_y = (height+ (TILE_SIZE-1)/*ceil*/)/TILE_SIZE;
+ ntiles = ntiles_x * ntiles_y;
+
+ /* Compute the pixel size in the normalized image plane */
+ pix_sz[0] = 1.0 / (double)width;
+ pix_sz[1] = 1.0 / (double)height;
+
+ /* Adjust the #tiles for the morton-encoding procedure */
+ ntiles_adjusted = round_up_pow2(MMAX(ntiles_x, ntiles_y));
+ ntiles_adjusted *= ntiles_adjusted;
+
+ /* Define the initial number of tiles of the current process */
+ proc_ntiles_adjusted = ntiles_ajusted / (size_t)htrdr->mpi_nprocs;
+ if(htrdr->mpi_rank == 0) { /* Affect the remaining tiles to the master proc */
+ proc_ntiles_adjusted +=
+ ntiles_adjusted - proc_ntiles_adjusted*(size_t)htrdr->mpi_nprocs;
+ }
+
+ /* Define the initial list of tiles of the process */
+ FOR_EACH(itile, 0, proc_ntiles_adjusted) {
+ uint16_t tile_org[2];
+ const uint32_t mcode =
+ (uint32_t)itile*(uint32_t)htrdr->mpi_nprocs + (uint32_t)htrdr->mpi_rank;
+
+ morton_xy_decode_u16(mcode, tile_org);
+ if(tile_org[0] >= ntiles_x || tile_org[1] >= ntiles_y) continue;
+ proc_work_add_tile(&work, mcode);
+ }
+
+ if(htrdr->mpi_rank == 0) {
+ fetch_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING);
+ print_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING);
+ }
+
+ time_current(&t0);
+
+ omp_set_nested(1); /* Enable nested threads for draw_image */
+ #pragma omp parallel sections num_threads(2)
+ {
+ #pragma omp section
+ mpi_probe_thieves(htrdr, &work, &probe_thieves);
+
+ #pragma omp section
+ {
+ draw_map(htrdr, args, &layout, ntiles_x, ntiles_y, ntiles_adjusted, pix_sz, &work,
+ &tiles);
+ /* The processes have no more work to do. Stop probing for thieves */
+ ATOMIC_SET(&probe_thieves, 0);
+ }
+ }
+
+ if(htrdr->mpi_rank == 0) {
+ update_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING);
+ fprintf(stderr, "\n"); /* Add a new line after the progress statuses */
+ }
+
+ time_sub(&t0, time_current(&t1), &t0);
+ time_dump(&t0, TIME_ALL, NULL, strbuf, sizeof(strbuf));
+ htrdr_log(htrdr, "Rendering time: %s\n", strbuf);
+
+ /* Gather accum buffers from the group of processes */
+ time_current(&t0);
+ res = mpi_gather_tiles(htrdr, sensor, buf, ntiles, &tiles);
+ if(res != RES_OK) goto error;
+ time_sub(&t0, time_current(&t1), &t0);
+ time_dump(&t0, TIME_ALL, NULL, strbuf, sizeof(strbuf));
+ htrdr_log(htrdr, "Image gathering time: %s\n", strbuf);
+
+exit:
+ { /* Free allocated tiles */
+ struct list_node* node;
+ struct list_node* tmp;
+ LIST_FOR_EACH_SAFE(node, tmp, &tiles) {
+ struct tile* tile = CONTAINER_OF(node, struct tile, node);
+ list_del(node);
+ tile_ref_put(tile);
+ }
+ }
+ proc_work_release(&work);
+ return (res_T)res;
+error:
+ goto exit;
+}
+
diff --git a/src/core/htrdr_draw_map.h b/src/core/htrdr_draw_map.h
@@ -0,0 +1,91 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_DRAW_MAP_H
+#define HTRDR_DRAW_MAP_H
+
+#include "htrdr.h"
+#include <rsys/rsys.h>
+
+struct htrdr_draw_pixel_args {
+ size_t pixel_coord[2]; /* Image plane pixel coordinates */
+ double pixel_normalized_size[2]; /* Pixel size in the normalized img plane */
+ struct ssp_rng* rng; /* Random Number Generator */
+ size_t spp; /* #samples per pixel */
+ size_t ithread; /* Id of the thread drawing the pixel */
+ void* context; /* User defined data */
+};
+
+#define HTRDR_DRAW_PIXEL_ARGS_NULL__ { \
+ {0, 0}, /* Image plane pixel coordinates */ \
+ {0, 0}, /* Pixel size in the normalized img plane */ \
+ NULL, /* RNG */ \
+ 0, /* SPP */ \
+ 0, /* Thread id */ \
+ NULL /* User data */ \
+}
+static const struct htrdr_draw_pixel_args HTRDR_DRAW_PIXEL_ARGS_NULL =
+ HTRDR_DRAW_PIXEL_ARGS_NULL__;
+
+typedef void
+(*htrdr_draw_pixel_T)
+ (struct htrdr* htrdr,
+ const struct htrdr_draw_pixel_args* args,
+ void* pixel) /* Output data */
+
+struct htrdr_draw_map_args {
+ htrdr_draw_pixel_T draw_pixel;
+ size_t spp; /* Samples per pixel */
+ void* context; /* User defined data */
+};
+
+#define HTRDR_DRAW_MAP_ARGS_NULL__ { \
+ NULL, /* Draw pixel functor */ \
+ 0, /* #Samples per pixel */ \
+ NULL /* User defined data */ \
+}
+static const struct htrdr_draw_map_args HTRDR_DRAW_MAP_ARGS_NULL =
+ HTRDR_DRAW_MAP_ARGS_NULL__;
+
+/* Forward declarations */
+struct htrdr;
+struct htrdr_buffer;
+
+static INLINE int
+htrdr_draw_pixel_args_check(struct htrdr_draw_pixel_args* args)
+{
+ return args
+ && args->pixel_normalized_size[0] > 0
+ && args->pixel_normalized_size[1] > 0
+ && args->rng
+ && args->spp > 0;
+}
+
+/*******************************************************************************
+ * Exported symbols
+ ******************************************************************************/
+BEGIN_DECLS
+
+HTRDR_API res_T
+htrdr_draw_map_args
+ (struct htrdr* htrdr,
+ const struct htrdr_draw_map_args* args,
+ struct htrdr_buffer* buf;
+
+END_DECLS
+
+#endif /* HTRDR_DRAW_MAP_H */
+
diff --git a/src/htrdr_interface.h b/src/core/htrdr_interface.h
diff --git a/src/core/htrdr_log.c b/src/core/htrdr_log.c
@@ -0,0 +1,101 @@
+/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019, 2021 CNRS
+ * Copyright (C) 2018, 2019, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr.h"
+#include "htrdr_log.h"
+
+#include <rsys/logger.h>
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static void
+print_out(const char* msg, void* ctx)
+{
+ ASSERT(msg);
+ (void)ctx;
+ fprintf(stderr, HTRDR_LOG_INFO_PREFIX"%s", msg);
+}
+
+static void
+print_err(const char* msg, void* ctx)
+{
+ ASSERT(msg);
+ (void)ctx;
+ fprintf(stderr, HTRDR_LOG_ERROR_PREFIX"%s", msg);
+}
+
+static void
+print_warn(const char* msg, void* ctx)
+{
+ ASSERT(msg);
+ (void)ctx;
+ fprintf(stderr, HTRDR_LOG_WARNING_PREFIX"%s", msg);
+}
+
+static void
+log_msg
+ (struct htrdr* htrdr,
+ const enum log_type stream,
+ const char* msg,
+ va_list vargs)
+{
+ ASSERT(htrdr && msg);
+ if(htrdr->verbose) {
+ CHK(logger_vprint(&htrdr->logger, stream, msg, vargs) == RES_OK);
+ }
+}
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+void
+htrdr_log(struct htrdr* htrdr, const char* msg, ...)
+{
+ ASSERT(htrdr && msg);
+ /* Log standard message only on master process */
+ if(htrdr->mpi_rank == 0) {
+ va_list vargs_list;
+ va_start(vargs_list, msg);
+ log_msg(htrdr, LOG_OUTPUT, msg, vargs_list);
+ va_end(vargs_list);
+ }
+}
+
+void
+htrdr_log_err(struct htrdr* htrdr, const char* msg, ...)
+{
+ va_list vargs_list;
+ ASSERT(htrdr && msg);
+ /* Log errors on all processes */
+ va_start(vargs_list, msg);
+ log_msg(htrdr, LOG_ERROR, msg, vargs_list);
+ va_end(vargs_list);
+}
+
+void
+htrdr_log_warn(struct htrdr* htrdr, const char* msg, ...)
+{
+ ASSERT(htrdr && msg);
+ /* Log warnings only on master process */
+ if(htrdr->mpi_rank == 0) {
+ va_list vargs_list;
+ va_start(vargs_list, msg);
+ log_msg(htrdr, LOG_WARNING, msg, vargs_list);
+ va_end(vargs_list);
+ }
+}
diff --git a/src/core/htrdr_log.h b/src/core/htrdr_log.h
@@ -0,0 +1,64 @@
+/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019, 2021 CNRS
+ * Copyright (C) 2018, 2019, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_LOG_H
+#define HTRDR_LOG_H
+
+#include "htrdr.h"
+#include <rsys/rsys.h>
+
+#define HTRDR_LOG_INFO_PREFIX "\x1b[1m\x1b[32m>\x1b[0m "
+#define HTRDR_LOG_ERROR_PREFIX "\x1b[31merror:\x1b[0m "
+#define HTRDR_LOG_WARNING_PREFIX "\x1b[33mwarning:\x1b[0m "
+
+struct htrdr;
+
+BEGIN_DECLS
+
+HTRDR_API void
+htrdr_log
+ (struct htrdr* htrdr,
+ const char* msg,
+ ...)
+#ifdef COMPILER_GCC
+ __attribute((format(printf, 2, 3)))
+#endif
+ ;
+
+extern LOCAL_SYM void
+htrdr_log_err
+ (struct htrdr* htrdr,
+ const char* msg,
+ ...)
+#ifdef COMPILER_GCC
+ __attribute((format(printf, 2, 3)))
+#endif
+ ;
+
+extern LOCAL_SYM void
+htrdr_log_warn
+ (struct htrdr* htrdr,
+ const char* msg,
+ ...)
+#ifdef COMPILER_GCC
+ __attribute((format(printf, 2, 3)))
+#endif
+ ;
+
+END_DECLS
+
+#endif /* HTRDR_LOG_H */
diff --git a/src/core/htrdr_main.c b/src/core/htrdr_main.c
@@ -0,0 +1,105 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr.h"
+#include "htrdr_version.h"
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static void
+print_usage(const char* cmd)
+{
+ ASSERT(cmd);
+ printf("Usage: %s [--version] [--help] <mode> [<args>].\n");
+}
+
+static void
+print_help(const char* cmd)
+{
+ ASSERT(cmd);
+
+ print_usage(cmd);
+ printf("\n");
+
+ printf(
+" --version display version information and exit.\n");
+ printf(
+" --help display this help and exit.\n");
+ printf("\n");
+
+ printf("These are %s available modes:\n");
+ printf(
+" atmosphere Radiative transfer computations in a cloudy atmosphere.\n");
+ printf(
+" combustion Radiative transfer computations in a combustion medium.\n");
+ printf("\n");
+
+ htrdr_fprint_copyright(cmd, stdout);
+ htrdr_fprint_license(cmd, stdout);
+}
+
+/*******************************************************************************
+ * Program
+ ******************************************************************************/
+int
+main(int argc, char** argv)
+{
+ int err = 0;
+
+ if(argc < 2) {
+ print_usage(argv[0]);
+ goto error;
+ }
+
+ /* Atmosphere mode */
+ if(!strcmp(argv[1], "atmosphere")) {
+ /* TODO */
+
+ /* Combustion mode */
+ } else if(!strcmp(argv[1], "combustion")) {
+ /* TODO */
+
+ /* Version */
+ } else if(!strcmp(argv[1], "--version")) {
+ printf("%s version %d.%d.%d\n",
+ argv[0],
+ HTRDR_VERSION_MAJOR,
+ HTRDR_VERSION_MINOR,
+ HTRDR_VERSION_PATCH);
+ args->quit = 1;
+ goto exit;
+
+ /* Help */
+ } else if(!strcmp(argv[1], "--help")) {
+ print_usage(argv[0]);
+ args->quit = 1;
+ goto exit;
+
+ /* Fallback */
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", argv[1]);
+ print_usage(argv[0]);
+ goto error;
+ }
+
+exit:
+ return err;
+error:
+ err = -1;
+ goto exit;
+}
+
diff --git a/src/core/htrdr_materials.c b/src/core/htrdr_materials.c
@@ -0,0 +1,454 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define _POSIX_C_SOURCE 200112L /* strtok_r and wordexp support */
+
+#include "htrdr.h"
+#include "htrdr_materials.h"
+
+#include <modradurb/mrumtl.h>
+#include <star/ssf.h>
+#include <star/ssp.h>
+
+#include <rsys/cstr.h>
+#include <rsys/double3.h>
+#include <rsys/hash_table.h>
+#include <rsys/mem_allocator.h>
+#include <rsys/ref_count.h>
+#include <rsys/str.h>
+#include <rsys/text_reader.h>
+
+#include <string.h>
+#include <wordexp.h>
+
+struct mtl {
+ struct mrumtl* mrumtl;
+ double temperature; /* In Kelvin */
+};
+static const struct mtl MTL_NULL = {NULL, -1};
+
+/* Generate the hash table that maps a material name to its data */
+#define HTABLE_NAME name2mtl
+#define HTABLE_DATA struct mtl
+#define HTABLE_KEY struct str
+#define HTABLE_KEY_FUNCTOR_INIT str_init
+#define HTABLE_KEY_FUNCTOR_RELEASE str_release
+#define HTABLE_KEY_FUNCTOR_COPY str_copy
+#define HTABLE_KEY_FUNCTOR_COPY_AND_RELEASE str_copy_and_release
+#define HTABLE_KEY_FUNCTOR_HASH str_hash
+#define HTABLE_KEY_FUNCTOR_EQ str_eq
+#include <rsys/hash_table.h>
+
+struct htrdr_materials {
+ struct htable_name2mtl name2mtl;
+ struct htrdr* htrdr;
+ ref_T ref;
+};
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+static res_T
+parse_material
+ (struct htrdr_materials* mats,
+ struct txtrdr* txtrdr,
+ struct str* str) /* Scratch string */
+{
+ wordexp_t wexp;
+ char* tk = NULL;
+ char* tk_ctx = NULL;
+ struct mtl mtl = MTL_NULL;
+ int err = 0;
+ int wexp_is_allocated = 0;
+ res_T res = RES_OK;
+ ASSERT(mats && txtrdr);
+
+ tk = strtok_r(txtrdr_get_line(txtrdr), " \t", &tk_ctx);
+ ASSERT(tk);
+
+ res = str_set(str, tk);
+ if(res != RES_OK) {
+ htrdr_log_err(mats->htrdr,
+ "%s:%lu: could not copy the material name `%s' -- %s.\n",
+ txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk,
+ res_to_cstr(res));
+ goto error;
+ }
+
+ tk = strtok_r(NULL, "", &tk_ctx);
+ if(!tk) {
+ htrdr_log_err(mats->htrdr,
+ "%s:%lu: missing the MruMtl file for the material `%s'.\n",
+ txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
+ str_cget(str));
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ err = wordexp(tk, &wexp, 0);
+ if(err) {
+ htrdr_log_err(mats->htrdr,
+ "%s:%lu: error in word expension of the mrumtl path.\n",
+ txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr));
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ wexp_is_allocated = 1;
+
+ if(wexp.we_wordc < 1) {
+ htrdr_log_err(mats->htrdr,
+ "%s:%lu: missing the MruMtl file for the material `%s'.\n",
+ txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
+ str_cget(str));
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ /* Parse the mrumtl file if any */
+ if(strcmp(wexp.we_wordv[0], "none")) {
+ res = mrumtl_create(&mats->htrdr->logger, htrdr_get_allocator(mats->htrdr),
+ mats->htrdr->verbose, &mtl.mrumtl);
+ if(res != RES_OK) {
+ htrdr_log_err(mats->htrdr,
+ "%s:%lu: error creating the MruMtl loader for the material `%s'-- %s.\n",
+ txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
+ str_cget(str), res_to_cstr(res));
+ goto error;
+ }
+
+ res = mrumtl_load(mtl.mrumtl, wexp.we_wordv[0]);
+ if(res != RES_OK) goto error;
+ }
+
+ if(wexp.we_wordc < 2) {
+ if(mtl.mrumtl) {
+ htrdr_log_err(mats->htrdr,
+ "%s:%lu: missing temperature for the material `%s'.\n",
+ txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
+ str_cget(str));
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ } else {
+ /* Parse the temperature */
+ res = cstr_to_double(wexp.we_wordv[1], &mtl.temperature);
+ if(res != RES_OK) {
+ htrdr_log_err(mats->htrdr,
+ "%s:%lu: error parsing the temperature `%s' for the material `%s' "
+ "-- %s.\n",
+ txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
+ wexp.we_wordv[1], str_cget(str), res_to_cstr(res));
+ goto error;
+ }
+ }
+
+ /* Register the material */
+ res = htable_name2mtl_set(&mats->name2mtl, str, &mtl);
+ if(res != RES_OK) {
+ htrdr_log_err(mats->htrdr,
+ "%s:%lu: could not register the material `%s' -- %s.\n",
+ txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
+ str_cget(str), res_to_cstr(res));
+ goto error;
+ }
+
+ if(wexp.we_wordc > 2) {
+ htrdr_log_warn(mats->htrdr, "%s:%lu: unexpected text `%s'.\n",
+ txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
+ wexp.we_wordv[2]);
+ }
+
+exit:
+ if(wexp_is_allocated) wordfree(&wexp);
+ return res;
+error:
+ if(mtl.mrumtl) MRUMTL(ref_put(mtl.mrumtl));
+ goto exit;
+}
+
+static res_T
+parse_materials_list
+ (struct htrdr_materials* mats,
+ const char* filename,
+ const char* func_name)
+{
+ struct txtrdr* txtrdr = NULL;
+ struct str str;
+ res_T res = RES_OK;
+ ASSERT(mats && filename && func_name);
+
+ str_init(htrdr_get_allocator(mats->htrdr), &str);
+
+ res = txtrdr_file(htrdr_get_allocator(mats->htrdr), filename, '#', &txtrdr);
+ if(res != RES_OK) {
+ htrdr_log_err(mats->htrdr,
+ "%s: could not create the text reader for the material file `%s' -- %s.\n",
+ func_name, filename, res_to_cstr(res));
+ goto error;
+ }
+
+ for(;;) {
+ res = txtrdr_read_line(txtrdr);
+ if(res != RES_OK) {
+ htrdr_log_err(mats->htrdr,
+ "%s: error reading a line in the material file `%s' -- %s.\n",
+ func_name, filename, res_to_cstr(res));
+ goto error;
+ }
+
+ if(!txtrdr_get_cline(txtrdr)) break;
+
+ res = parse_material(mats, txtrdr, &str);
+ if(res != RES_OK) goto error;
+ }
+
+exit:
+ str_release(&str);
+ if(txtrdr) txtrdr_ref_put(txtrdr);
+ return res;
+error:
+ goto exit;
+}
+
+static void
+materials_release(ref_T* ref)
+{
+ struct htable_name2mtl_iterator it, it_end;
+ struct htrdr_materials* mats;
+ struct htrdr* htrdr;
+ ASSERT(ref);
+ mats = CONTAINER_OF(ref, struct htrdr_materials, ref);
+
+ htable_name2mtl_begin(&mats->name2mtl, &it);
+ htable_name2mtl_end(&mats->name2mtl, &it_end);
+ while(!htable_name2mtl_iterator_eq(&it, &it_end)) {
+ struct mtl* mtl = htable_name2mtl_iterator_data_get(&it);
+ /* The mrumtl can be NULL for semi transparent materials */
+ if(mtl->mrumtl) MRUMTL(ref_put(mtl->mrumtl));
+ htable_name2mtl_iterator_next(&it);
+ }
+ htable_name2mtl_release(&mats->name2mtl);
+ htrdr = mats->htrdr;
+ MEM_RM(htrdr_get_allocator(htrdr), mats);
+ htrdr_ref_put(htrdr);
+}
+
+static res_T
+create_bsdf_diffuse
+ (struct htrdr* htrdr,
+ const struct mrumtl_brdf* brdf,
+ const size_t ithread,
+ struct ssf_bsdf** out_bsdf)
+{
+ struct ssf_bsdf* bsdf = NULL;
+ double reflectivity = 0;
+ res_T res = RES_OK;
+ ASSERT(htrdr && brdf && out_bsdf);
+ ASSERT(mrumtl_brdf_get_type(brdf) == MRUMTL_BRDF_LAMBERTIAN);
+ ASSERT(ithread < htrdr->nthreads);
+
+ res = ssf_bsdf_create(htrdr_get_lifo_allocator(htrdr, ithread),
+ &ssf_lambertian_reflection, &bsdf);
+ if(res != RES_OK) goto error;
+
+ reflectivity = mrumtl_brdf_lambertian_get_reflectivity(brdf);
+ res = ssf_lambertian_reflection_setup(bsdf, reflectivity);
+ if(res != RES_OK) goto error;
+
+exit:
+ *out_bsdf = bsdf;
+ return res;
+error:
+ if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; }
+ goto exit;
+}
+
+static res_T
+create_bsdf_specular
+ (struct htrdr* htrdr,
+ const struct mrumtl_brdf* brdf,
+ const size_t ithread,
+ struct ssf_bsdf** out_bsdf)
+{
+ struct ssf_bsdf* bsdf = NULL;
+ struct ssf_fresnel* fresnel = NULL;
+ struct mem_allocator* allocator = NULL;
+ double reflectivity = 0;
+ res_T res = RES_OK;
+ ASSERT(htrdr && brdf && out_bsdf);
+ ASSERT(mrumtl_brdf_get_type(brdf) == MRUMTL_BRDF_SPECULAR);
+ ASSERT(ithread < htrdr->nthreads);
+
+ allocator = htrdr_get_lifo_allocator(htrdr, ithread);
+
+ res = ssf_bsdf_create(allocator &ssf_specular_reflection, &bsdf);
+ if(res != RES_OK) goto error;
+
+ res = ssf_fresnel_create(allocator, &ssf_fresnel_constant, &fresnel);
+ if(res != RES_OK) goto error;
+
+ reflectivity = mrumtl_brdf_specular_get_reflectivity(brdf);
+ res = ssf_fresnel_constant_setup(fresnel, reflectivity);
+ if(res != RES_OK) goto error;
+
+ res = ssf_specular_reflection_setup(bsdf, fresnel);
+ if(res != RES_OK) goto error;
+
+exit:
+ if(fresnel) SSF(fresnel_ref_put(fresnel));
+ *out_bsdf = bsdf;
+ return res;
+error:
+ if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; }
+ goto exit;
+}
+
+/*******************************************************************************
+ * Local symbol
+ ******************************************************************************/
+res_T
+htrdr_materials_create
+ (struct htrdr* htrdr,
+ const char* filename,
+ struct htrdr_materials** out_mtl)
+{
+ struct htrdr_materials* mats = NULL;
+ res_T res = RES_OK;
+ ASSERT(htrdr && filename && out_mtl);
+
+ mats = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*mats));
+ if(!mats) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "%s: could not allocate the mats data structure -- %s.\n",
+ FUNC_NAME, res_to_cstr(res));
+ goto error;
+ }
+ ref_init(&mats->ref);
+ htrdr_ref_get(htrdr);
+ mats->htrdr = htrdr;
+ htable_name2mtl_init(htrdr_get_allocator(htrdr), &mats->name2mtl);
+
+ res = parse_materials_list(mats, filename, FUNC_NAME);
+ if(res != RES_OK) goto error;
+
+exit:
+ if(out_mtl) *out_mtl = mats;
+ return res;
+error:
+ if(mats) {
+ htrdr_materials_ref_put(mats);
+ mats = NULL;
+ }
+ goto exit;
+}
+
+void
+htrdr_materials_ref_get(struct htrdr_materials* mats)
+{
+ ASSERT(mats);
+ ref_get(&mats->ref);
+}
+
+void
+htrdr_materials_ref_put(struct htrdr_materials* mats)
+{
+ ASSERT(mats);
+ ref_put(&mats->ref, materials_release);
+}
+
+int
+htrdr_materials_find_mtl
+ (struct htrdr_materials* mats,
+ const char* name,
+ struct htrdr_mtl* htrdr_mtl)
+{
+ struct str str;
+ struct htable_name2mtl_iterator it, it_end;
+ int found = 0;
+ ASSERT(mats && name && htrdr_mtl);
+
+ str_init(htrdr_get_allocator(htrdr), &str);
+ CHK(str_set(&str, name) == RES_OK);
+
+ htable_name2mtl_find_iterator(&mats->name2mtl, &str, &it);
+ htable_name2mtl_end(&mats->name2mtl, &it_end);
+ if(htable_name2mtl_iterator_eq(&it, &it_end)) { /* No material found */
+ *htrdr_mtl = HTRDR_MTL_NULL;
+ found = 0;
+ } else {
+ struct mtl* mtl = htable_name2mtl_iterator_data_get(&it);
+ ASSERT(mtl != NULL);
+ htrdr_mtl->name = str_cget(htable_name2mtl_iterator_key_get(&it));
+ htrdr_mtl->mrumtl = mtl->mrumtl;
+ htrdr_mtl->temperature = mtl->temperature;
+ found = 1;
+ }
+ str_release(&str);
+
+ return found;
+}
+
+res_T
+htrdr_mtl_create_bsdf
+ (struct htrdr* htrdr,
+ const struct htrdr_mtl* mtl,
+ const size_t ithread,
+ const double wavelength,
+ struct ssp_rng* rng,
+ struct ssf_bsdf** out_bsdf)
+{
+ struct ssf_bsdf* bsdf = NULL;
+ const struct mrumtl_brdf* brdf = NULL;
+ double r;
+ res_T res = RES_OK;
+ ASSERT(htrdr && mtl && wavelength && rng && out_bsdf);
+ ASSERT(ithread < htrdr->nthreads);
+
+ r = ssp_rng_canonical(rng);
+
+ res = mrumtl_fetch_brdf2(mtl->mrumtl, wavelength, r, &brdf);
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr,
+ "%s: error retrieving the MruMtl BRDF for the wavelength %g.\n",
+ FUNC_NAME, wavelength);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ switch(mrumtl_brdf_get_type(brdf)) {
+ case MRUMTL_BRDF_LAMBERTIAN:
+ res = create_bsdf_diffuse(htrdr, brdf, ithread, &bsdf);
+ break;
+ case MRUMTL_BRDF_SPECULAR:
+ res = create_bsdf_specular(htrdr, brdf, ithread, &bsdf);
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+ if(res != RES_OK) {
+ htrdr_log_err(htrdr, "%s: could not create the BSDF -- %s.\n",
+ FUNC_NAME, res_to_cstr(res));
+ goto error;
+ }
+
+exit:
+ *out_bsdf = bsdf;
+ return res;
+error:
+ if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; }
+ goto exit;
+}
+
diff --git a/src/core/htrdr_materials.h b/src/core/htrdr_materials.h
@@ -0,0 +1,73 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_MATERIALS_H
+#define HTRDR_MATERIALS_H
+
+#include "htrdr.h"
+#include <rsys/rsys.h>
+
+/* Forward declarations */
+struct htrdr_materials;
+struct mrumtl;
+struct s3d_hit;
+struct ssf_bsdf;
+struct ssp_rng;
+
+struct htrdr_mtl {
+ const char* name;
+ const struct mrumtl* mrumtl;
+ double temperature;
+};
+#define HTRDR_MTL_NULL__ {NULL, NULL, 0}
+static const struct htrdr_mtl HTRDR_MTL_NULL = HTRDR_MTL_NULL__;
+
+BEGIN_DECLS
+
+HTRDR_API res_T
+htrdr_materials_create
+ (struct htrdr* htrdr,
+ const char* filename,
+ struct htrdr_materials** mats);
+
+HTRDR_API void
+htrdr_materials_ref_get
+ (struct htrdr_materials* mats);
+
+HTRDR_API void
+htrdr_materials_ref_put
+ (struct htrdr_materials* mats);
+
+/* Return 1 if the material exist and 0 otherwise */
+HTRDR_API int
+htrdr_materials_find_mtl
+ (struct htrdr_materials* mats,
+ const char* mtl_name,
+ struct htrdr_mtl* mtl);
+
+HTRDR_API res_T
+htrdr_mtl_create_bsdf
+ (struct htrdr* htrdr,
+ const struct htrdr_mtl* mtl,
+ const size_t ithread,
+ const double wavelength,
+ struct ssp_rng* rng,
+ struct ssf_bsdf** bsdf);
+
+END_DECLS
+
+#endif /* HTRDR_MATERIALS_H */
+
diff --git a/src/core/htrdr_ran_wlen.c b/src/core/htrdr_ran_wlen.c
@@ -0,0 +1,368 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define _POSIX_C_SOURCE 200112L /* nextafter */
+
+#include "htrdr.h"
+#include "htrdr_c.h"
+#include "htrdr_ran_wlen.h"
+
+#include <high_tune/htsky.h>
+
+#include <rsys/algorithm.h>
+#include <rsys/cstr.h>
+#include <rsys/dynamic_array_double.h>
+#include <rsys/mem_allocator.h>
+#include <rsys/ref_count.h>
+
+#include <math.h> /* nextafter */
+
+struct htrdr_ran_wlen {
+ struct darray_double pdf;
+ struct darray_double cdf;
+ double range[2]; /* Boundaries of the spectral integration interval */
+ double band_len; /* Length in nanometers of a band */
+ double ref_temperature; /* In Kelvin */
+ size_t nbands; /* # bands */
+
+ struct htrdr* htrdr;
+ ref_T ref;
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static res_T
+setup_wlen_ran_cdf
+ (struct htrdr_ran_wlen* wlen_ran,
+ const char* func_name)
+{
+ double* pdf = NULL;
+ double* cdf = NULL;
+ double sum = 0;
+ size_t i;
+ res_T res = RES_OK;
+ ASSERT(wlen_ran && func_name && wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE);
+
+ res = darray_double_resize(&wlen_ran->cdf, wlen_ran->nbands);
+ if(res != RES_OK) {
+ htrdr_log_err(wlen_ran->htrdr,
+ "%s: Error allocating the CDF of the spectral bands -- %s.\n",
+ func_name, res_to_cstr(res));
+ goto error;
+ }
+ res = darray_double_resize(&wlen_ran->pdf, wlen_ran->nbands);
+ if(res != RES_OK) {
+ htrdr_log_err(wlen_ran->htrdr,
+ "%s: Error allocating the pdf of the spectral bands -- %s.\n",
+ func_name, res_to_cstr(res));
+ goto error;
+ }
+
+ cdf = darray_double_data_get(&wlen_ran->cdf);
+ pdf = darray_double_data_get(&wlen_ran->pdf); /* Now save pdf to correct weight */
+
+ htrdr_log(wlen_ran->htrdr,
+ "Number of bands of the spectrum cumulative: %lu\n",
+ (unsigned long)wlen_ran->nbands);
+
+ /* Compute the *unnormalized* probability to sample a small band */
+ FOR_EACH(i, 0, wlen_ran->nbands) {
+ double lambda_lo = wlen_ran->range[0] + (double)i * wlen_ran->band_len;
+ double lambda_hi = MMIN(lambda_lo + wlen_ran->band_len, wlen_ran->range[1]);
+ ASSERT(lambda_lo<= lambda_hi);
+ ASSERT(lambda_lo > wlen_ran->range[0] || eq_eps(lambda_lo, wlen_ran->range[0], 1.e-6));
+ ASSERT(lambda_lo < wlen_ran->range[1] || eq_eps(lambda_lo, wlen_ran->range[1], 1.e-6));
+
+ /* Convert from nanometer to meter */
+ lambda_lo *= 1.e-9;
+ lambda_hi *= 1.e-9;
+
+ /* Compute the probability of the current band */
+ pdf[i] = blackbody_fraction(lambda_lo, lambda_hi, wlen_ran->ref_temperature);
+
+ /* Update the norm */
+ sum += pdf[i];
+ }
+
+ /* Compute the cumulative of the previously computed probabilities */
+ FOR_EACH(i, 0, wlen_ran->nbands) {
+ /* Normalize the probability */
+ pdf[i] /= sum;
+
+ /* Setup the cumulative */
+ if(i == 0) {
+ cdf[i] = pdf[i];
+ } else {
+ cdf[i] = pdf[i] + cdf[i-1];
+ ASSERT(cdf[i] >= cdf[i-1]);
+ }
+ }
+ ASSERT(eq_eps(cdf[wlen_ran->nbands-1], 1, 1.e-6));
+ cdf[wlen_ran->nbands - 1] = 1.0; /* Handle numerical issue */
+
+exit:
+ return res;
+error:
+ darray_double_clear(&wlen_ran->cdf);
+ darray_double_clear(&wlen_ran->pdf);
+ goto exit;
+}
+
+static double
+wlen_ran_sample_continue
+ (const struct htrdr_ran_wlen* wlen_ran,
+ const double r,
+ const double range[2], /* In nanometer */
+ const char* func_name,
+ double* pdf)
+{
+ /* Numerical parameters of the dichotomy search */
+ const size_t MAX_ITER = 100;
+ const double EPSILON_LAMBDA_M = 1e-15;
+ const double EPSILON_BF = 1e-6;
+
+ /* Local variables */
+ double bf = 0;
+ double bf_prev = 0;
+ double bf_min_max = 0;
+ double lambda_m = 0;
+ double lambda_m_prev = 0;
+ double lambda_m_min = 0;
+ double lambda_m_max = 0;
+ double range_m[2] = {0, 0};
+ size_t i;
+
+ /* Check precondition */
+ ASSERT(wlen_ran && func_name);
+ ASSERT(range && range[0] < range[1]);
+ ASSERT(0 <= r && r < 1);
+
+ /* Convert the wavelength range in meters */
+ range_m[0] = range[0] * 1.0e-9;
+ range_m[1] = range[1] * 1.0e-9;
+
+ /* Setup the dichotomy search */
+ lambda_m_min = range_m[0];
+ lambda_m_max = range_m[1];
+ bf_min_max = blackbody_fraction
+ (range_m[0], range_m[1], wlen_ran->ref_temperature);
+
+ /* Numerically search the lambda corresponding to the submitted canonical
+ * number */
+ FOR_EACH(i, 0, MAX_ITER) {
+ double r_test;
+ lambda_m = (lambda_m_min + lambda_m_max) * 0.5;
+ bf = blackbody_fraction(range_m[0], lambda_m, wlen_ran->ref_temperature);
+
+ r_test = bf / bf_min_max;
+ if(r_test < r) {
+ lambda_m_min = lambda_m;
+ } else {
+ lambda_m_max = lambda_m;
+ }
+
+ if(fabs(lambda_m_prev - lambda_m) < EPSILON_LAMBDA_M
+ || fabs(bf_prev - bf) < EPSILON_BF)
+ break;
+
+ lambda_m_prev = lambda_m;
+ bf_prev = bf;
+ }
+ if(i >= MAX_ITER) {
+ htrdr_log_warn(wlen_ran->htrdr,
+ "%s: could not sample a wavelength in the range [%g, %g] nanometers "
+ "for the reference temperature %g Kelvin.\n",
+ func_name, SPLIT2(range), wlen_ran->ref_temperature);
+ }
+
+ if(pdf) {
+ const double Tref = wlen_ran->ref_temperature;
+ const double B_lambda = planck(lambda_m, lambda_m, Tref);
+ const double B_mean = planck(range_m[0], range_m[1], Tref);
+ *pdf = B_lambda / (B_mean * (range_m[1]-range_m[0]));
+ }
+
+ return lambda_m * 1.0e9; /* Convert in nanometers */
+}
+
+static double
+wlen_ran_sample_discrete
+ (const struct htrdr_ran_wlen* wlen_ran,
+ const double r0,
+ const double r1,
+ const char* func_name,
+ double* pdf)
+{
+ const double* cdf = NULL;
+ const double* find = NULL;
+ double r0_next = nextafter(r0, DBL_MAX);
+ double band_range[2];
+ double lambda = 0;
+ double pdf_continue = 0;
+ double pdf_band = 0;
+ size_t cdf_length = 0;
+ size_t i;
+ ASSERT(wlen_ran && wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE);
+ ASSERT(0 <= r0 && r0 < 1);
+ ASSERT(0 <= r1 && r1 < 1);
+ (void)func_name;
+ (void)pdf_band;
+
+ cdf = darray_double_cdata_get(&wlen_ran->cdf);
+ cdf_length = darray_double_size_get(&wlen_ran->cdf);
+ ASSERT(cdf_length > 0);
+
+ /* Use r_next rather than r0 in order to find the first entry that is not less
+ * than *or equal* to r0 */
+ find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl);
+ ASSERT(find);
+
+ i = (size_t)(find - cdf);
+ ASSERT(i < cdf_length && cdf[i] > r0 && (!i || cdf[i-1] <= r0));
+
+ band_range[0] = wlen_ran->range[0] + (double)i*wlen_ran->band_len;
+ band_range[1] = band_range[0] + wlen_ran->band_len;
+
+ /* Fetch the pdf of the sampled band */
+ pdf_band = darray_double_cdata_get(&wlen_ran->pdf)[i];
+
+ /* Uniformly sample a wavelength in the sampled band */
+ lambda = band_range[0] + (band_range[1] - band_range[0]) * r1;
+ pdf_continue = 1.0 / ((band_range[1] - band_range[0])*1.e-9);
+
+ if(pdf) {
+ *pdf = pdf_band * pdf_continue;
+ }
+
+ return lambda;
+}
+
+static void
+release_wlen_ran(ref_T* ref)
+{
+ struct htrdr_ran_wlen* wlen_ran = NULL;
+ struct htrdr* htrdr = NULL;
+ ASSERT(ref);
+ wlen_ran = CONTAINER_OF(ref, struct htrdr_ran_wlen, ref);
+ darray_double_release(&wlen_ran->cdf);
+ darray_double_release(&wlen_ran->pdf);
+ htrdr = wlen_ran->htrdr;
+ MEM_RM(htrdr_get_allocator(htrdr), wlen_ran);
+ htrdr_ref_put(wlen_ran->htrdr);
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_ran_wlen_create
+ (struct htrdr* htrdr,
+ /* range must be included in [200,1000] nm for shortwave or in [1000,100000]
+ * nanometers for longwave (thermal) */
+ const double range[2],
+ const size_t nbands, /* # bands used to discretized CDF */
+ const double ref_temperature,
+ struct htrdr_ran_wlen** out_wlen_ran)
+{
+ struct htrdr_ran_wlen* wlen_ran = NULL;
+ double min_band_len = 0;
+ res_T res = RES_OK;
+ ASSERT(htrdr && range && out_wlen_ran && ref_temperature > 0);
+ ASSERT(ref_temperature > 0);
+ ASSERT(range[0] <= range[1]);
+
+ wlen_ran = MEM_CALLOC(htrdr->allocator, 1, sizeof(*wlen_ran));
+ if(!wlen_ran) {
+ res = RES_MEM_ERR;
+ htrdr_log_err(htrdr,
+ "%s: could not allocate longwave random variate data structure -- %s.\n",
+ FUNC_NAME, res_to_cstr(res));
+ goto error;
+ }
+ ref_init(&wlen_ran->ref);
+ darray_double_init(htrdr->allocator, &wlen_ran->cdf);
+ darray_double_init(htrdr->allocator, &wlen_ran->pdf);
+ htrdr_ref_get(htrdr);
+ wlen_ran->htrdr = htrdr;
+
+ wlen_ran->range[0] = range[0];
+ wlen_ran->range[1] = range[1];
+ wlen_ran->ref_temperature = ref_temperature;
+ wlen_ran->nbands = nbands;
+
+ min_band_len = compute_sky_min_band_len(wlen_ran->htrdr->sky, wlen_ran->range);
+
+ if(nbands == HTRDR_WLEN_RAN_CONTINUE) {
+ wlen_ran->band_len = 0;
+ } else {
+ wlen_ran->band_len = (range[1] - range[0]) / (double)wlen_ran->nbands;
+
+ /* Adjust the band length to ensure that each sky spectral interval is
+ * overlapped by at least one band */
+ if(wlen_ran->band_len > min_band_len) {
+ wlen_ran->band_len = min_band_len;
+ wlen_ran->nbands = (size_t)ceil((range[1] - range[0]) / wlen_ran->band_len);
+ }
+
+ res = setup_wlen_ran_cdf(wlen_ran, FUNC_NAME);
+ if(res != RES_OK) goto error;
+ }
+
+ htrdr_log(htrdr, "Spectral interval defined on [%g, %g] nanometers.\n",
+ range[0], range[1]);
+
+exit:
+ *out_wlen_ran = wlen_ran;
+ return res;
+error:
+ if(wlen_ran) htrdr_ran_wlen_ref_put(wlen_ran);
+ goto exit;
+}
+
+void
+htrdr_ran_wlen_ref_get(struct htrdr_ran_wlen* wlen_ran)
+{
+ ASSERT(wlen_ran);
+ ref_get(&wlen_ran->ref);
+}
+
+void
+htrdr_ran_wlen_ref_put(struct htrdr_ran_wlen* wlen_ran)
+{
+ ASSERT(wlen_ran);
+ ref_put(&wlen_ran->ref, release_wlen_ran);
+}
+
+double
+htrdr_ran_wlen_sample
+ (const struct htrdr_ran_wlen* wlen_ran,
+ const double r0,
+ const double r1,
+ double* pdf)
+{
+ ASSERT(wlen_ran);
+ if(wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE) { /* Discrete */
+ return wlen_ran_sample_discrete(wlen_ran, r0, r1, FUNC_NAME, pdf);
+ } else if(eq_eps(wlen_ran->range[0], wlen_ran->range[1], 1.e-6)) {
+ if(pdf) *pdf = 1;
+ return wlen_ran->range[0];
+ } else { /* Continue */
+ return wlen_ran_sample_continue
+ (wlen_ran, r0, wlen_ran->range, FUNC_NAME, pdf);
+ }
+}
+
diff --git a/src/core/htrdr_ran_wlen.h b/src/core/htrdr_ran_wlen.h
@@ -0,0 +1,60 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_RAN_WLEN_H
+#define HTRDR_RAN_WLEN_H
+
+#include "htrdr.h"
+#include <rsys/rsys.h>
+
+#define HTRDR_WLEN_RAN_CONTINUE 0
+
+/* Forward declarations */
+struct htrdr;
+struct htrdr_ran_wlen;
+
+BEGIN_DECLS
+
+HTRDR_API res_T
+htrdr_ran_wlen_create
+ (struct htrdr* htrdr,
+ const double range[2],
+ /* # bands used to discretisze the spectral domain. HTRDR_WLEN_RAN_CONTINUE
+ * <=> no discretisation */
+ const size_t nbands, /* Hint on #bands used to discretised th CDF */
+ const double ref_temperature, /* Reference temperature */
+ struct htrdr_ran_wlen** wlen_ran);
+
+HTRDR_API void
+htrdr_ran_wlen_ref_get
+ (struct htrdr_ran_wlen* wlen_ran);
+
+HTRDR_API void
+htrdr_ran_wlen_ref_put
+ (struct htrdr_ran_wlen* wlen_ran);
+
+/* Return a wavelength in nanometer */
+HTRDR_API double
+htrdr_ran_wlen_sample
+ (const struct htrdr_ran_wlen* wlen_ran,
+ const double r0, /* Canonical number in [0, 1[ */
+ const double r1, /* Canonical number in [0, 1[ */
+ double* pdf); /* May be NULL */
+
+END_DECLS
+
+#endif /* HTRDR_RAN_WLEN_H */
+
diff --git a/src/core/htrdr_rectangle.c b/src/core/htrdr_rectangle.c
@@ -0,0 +1,146 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr.h"
+#include "htrdr_rectangle.h"
+
+#include <rsys/double3.h>
+#include <rsys/mem_allocator.h>
+#include <rsys/ref_count.h>
+
+struct htrdr_rectangle {
+ /* Frame of the rectangle in world space */
+ double axis_x[3];
+ double axis_y[3];
+
+ double normal[3];
+ double position[3]; /* Center of the rectangle */
+ struct htrdr* htrdr;
+ ref_T ref;
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static void
+rectangle_release(ref_T* ref)
+{
+ struct htrdr_rectangle* rect;
+ struct htrdr* htrdr;
+ ASSERT(ref);
+ rect = CONTAINER_OF(ref, struct htrdr_rectangle, ref);
+ htrdr = rect->htrdr;
+ MEM_RM(htrdr_get_allocator(htrdr), rect);
+ htrdr_ref_put(htrdr);
+}
+
+/*******************************************************************************
+ * Local fuuction
+ ******************************************************************************/
+res_T
+htrdr_rectangle_create
+ (struct htrdr* htrdr,
+ const double sz[2],
+ const double pos[3],
+ const double tgt[3],
+ const double up[3],
+ struct htrdr_rectangle** out_rect)
+{
+ struct htrdr_rectangle* rect = NULL;
+ double x[3], y[3], z[3];
+ res_T res = RES_OK;
+ ASSERT(htrdr && pos && tgt && up && sz && out_rect);
+
+ rect = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*rect));
+ if(!rect) {
+ htrdr_log_err(htrdr, "could not allocate the rectangle data structure.\n");
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ ref_init(&rect->ref);
+ htrdr_ref_get(htrdr);
+ rect->htrdr = htrdr;
+
+ if(sz[0] <= 0 || sz[1] <= 0) {
+ htrdr_log_err(htrdr,
+ "invalid rectangle size `%g %g'. It must be strictly positive.\n",
+ SPLIT2(sz));
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ if(d3_normalize(z, d3_sub(z, tgt, pos)) <= 0
+ || d3_normalize(x, d3_cross(x, z, up)) <= 0
+ || d3_normalize(y, d3_cross(y, z, x)) <= 0) {
+ htrdr_log_err(htrdr, "invalid rectangle frame:\n"
+ "\tposition = %g %g %g\n"
+ "\ttarget = %g %g %g\n"
+ "\tup = %g %g %g\n",
+ SPLIT3(pos), SPLIT3(tgt), SPLIT3(up));
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ d3_muld(rect->axis_x, x, sz[0]*0.5);
+ d3_muld(rect->axis_y, y, sz[1]*0.5);
+ d3_set(rect->normal, z);
+ d3_set(rect->position, pos);
+
+exit:
+ *out_rect = rect;
+ return res;
+error:
+ if(rect) {
+ htrdr_rectangle_ref_put(rect);
+ rect = NULL;
+ }
+ goto exit;
+}
+
+void
+htrdr_rectangle_sample_pos
+ (const struct htrdr_rectangle* rect,
+ const double sample[2], /* In [0, 1[ */
+ double pos[3])
+{
+ double x[3], y[3];
+ ASSERT(rect && sample && pos);
+ d3_muld(x, rect->axis_x, sample[0]*2.0 - 1.0);
+ d3_muld(y, rect->axis_y, sample[1]*2.0 - 1.0);
+ d3_add(pos, d3_add(pos, rect->position, x), y);
+}
+
+void
+htrdr_rectangle_ref_get(struct htrdr_rectangle* rect)
+{
+ ASSERT(rect);
+ ref_get(&rect->ref);
+}
+
+void
+htrdr_rectangle_ref_put(struct htrdr_rectangle* rect)
+{
+ ASSERT(rect);
+ ref_put(&rect->ref, rectangle_release);
+}
+
+void
+htrdr_rectangle_get_normal(const struct htrdr_rectangle* rect, double normal[3])
+{
+ ASSERT(rect && normal);
+ d3_set(normal, rect->normal);
+}
+
diff --git a/src/core/htrdr_rectangle.h b/src/core/htrdr_rectangle.h
@@ -0,0 +1,60 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_RECTANGLE_H
+#define HTRDR_RECTANGLE_H
+
+#include "htrdr.h"
+#include <rsys/rsys.h>
+
+/* Forwar declarations */
+struct htrdr;
+struct htrdr_rectangle; /* 2D rectangle transformed in 3D */
+
+BEGIN_DECLS
+
+HTRDR_API res_T
+htrdr_rectangle_create
+ (struct htrdr* htrdr,
+ const double sz[2], /* Size of the rectangle along its local X and Y axis */
+ const double pos[3], /* World space position of the rectangle center */
+ const double tgt[3], /* Vector orthognal to the rectangle plane */
+ const double up[3], /* vector orthogonal to the rectangle X axis */
+ struct htrdr_rectangle** rect);
+
+HTRDR_API void
+htrdr_rectangle_ref_get
+ (struct htrdr_rectangle* rect);
+
+HTRDR_API void
+htrdr_rectangle_ref_put
+ (struct htrdr_rectangle* rect);
+
+HTRDR_API void
+htrdr_rectangle_sample_pos
+ (const struct htrdr_rectangle* rect,
+ const double sample[2], /* In [0, 1[ */
+ double pos[3]);
+
+HTRDR_API void
+htrdr_rectangle_get_normal
+ (const struct htrdr_rectangle* rect,
+ double normal[3]);
+
+END_DECLS
+
+#endif /* HTRDR_RECTANGLE_H */
+
diff --git a/src/core/htrdr_sensor.c b/src/core/htrdr_sensor.c
@@ -0,0 +1,79 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr.h"
+#include "htrdr_camera.h"
+#include "htrdr_ground.h"
+#include "htrdr_interface.h"
+#include "htrdr_rectangle.h"
+#include "htrdr_sensor.h"
+
+#include <star/s3d.h>
+#include <star/ssp.h>
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static res_T
+sample_camera_ray
+ (struct htrdr_camera* cam,
+ const size_t ipix[2],
+ const double pix_sz[2],
+ struct ssp_rng* rng,
+ double ray_org[3],
+ double ray_dir[3])
+{
+ double pix_samp[2];
+ ASSERT(cam && ipix && pix_sz && rng && ray_org && ray_dir);
+
+ /* Sample a position into the pixel, in the normalized image plane */
+ pix_samp[0] = ((double)ipix[0] + ssp_rng_canonical(rng)) * pix_sz[0];
+ pix_samp[1] = ((double)ipix[1] + ssp_rng_canonical(rng)) * pix_sz[1];
+
+ /* Generate a ray starting from the pinhole camera and passing through the
+ * pixel sample */
+ htrdr_camera_ray(cam, pix_samp, ray_org, ray_dir);
+
+ return RES_OK;
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+res_T
+htrdr_sensor_sample_primary_ray
+ (const struct htrdr_sensor* sensor,
+ struct htrdr* htrdr,
+ const size_t ipix[2],
+ const double pix_sz[2],
+ struct ssp_rng* rng,
+ double ray_org[3],
+ double ray_dir[3])
+{
+ res_T res = RES_OK;
+ switch(sensor->type) {
+ case HTRDR_SENSOR_CAMERA:
+ res = sample_camera_ray(sensor->camera, ipix, pix_sz, rng, ray_org, ray_dir);
+ break;
+ case HTRDR_SENSOR_RECTANGLE:
+ res = sample_rectangle_ray(sensor->rectangle, htrdr, ipix,
+ pix_sz, rng, ray_org, ray_dir);
+ break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+ return res;
+}
+
diff --git a/src/core/htrdr_sensor.h b/src/core/htrdr_sensor.h
@@ -0,0 +1,38 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_SENSOR_H
+#define HTRDR_SENSOR_H
+
+/* Forward declarations */
+struct htrdr;
+struct htrdr_camera;
+struct htrdr_rectangle;
+struct ssp_rng;
+
+enum htrdr_sensor_type {
+ HTRDR_SENSOR_CAMERA,
+ HTRDR_SENSOR_RECTANGLE
+};
+
+struct htrdr_sensor {
+ struct htrdr_camera* camera;
+ struct htrdr_rectangle* rectangle;
+ enum htrdr_sensor_type type;
+};
+
+#endif /* HTRDR_SENSOR_H */
+
diff --git a/src/htrdr_slab.c b/src/core/htrdr_slab.c
diff --git a/src/core/htrdr_slab.h b/src/core/htrdr_slab.h
@@ -0,0 +1,52 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_SLAB_H
+#define HTRDR_SLAB_H
+
+#include "htrdr.h"
+#include <rsys/rsys.h>
+
+/* Forward declaration */
+struct htrdr;
+
+typedef res_T
+(*htrdr_trace_cell_T)
+ (const double org[3], /* Ray origin */
+ const double dir[3], /* Ray direction. Must be normalized */
+ const double range[2], /* Ray range */
+ void* ctx, /* User defined data */
+ int* hit); /* Hit something ? */
+
+BEGIN_DECLS
+
+/* Trace a ray into a slab composed of a cell infinitely repeated in X and Y */
+HTRDR_API res_T
+htrdr_slab_trace_ray
+ (struct htrdr* htrdr,
+ const double org[3],
+ const double dir[3],
+ const double range[2],
+ const double cell_low[2],
+ const double cell_upp[2],
+ htrdr_trace_cell_T trace_cell,
+ const size_t max_steps, /* Max traversed cell */
+ void* trace_cell_context);
+
+END_DECLS
+
+#endif /* HTRDR_SLAB_H */
+
diff --git a/src/core/htrdr_spectral.c b/src/core/htrdr_spectral.c
@@ -0,0 +1,114 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "htrdr.h"
+#include "htrdr_spectral.h"
+
+/*******************************************************************************
+ * Exported symbols
+ ******************************************************************************/
+res_T
+htrdr_brightness_temperature
+ (struct htrdr* htrdr,
+ const double lambda_min,
+ const double lambda_max,
+ const double radiance, /* In W/m2/sr/m */
+ double* temperature)
+{
+ const size_t MAX_ITER = 100;
+ const double epsilon_T = 1e-4; /* In K */
+ const double epsilon_B = radiance * 1e-8;
+ double T, T0, T1, T2;
+ double B, B0;
+ size_t i;
+ res_T res = RES_OK;
+ ASSERT(temperature && lambda_min <= lambda_max);
+
+ /* Search for a brightness temperature whose radiance is greater than or
+ * equal to the estimated radiance */
+ T2 = 200;
+ FOR_EACH(i, 0, MAX_ITER) {
+ const double B2 = htrdr_planck(lambda_min, lambda_max, T2);
+ if(B2 >= radiance) break;
+ T2 *= 2;
+ }
+ if(i >= MAX_ITER) { res = RES_BAD_OP; goto error; }
+
+ B0 = T0 = T1 = 0;
+ FOR_EACH(i, 0, MAX_ITER) {
+ T = (T1+T2)*0.5;
+ B = htrdr_planck(lambda_min, lambda_max, T);
+
+ if(B < radiance) {
+ T1 = T;
+ } else {
+ T2 = T;
+ }
+
+ if(fabs(T-T0) < epsilon_T || fabs(B-B0) < epsilon_B)
+ break;
+
+ T0 = T;
+ B0 = B;
+ }
+ if(i >= MAX_ITER) { res = RES_BAD_OP; goto error; }
+
+ *temperature = T;
+
+exit:
+ return res;
+error:
+ htrdr_log_err(htrdr,
+ "Could not compute the brightness temperature for the estimated radiance %g "
+ "averaged over [%g, %g] nanometers.\n",
+ radiance,
+ lambda_min*1e9,
+ lambda_max*1e9);
+ goto exit;
+}
+
+double
+htrdr_radiance_temperature
+ (struct htrdr* htrdr,
+ const double lambda_min, /* In meters */
+ const double lambda_max, /* In meters */
+ const double radiance) /* In W/m^2/sr */
+{
+ double temperature = 0;
+ double radiance_avg = radiance;
+ res_T res = RES_OK;
+ ASSERT(htrdr && radiance >= 0);
+
+ /* From integrated radiance to average radiance in W/m^2/sr/m */
+ if(lambda_min != lambda_max) { /* !monochromatic */
+ radiance_avg /= (lambda_max - lambda_min);
+ }
+
+ res = htrdr_brightness_temperature
+ (htrdr,
+ lambda_min,
+ lambda_max,
+ radiance_avg,
+ &temperature);
+ if(res != RES_OK) {
+ htrdr_log_warn(cmd->htrdr,
+ "Could not compute the brightness temperature for the radiance %g.\n",
+ radiance_avg);
+ temperature = 0;
+ }
+ return temperature;
+}
+
diff --git a/src/core/htrdr_spectral.h b/src/core/htrdr_spectral.h
@@ -0,0 +1,150 @@
+/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
+ * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef HTRDR_SPECTRAL_H
+#define HTRDR_SPECTRAL_H
+
+#include "htrdr.h"
+
+#include <rsys/rsys.h>
+#include <rsys/math.h> /* PI support */
+
+#define HTRDR_SUN_TEMPERATURE 5778 /* In K */
+#define HTRDR_DEFAULT_LW_REF_TEMPERATURE 290 /* Default longwave temperature in K */
+
+enum htrdr_spectral_type {
+ HTRDR_SPECTRAL_LW, /* Longwave */
+ HTRDR_SPECTRAL_SW, /* Shortwave */
+ HTRDR_SPECTRAL_SW_CIE_XYZ /* Shortwave wrt the CIE XYZ tristimulus */
+};
+
+struct htrdr;
+
+static INLINE double
+htrdr_wiebelt(const double v)
+{
+ int m;
+ double w, v2, v4;
+ /*.153989717364e+00;*/
+ const double fifteen_over_pi_power_4 = 15.0/(PI*PI*PI*PI);
+ const double z0 = 1.0/3.0;
+ const double z1 = 1.0/8.0;
+ const double z2 = 1.0/60.0;
+ const double z4 = 1.0/5040.0;
+ const double z6 = 1.0/272160.0;
+ const double z8 = 1.0/13305600.0;
+
+ if(v >= 2.) {
+ w = 0;
+ for(m=1; m<6 ;m++)
+ w+=exp(-m*v)/(m*m*m*m) * (((m*v+3)*m*v+6)*m*v+6);
+ w = w * fifteen_over_pi_power_4;
+ } else {
+ v2 = v*v;
+ v4 = v2*v2;
+ w = z0 - z1*v + z2*v2 - z4*v2*v2 + z6*v4*v2 - z8*v4*v4;
+ w = 1. - fifteen_over_pi_power_4*v2*v*w;
+ }
+ ASSERT(w >= 0.0 && w <= 1.0);
+ return w;
+}
+
+static INLINE double
+htrdr_blackbody_fraction
+ (const double lambda0, /* In meters */
+ const double lambda1, /* In meters */
+ const double temperature) /* In Kelvin */
+{
+ const double C2 = 1.43877735e-2; /* m.K */
+ double x0 = C2 / lambda0;
+ double x1 = C2 / lambda1;
+ double v0 = x0 / temperature;
+ double v1 = x1 / temperature;
+ double w0 = htrdr_wiebelt(v0);
+ double w1 = htrdr_wiebelt(v1);
+ return w1 - w0;
+}
+
+/* Return the Planck value in W/m^2/sr/m at a given wavelength */
+static INLINE double
+htrdr_planck_monochromatic
+ (const double lambda, /* In meters */
+ const double temperature) /* In Kelvin */
+{
+ const double c = 299792458; /* m/s */
+ const double h = 6.62607015e-34; /* J.s */
+ const double k = 1.380649e-23; /* J/K */
+ const double lambda2 = lambda*lambda;
+ const double lambda5 = lambda2*lambda2*lambda;
+ const double B = ((2.0 * h * c*c) / lambda5) /* W/m^2/sr/m */
+ / (exp(h*c/(lambda*k*temperature))-1.0);
+ ASSERT(temperature > 0);
+ return B;
+}
+
+/* Return the average Planck value in W/m^2/sr/m over the
+ * [lambda_min, lambda_max] interval. */
+static INLINE double
+htrdr_planck_interval
+ (const double lambda_min, /* In meters */
+ const double lambda_max, /* In meters */
+ const double temperature) /* In Kelvin */
+{
+ const double T2 = temperature*temperature;
+ const double T4 = T2*T2;
+ const double BOLTZMANN_CONSTANT = 5.6696e-8; /* W/m^2/K^4 */
+ ASSERT(lambda_min < lambda_max && temperature > 0);
+ return htrdr_blackbody_fraction(lambda_min, lambda_max, temperature)
+ * BOLTZMANN_CONSTANT * T4 / (PI * (lambda_max-lambda_min)); /* In W/m^2/sr/m */
+}
+
+/* Invoke planck_monochromatic or planck_interval whether the submitted
+ * interval is null or not, respectively. The returned value is in W/m^2/sr/m */
+static FINLINE double
+htrdr_planck
+ (const double lambda_min, /* In meters */
+ const double lambda_max, /* In meters */
+ const double temperature) /* In Kelvin */
+{
+ ASSERT(lambda_min <= lambda_max && temperature > 0);
+ if(lambda_min == lambda_max) {
+ return htrdr_planck_monochromatic(lambda_min, temperature);
+ } else {
+ return htrdr_planck_interval(lambda_min, lambda_max, temperature);
+ }
+}
+
+BEGIN_DECL
+
+HTRDR_API res_T
+htrdr_brightness_temperature
+ (struct htrdr* htrdr,
+ const double lambda_min, /* In meters */
+ const double lambda_max, /* In meters */
+ /* Averaged over [lambda_min, lambda_max]. In W/m^2/sr/m */
+ const double radiance,
+ double* temperature);
+
+HTRDR_API double
+htrdr_radiance_temperature
+ (struct htrdr* htrdr,
+ const double lambda_min, /* In meters */
+ const double lambda_max, /* In meters */
+ const double radiance) /* In W/m^2/sr */
+
+END_DECL
+
+#endif /* HTRDR_SPECTRAL_H */
diff --git a/src/htrdr_version.h.in b/src/core/htrdr_version.h.in
diff --git a/src/htrdr.c b/src/htrdr.c
@@ -1,1034 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#define _POSIX_C_SOURCE 200809L /* stat.st_time support */
-
-#include "htrdr.h"
-#include "htrdr_c.h"
-#include "htrdr_args.h"
-#include "htrdr_buffer.h"
-#include "htrdr_cie_xyz.h"
-#include "htrdr_camera.h"
-#include "htrdr_ground.h"
-#include "htrdr_materials.h"
-#include "htrdr_ran_wlen.h"
-#include "htrdr_rectangle.h"
-#include "htrdr_sun.h"
-#include "htrdr_solve.h"
-#include "htrdr_version.h"
-
-#include <rsys/cstr.h>
-#include <rsys/mem_allocator.h>
-#include <rsys/str.h>
-
-#include "high_tune/htsky.h"
-
-#include <star/s3d.h>
-#include <star/ssf.h>
-
-#include <errno.h>
-#include <fcntl.h> /* open */
-#include <libgen.h> /* basename */
-#include <stdarg.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <time.h>
-#include <sys/time.h> /* timespec */
-#include <sys/stat.h> /* S_IRUSR & S_IWUSR */
-
-#include <omp.h>
-#include <mpi.h>
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static void
-print_out(const char* msg, void* ctx)
-{
- ASSERT(msg);
- (void)ctx;
-#ifdef OS_UNIX
- fprintf(stderr, "\x1b[1m\x1b[32m>\x1b[0m %s", msg);
-#else
- fprintf(stderr, "> %s", msg);
-#endif
-}
-
-static void
-print_err(const char* msg, void* ctx)
-{
- ASSERT(msg);
- (void)ctx;
-#ifdef OS_UNIX
- fprintf(stderr, "\x1b[31merror:\x1b[0m %s", msg);
-#else
- fprintf(stderr, "error: %s", msg);
-#endif
-}
-
-static void
-print_warn(const char* msg, void* ctx)
-{
- ASSERT(msg);
- (void)ctx;
-#ifdef OS_UNIX
- fprintf(stderr, "\x1b[33mwarning:\x1b[0m %s", msg);
-#else
- fprintf(stderr,"warning: %s", msg);
-#endif
-}
-
-static void
-log_msg
- (struct htrdr* htrdr,
- const enum log_type stream,
- const char* msg,
- va_list vargs)
-{
- ASSERT(htrdr && msg);
- if(htrdr->verbose) {
- CHK(logger_vprint(&htrdr->logger, stream, msg, vargs) == RES_OK);
- }
-}
-
-static enum htsky_spectral_type
-htrdr_to_sky_spectral_type(const enum htrdr_spectral_type type)
-{
- enum htsky_spectral_type spectype;
- switch(type) {
- case HTRDR_SPECTRAL_LW:
- spectype = HTSKY_SPECTRAL_LW;
- break;
- case HTRDR_SPECTRAL_SW:
- case HTRDR_SPECTRAL_SW_CIE_XYZ:
- spectype = HTSKY_SPECTRAL_SW;
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- return spectype;
-}
-
-static res_T
-dump_buffer
- (struct htrdr* htrdr,
- struct htrdr_buffer* buf,
- struct htrdr_accum* time_acc, /* May be NULL */
- struct htrdr_accum* flux_acc, /* May be NULL */
- const char* stream_name,
- FILE* stream)
-{
- struct htrdr_buffer_layout layout;
- size_t pixsz, pixal;
- size_t x, y;
- res_T res = RES_OK;
- ASSERT(htrdr && buf && stream_name && stream);
- (void)stream_name;
-
- pixsz = htrdr_spectral_type_get_pixsz
- (htrdr->spectral_type, htrdr->sensor.type);
- pixal = htrdr_spectral_type_get_pixal
- (htrdr->spectral_type, htrdr->sensor.type);
-
- htrdr_buffer_get_layout(buf, &layout);
- if(layout.elmt_size != pixsz || layout.alignment != pixal) {
- htrdr_log_err(htrdr, "%s: invalid buffer layout. ", FUNC_NAME);
- res = RES_BAD_ARG;
- goto error;
- }
-
- fprintf(stream, "%lu %lu\n", layout.width, layout.height);
-
- if(time_acc) *time_acc = HTRDR_ACCUM_NULL;
- if(flux_acc) *flux_acc = HTRDR_ACCUM_NULL;
- FOR_EACH(y, 0, layout.height) {
- FOR_EACH(x, 0, layout.width) {
- struct htrdr_estimate pix_time = HTRDR_ESTIMATE_NULL;
- const struct htrdr_accum* pix_time_acc = NULL;
-
- if(htrdr->sensor.type == HTRDR_SENSOR_RECTANGLE) {
- const struct htrdr_pixel_flux* pix = htrdr_buffer_at(buf, x, y);
- struct htrdr_estimate flux = HTRDR_ESTIMATE_NULL;
-
- if(pix->flux.nweights == 0) {
- fprintf(stream, "0 0 0 0 0 0 ");
- } else {
- htrdr_accum_get_estimation(&pix->flux, &flux);
- fprintf(stream, "%g %g 0 0 0 0 ", flux.E, flux.SE);
-
- if(flux_acc) {
- flux_acc->sum_weights += pix->flux.sum_weights;
- flux_acc->sum_weights_sqr += pix->flux.sum_weights_sqr;
- flux_acc->nweights += pix->flux.nweights;
- }
- }
- pix_time_acc = &pix->time;
-
- } else {
- if(htrdr->spectral_type != HTRDR_SPECTRAL_SW_CIE_XYZ){
- const struct htrdr_pixel_xwave* pix = htrdr_buffer_at(buf, x, y);
- fprintf(stream, "%g %g ",
- pix->radiance_temperature.E, pix->radiance_temperature.SE);
- fprintf(stream, "%g %g ", pix->radiance.E, pix->radiance.SE);
- fprintf(stream, "0 0 ");
- pix_time_acc = &pix->time;
-
- } else {
- const struct htrdr_pixel_image* pix = htrdr_buffer_at(buf, x, y);
- fprintf(stream, "%g %g ", pix->X.E, pix->X.SE);
- fprintf(stream, "%g %g ", pix->Y.E, pix->Y.SE);
- fprintf(stream, "%g %g ", pix->Z.E, pix->Z.SE);
- pix_time_acc = &pix->time;
- }
- }
-
- htrdr_accum_get_estimation(pix_time_acc, &pix_time);
- fprintf(stream, "%g %g\n", pix_time.E, pix_time.SE);
-
- if(time_acc) {
- time_acc->sum_weights += pix_time_acc->sum_weights;
- time_acc->sum_weights_sqr += pix_time_acc->sum_weights_sqr;
- time_acc->nweights += pix_time_acc->nweights;
- }
- }
- fprintf(stream, "\n");
- }
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-static INLINE void
-spherical_to_cartesian_dir
- (const double azimuth, /* In radians */
- const double elevation, /* In radians */
- double dir[3])
-{
- double cos_azimuth;
- double sin_azimuth;
- double cos_elevation;
- double sin_elevation;
- ASSERT(azimuth >= 0 && azimuth < 2*PI);
- ASSERT(elevation >= 0 && elevation <= PI/2.0);
- ASSERT(dir);
-
- cos_azimuth = cos(azimuth);
- sin_azimuth = sin(azimuth);
- cos_elevation = cos(elevation);
- sin_elevation = sin(elevation);
-
- dir[0] = cos_elevation * cos_azimuth;
- dir[1] = cos_elevation * sin_azimuth;
- dir[2] = sin_elevation;
-}
-
-static void
-release_mpi(struct htrdr* htrdr)
-{
- ASSERT(htrdr);
- if(htrdr->mpi_working_procs) {
- MEM_RM(htrdr->allocator, htrdr->mpi_working_procs);
- htrdr->mpi_working_procs = NULL;
- }
- if(htrdr->mpi_progress_octree) {
- MEM_RM(htrdr->allocator, htrdr->mpi_progress_octree);
- htrdr->mpi_progress_octree = NULL;
- }
- if(htrdr->mpi_progress_render) {
- MEM_RM(htrdr->allocator, htrdr->mpi_progress_render);
- htrdr->mpi_progress_render = NULL;
- }
- if(htrdr->mpi_err_str) {
- MEM_RM(htrdr->allocator, htrdr->mpi_err_str);
- htrdr->mpi_err_str = NULL;
- }
- if(htrdr->mpi_mutex) {
- mutex_destroy(htrdr->mpi_mutex);
- htrdr->mpi_mutex = NULL;
- }
-}
-
-static res_T
-mpi_print_proc_info(struct htrdr* htrdr)
-{
- char proc_name[MPI_MAX_PROCESSOR_NAME];
- int proc_name_len;
- char* proc_names = NULL;
- uint32_t* proc_nthreads = NULL;
- uint32_t nthreads = 0;
- int iproc;
- res_T res = RES_OK;
- ASSERT(htrdr);
-
- if(htrdr->mpi_rank == 0) {
- proc_names = MEM_CALLOC(htrdr->allocator, (size_t)htrdr->mpi_nprocs,
- MPI_MAX_PROCESSOR_NAME*sizeof(*proc_names));
- if(!proc_names) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "could not allocate the temporary memory for MPI process names -- "
- "%s.\n", res_to_cstr(res));
- goto error;
- }
-
- proc_nthreads = MEM_CALLOC(htrdr->allocator, (size_t)htrdr->mpi_nprocs,
- sizeof(*proc_nthreads));
- if(!proc_nthreads) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "could not allocate the temporary memory for the #threads of the MPI "
- "processes -- %s.\n", res_to_cstr(res));
- goto error;
- }
- }
-
- /* Gather process name */
- MPI(Get_processor_name(proc_name, &proc_name_len));
- MPI(Gather(proc_name, MPI_MAX_PROCESSOR_NAME, MPI_CHAR, proc_names,
- MPI_MAX_PROCESSOR_NAME, MPI_CHAR, 0, MPI_COMM_WORLD));
-
- /* Gather process #threads */
- nthreads = (uint32_t)htrdr->nthreads;
- MPI(Gather(&nthreads, 1, MPI_UINT32_T, proc_nthreads, 1, MPI_UINT32_T, 0,
- MPI_COMM_WORLD));
-
- if(htrdr->mpi_rank == 0) {
- FOR_EACH(iproc, 0, htrdr->mpi_nprocs) {
- htrdr_log(htrdr, "Process %d -- %s; #threads: %u\n",
- iproc, proc_names + iproc*MPI_MAX_PROCESSOR_NAME, proc_nthreads[iproc]);
- }
- }
-
-exit:
- if(proc_names) MEM_RM(htrdr->allocator, proc_names);
- if(proc_nthreads) MEM_RM(htrdr->allocator, proc_nthreads);
- return res;
-error:
- goto exit;
-}
-
-static res_T
-init_mpi(struct htrdr* htrdr)
-{
- size_t n;
- int err;
- res_T res = RES_OK;
- ASSERT(htrdr);
-
- htrdr->mpi_err_str = MEM_CALLOC
- (htrdr->allocator, htrdr->nthreads, MPI_MAX_ERROR_STRING);
- if(!htrdr->mpi_err_str) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "could not allocate the MPI error strings -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- err = MPI_Comm_rank(MPI_COMM_WORLD, &htrdr->mpi_rank);
- if(err != MPI_SUCCESS) {
- htrdr_log_err(htrdr,
- "could not determine the MPI rank of the calling process -- %s.\n",
- htrdr_mpi_error_string(htrdr, err));
- res = RES_UNKNOWN_ERR;
- goto error;
- }
-
- err = MPI_Comm_size(MPI_COMM_WORLD, &htrdr->mpi_nprocs);
- if(err != MPI_SUCCESS) {
- htrdr_log_err(htrdr,
- "could retrieve the size of the MPI group -- %s.\n",
- htrdr_mpi_error_string(htrdr, err));
- res = RES_UNKNOWN_ERR;
- goto error;
- }
-
- htrdr->mpi_working_procs = MEM_CALLOC(htrdr->allocator,
- (size_t)htrdr->mpi_nprocs, sizeof(*htrdr->mpi_working_procs));
- if(!htrdr->mpi_working_procs) {
- htrdr_log_err(htrdr,
- "could not allocate the list of working processes.\n");
- res = RES_MEM_ERR;
- goto error;
- }
-
- /* Initialy, all the processes are working */
- htrdr->mpi_nworking_procs = (size_t)htrdr->mpi_nprocs;
- memset(htrdr->mpi_working_procs, 0xFF,
- htrdr->mpi_nworking_procs*sizeof(*htrdr->mpi_working_procs));
-
- /* Allocate #processes progress statuses on the master process and only 1
- * progress status on the other ones: the master process will gather the
- * status of the other processes to report their progression. */
- n = (size_t)(htrdr->mpi_rank == 0 ? htrdr->mpi_nprocs : 1);
-
- htrdr->mpi_progress_octree = MEM_CALLOC
- (htrdr->allocator, n, sizeof(*htrdr->mpi_progress_octree));
- if(!htrdr->mpi_progress_octree) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "could not allocate the progress state of the octree building -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- htrdr->mpi_progress_render = MEM_CALLOC
- (htrdr->allocator, n, sizeof(*htrdr->mpi_progress_render));
- if(!htrdr->mpi_progress_render) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "could not allocate the progress state of the scene rendering -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- htrdr->mpi_mutex = mutex_create();
- if(!htrdr->mpi_mutex) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "could not create the mutex to protect MPI calls from concurrent "
- "threads -- %s.\n", res_to_cstr(res));
- goto error;
- }
-
- mpi_print_proc_info(htrdr);
-
-exit:
- return res;
-error:
- release_mpi(htrdr);
- goto exit;
-}
-
-static res_T
-setup_sensor(struct htrdr* htrdr, const struct htrdr_args* args)
-{
- double proj_ratio;
- res_T res = RES_OK;
- ASSERT(htrdr && args);
-
- htrdr->sensor.type = args->sensor_type;
-
- if(args->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ
- && args->sensor_type != HTRDR_SENSOR_CAMERA) {
- htrdr_log_err(htrdr, "the CIE 1931 XYZ spectral integration can be used "
- "only with a camera sensor.\n");
- res = RES_BAD_ARG;
- goto error;
- }
-
- switch(args->sensor_type) {
- case HTRDR_SENSOR_CAMERA:
- proj_ratio =
- (double)args->image.definition[0]
- / (double)args->image.definition[1];
- res = htrdr_camera_create(htrdr, args->camera.pos, args->camera.tgt,
- args->camera.up, proj_ratio, MDEG2RAD(args->camera.fov_y),
- &htrdr->sensor.camera);
- break;
- case HTRDR_SENSOR_RECTANGLE:
- res = htrdr_rectangle_create(htrdr, args->rectangle.sz,
- args->rectangle.pos, args->rectangle.tgt, args->rectangle.up,
- &htrdr->sensor.rectangle);
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- if(res != RES_OK) goto error;
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_init
- (struct mem_allocator* mem_allocator,
- const struct htrdr_args* args,
- struct htrdr* htrdr)
-{
- struct htsky_args htsky_args = HTSKY_ARGS_DEFAULT;
- double sun_dir[3];
- double spectral_range[2];
- const char* output_name = NULL;
- size_t ithread;
- int nthreads_max;
- res_T res = RES_OK;
- ASSERT(args && htrdr);
-
- memset(htrdr, 0, sizeof(*htrdr));
-
- htrdr->allocator = mem_allocator ? mem_allocator : &mem_default_allocator;
-
- logger_init(htrdr->allocator, &htrdr->logger);
- logger_set_stream(&htrdr->logger, LOG_OUTPUT, print_out, NULL);
- logger_set_stream(&htrdr->logger, LOG_ERROR, print_err, NULL);
- logger_set_stream(&htrdr->logger, LOG_WARNING, print_warn, NULL);
- str_init(htrdr->allocator, &htrdr->output_name);
- nthreads_max = MMAX(omp_get_max_threads(), omp_get_num_procs());
- htrdr->dump_vtk = args->dump_vtk;
- htrdr->verbose = args->verbose;
- htrdr->nthreads = MMIN(args->nthreads, (unsigned)nthreads_max);
- htrdr->spp = args->image.spp;
- htrdr->width = args->image.definition[0];
- htrdr->height = args->image.definition[1];
- htrdr->grid_max_definition[0] = args->grid_max_definition[0];
- htrdr->grid_max_definition[1] = args->grid_max_definition[1];
- htrdr->grid_max_definition[2] = args->grid_max_definition[2];
- htrdr->spectral_type = args->spectral_type;
- htrdr->ref_temperature = args->ref_temperature;
- htrdr->sky_mtl_name = args->sky_mtl_name;
-
- res = init_mpi(htrdr);
- if(res != RES_OK) goto error;
-
- if(!args->output) {
- htrdr->output = stdout;
- output_name = "<stdout>";
- } else if(htrdr->mpi_rank != 0) {
- htrdr->output = NULL;
- output_name = "<null>";
- } else {
- res = open_output_stream
- (htrdr, args->output, 0/*read*/, args->force_overwriting, &htrdr->output);
- if(res != RES_OK) goto error;
- output_name = args->output;
- }
- res = str_set(&htrdr->output_name, output_name);
- if(res != RES_OK) {
- htrdr_log_err(htrdr,
- "%s: could not store the name of the output stream `%s' -- %s.\n",
- FUNC_NAME, output_name, res_to_cstr(res));
- goto error;
- }
-
- /* Disable the Star-3D verbosity since the Embree backend prints some messages
- * on stdout rather than stderr. This is annoying since stdout may be used by
- * htrdr to write output data */
- res = s3d_device_create
- (&htrdr->logger, htrdr->allocator, 0, &htrdr->s3d);
- if(res != RES_OK) {
- htrdr_log_err(htrdr,
- "%s: could not create the Star-3D device -- %s.\n",
- FUNC_NAME, res_to_cstr(res));
- goto error;
- }
-
- /* Materials are necessary only if a ground geometry is defined */
- if(args->filename_obj) {
- res = htrdr_materials_create(htrdr, args->filename_mtl, &htrdr->mats);
- if(res != RES_OK) goto error;
- }
-
- res = htrdr_ground_create(htrdr, args->filename_obj, args->repeat_ground,
- &htrdr->ground);
- if(res != RES_OK) goto error;
-
- res = setup_sensor(htrdr, args);
- if(res != RES_OK) goto error;
-
- res = htrdr_sun_create(htrdr, &htrdr->sun);
- if(res != RES_OK) goto error;
- spherical_to_cartesian_dir
- (MDEG2RAD(args->sun_azimuth), MDEG2RAD(args->sun_elevation), sun_dir);
- htrdr_sun_set_direction(htrdr->sun, sun_dir);
-
- htsky_args.htcp_filename = args->filename_les;
- htsky_args.htgop_filename = args->filename_gas;
- htsky_args.htmie_filename = args->filename_mie;
- htsky_args.cache_filename = args->cache;
- htsky_args.grid_max_definition[0] = args->grid_max_definition[0];
- htsky_args.grid_max_definition[1] = args->grid_max_definition[1];
- htsky_args.grid_max_definition[2] = args->grid_max_definition[2];
- htsky_args.optical_thickness = args->optical_thickness;
- htsky_args.nthreads = htrdr->nthreads;
- htsky_args.repeat_clouds = args->repeat_clouds;
- htsky_args.verbose = htrdr->mpi_rank == 0 ? args->verbose : 0;
- htsky_args.spectral_type = htrdr_to_sky_spectral_type(args->spectral_type);
- htsky_args.wlen_range[0] = args->wlen_range[0];
- htsky_args.wlen_range[1] = args->wlen_range[1];
- res = htsky_create(&htrdr->logger, htrdr->allocator, &htsky_args, &htrdr->sky);
- if(res != RES_OK) goto error;
-
- HTSKY(get_raw_spectral_bounds(htrdr->sky, spectral_range));
-
- spectral_range[0] = MMAX(args->wlen_range[0], spectral_range[0]);
- spectral_range[1] = MMIN(args->wlen_range[1], spectral_range[1]);
- if(spectral_range[0] != args->wlen_range[0]
- || spectral_range[1] != args->wlen_range[1]) {
- htrdr_log_warn(htrdr,
- "%s: the submitted spectral range overflowed the spectral data.\n", FUNC_NAME);
- }
-
- htrdr->wlen_range_m[0] = spectral_range[0]*1e-9; /* Convert in meters */
- htrdr->wlen_range_m[1] = spectral_range[1]*1e-9; /* Convert in meters */
-
- if(htrdr->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ) {
- size_t n;
- n = (size_t)(spectral_range[1] - spectral_range[0]);
- res = htrdr_cie_xyz_create(htrdr, spectral_range, n, &htrdr->cie);
- if(res != RES_OK) goto error;
-
- } else {
- size_t n;
-
- if(htrdr->ref_temperature <= 0) {
- htrdr_log_err(htrdr, "%s: invalid reference temperature %g K.\n",
- FUNC_NAME, htrdr->ref_temperature);
- res = RES_BAD_ARG;
- goto error;
- }
-
- ASSERT(htrdr->wlen_range_m[0] <= htrdr->wlen_range_m[1]);
- n = (size_t)(spectral_range[1] - spectral_range[0]);
-
- res = htrdr_ran_wlen_create
- (htrdr, spectral_range, n, htrdr->ref_temperature, &htrdr->ran_wlen);
- if(res != RES_OK) goto error;
- }
-
- htrdr->lifo_allocators = MEM_CALLOC
- (htrdr->allocator, htrdr->nthreads, sizeof(*htrdr->lifo_allocators));
- if(!htrdr->lifo_allocators) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "%s: could not allocate the list of per thread LIFO allocator -- %s.\n",
- FUNC_NAME, res_to_cstr(res));
- goto error;
- }
-
- FOR_EACH(ithread, 0, htrdr->nthreads) {
- res = mem_init_lifo_allocator
- (&htrdr->lifo_allocators[ithread], htrdr->allocator, 16384);
- if(res != RES_OK) {
- htrdr_log_err(htrdr,
- "%s: could not initialise the LIFO allocator of the thread %lu -- %s.\n",
- FUNC_NAME, (unsigned long)ithread, res_to_cstr(res));
- goto error;
- }
- }
-
- /* Create the image buffer only on the master process; the image parts
- * rendered by the processes are gathered onto the master process. */
- if(!htrdr->dump_vtk && htrdr->mpi_rank == 0) {
- const size_t pixsz = htrdr_spectral_type_get_pixsz
- (htrdr->spectral_type, htrdr->sensor.type);
- const size_t pixal = htrdr_spectral_type_get_pixal
- (htrdr->spectral_type, htrdr->sensor.type);
-
- res = htrdr_buffer_create(htrdr,
- args->image.definition[0], /* Width */
- args->image.definition[1], /* Height */
- args->image.definition[0] * pixsz, /* Pitch */
- pixsz, /* Size of a pixel */
- pixal, /* Alignment of a pixel */
- &htrdr->buf);
- if(res != RES_OK) goto error;
- }
-
-exit:
- return res;
-error:
- htrdr_release(htrdr);
- goto exit;
-}
-
-void
-htrdr_release(struct htrdr* htrdr)
-{
- ASSERT(htrdr);
- release_mpi(htrdr);
- if(htrdr->s3d) S3D(device_ref_put(htrdr->s3d));
- if(htrdr->ground) htrdr_ground_ref_put(htrdr->ground);
- if(htrdr->sky) HTSKY(ref_put(htrdr->sky));
- if(htrdr->sun) htrdr_sun_ref_put(htrdr->sun);
- if(htrdr->sensor.camera) htrdr_camera_ref_put(htrdr->sensor.camera);
- if(htrdr->sensor.rectangle) htrdr_rectangle_ref_put(htrdr->sensor.rectangle);
- if(htrdr->buf) htrdr_buffer_ref_put(htrdr->buf);
- if(htrdr->mats) htrdr_materials_ref_put(htrdr->mats);
- if(htrdr->cie) htrdr_cie_xyz_ref_put(htrdr->cie);
- if(htrdr->ran_wlen) htrdr_ran_wlen_ref_put(htrdr->ran_wlen);
- if(htrdr->output && htrdr->output != stdout) fclose(htrdr->output);
- if(htrdr->lifo_allocators) {
- size_t i;
- FOR_EACH(i, 0, htrdr->nthreads) {
- mem_shutdown_lifo_allocator(&htrdr->lifo_allocators[i]);
- }
- MEM_RM(htrdr->allocator, htrdr->lifo_allocators);
- }
- str_release(&htrdr->output_name);
- logger_release(&htrdr->logger);
-}
-
-res_T
-htrdr_run(struct htrdr* htrdr)
-{
- res_T res = RES_OK;
- if(htrdr->dump_vtk) {
- const size_t nbands = htsky_get_spectral_bands_count(htrdr->sky);
- size_t i;
-
- /* Nothing to do */
- if(htrdr->mpi_rank != 0) goto exit;
-
- FOR_EACH(i, 0, nbands) {
- const size_t iband = htsky_get_spectral_band_id(htrdr->sky, i);
- const size_t nquads = htsky_get_spectral_band_quadrature_length
- (htrdr->sky, iband);
- size_t iquad;
- FOR_EACH(iquad, 0, nquads) {
- res = htsky_dump_cloud_vtk(htrdr->sky, iband, iquad, htrdr->output);
- if(res != RES_OK) goto error;
- fprintf(htrdr->output, "---\n");
- }
- }
- } else {
- res = htrdr_draw_map(htrdr, &htrdr->sensor, htrdr->width, htrdr->height,
- htrdr->spp, htrdr->buf);
- if(res != RES_OK) goto error;
- if(htrdr->mpi_rank == 0) {
- struct htrdr_accum path_time_acc = HTRDR_ACCUM_NULL;
- struct htrdr_accum flux_acc = HTRDR_ACCUM_NULL;
- struct htrdr_estimate path_time;
- struct htrdr_estimate flux;
-
- res = dump_buffer(htrdr, htrdr->buf, &path_time_acc, &flux_acc,
- str_cget(&htrdr->output_name), htrdr->output);
- if(res != RES_OK) goto error;
-
- htrdr_accum_get_estimation(&path_time_acc, &path_time);
- htrdr_log(htrdr,
- "Time per radiative path (in micro seconds): %g +/- %g\n",
- path_time.E,
- path_time.SE);
-
- if(htrdr->sensor.type == HTRDR_SENSOR_RECTANGLE) {
- htrdr_accum_get_estimation(&flux_acc, &flux);
- htrdr_log(htrdr,
- "Radiative flux density (in W/(external m^2)): %g +/- %g\n",
- flux.E,
- flux.SE);
- }
- }
- }
-exit:
- return res;
-error:
- goto exit;
-}
-
-void
-htrdr_log(struct htrdr* htrdr, const char* msg, ...)
-{
- ASSERT(htrdr && msg);
- /* Log standard message only on master process */
- if(htrdr->mpi_rank == 0) {
- va_list vargs_list;
- va_start(vargs_list, msg);
- log_msg(htrdr, LOG_OUTPUT, msg, vargs_list);
- va_end(vargs_list);
- }
-}
-
-void
-htrdr_log_err(struct htrdr* htrdr, const char* msg, ...)
-{
- va_list vargs_list;
- ASSERT(htrdr && msg);
- /* Log errors on all processes */
- va_start(vargs_list, msg);
- log_msg(htrdr, LOG_ERROR, msg, vargs_list);
- va_end(vargs_list);
-}
-
-void
-htrdr_log_warn(struct htrdr* htrdr, const char* msg, ...)
-{
- ASSERT(htrdr && msg);
- /* Log warnings only on master process */
- if(htrdr->mpi_rank == 0) {
- va_list vargs_list;
- va_start(vargs_list, msg);
- log_msg(htrdr, LOG_WARNING, msg, vargs_list);
- va_end(vargs_list);
- }
-}
-
-const char*
-htrdr_mpi_error_string(struct htrdr* htrdr, const int mpi_err)
-{
- const int ithread = omp_get_thread_num();
- char* str;
- int strlen_err;
- int err;
- ASSERT(htrdr && (size_t)ithread < htrdr->nthreads);
- str = htrdr->mpi_err_str + ithread*MPI_MAX_ERROR_STRING;
- err = MPI_Error_string(mpi_err, str, &strlen_err);
- return err == MPI_SUCCESS ? str : "Invalid MPI error";
-}
-
-void
-htrdr_fprintf(struct htrdr* htrdr, FILE* stream, const char* msg, ...)
-{
- ASSERT(htrdr && msg);
- if(htrdr->mpi_rank == 0) {
- va_list vargs_list;
- va_start(vargs_list, msg);
- vfprintf(stream, msg, vargs_list);
- va_end(vargs_list);
- }
-}
-
-void
-htrdr_fflush(struct htrdr* htrdr, FILE* stream)
-{
- ASSERT(htrdr);
- if(htrdr->mpi_rank == 0) {
- fflush(stream);
- }
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-double
-compute_sky_min_band_len
- (struct htsky* sky,
- const double range[2])
-{
- double min_band_len = DBL_MAX;
- size_t nbands;
- ASSERT(sky && range && range[0] <= range[1]);
-
- nbands = htsky_get_spectral_bands_count(sky);
-
- if(eq_eps(range[0], range[1], 1.e-6)) {
- ASSERT(nbands == 1);
- min_band_len = 0;
- } else {
- size_t i = 0;
-
- /* Compute the length of the current band clamped to the submitted range */
- FOR_EACH(i, 0, nbands) {
- const size_t iband = htsky_get_spectral_band_id(sky, i);
- double wlens[2];
- HTSKY(get_spectral_band_bounds(sky, iband, wlens));
-
- /* Adjust band boundaries to the submitted range */
- wlens[0] = MMAX(wlens[0], range[0]);
- wlens[1] = MMIN(wlens[1], range[1]);
-
- min_band_len = MMIN(wlens[1] - wlens[0], min_band_len);
- }
- }
- return min_band_len;
-}
-
-res_T
-open_output_stream
- (struct htrdr* htrdr,
- const char* filename,
- const int read,
- int force_overwrite,
- FILE** out_fp)
-{
- FILE* fp = NULL;
- int fd = -1;
- const char* mode;
- res_T res = RES_OK;
- ASSERT(htrdr && filename && out_fp);
-
- mode = read ? "w+" : "w";
-
- if(force_overwrite) {
- fp = fopen(filename, mode);
- if(!fp) {
- htrdr_log_err(htrdr, "could not open the output file `%s'.\n", filename);
- goto error;
- }
- } else {
- const int access_flags = read ? O_RDWR : O_WRONLY;
- fd = open(filename, O_CREAT|O_EXCL|O_TRUNC|access_flags, S_IRUSR|S_IWUSR);
- if(fd >= 0) {
- fp = fdopen(fd, mode);
- if(fp == NULL) {
- htrdr_log_err(htrdr, "could not open the output file `%s'.\n", filename);
- goto error;
- }
- } else if(errno == EEXIST) {
- htrdr_log_err(htrdr, "the output file `%s' already exists. \n",
- filename);
- goto error;
- } else {
- htrdr_log_err(htrdr,
- "unexpected error while opening the output file `%s'.\n", filename);
- goto error;
- }
- }
-exit:
- *out_fp = fp;
- return res;
-error:
- res = RES_IO_ERR;
- if(fp) {
- CHK(fclose(fp) == 0);
- fp = NULL;
- } else if(fd >= 0) {
- CHK(close(fd) == 0);
- }
- goto exit;
-}
-
-void
-send_mpi_progress
- (struct htrdr* htrdr, const enum htrdr_mpi_message msg, int32_t percent)
-{
- ASSERT(htrdr);
- ASSERT(msg == HTRDR_MPI_PROGRESS_RENDERING
- || msg == HTRDR_MPI_PROGRESS_BUILD_OCTREE);
- (void)htrdr;
- mutex_lock(htrdr->mpi_mutex);
- MPI(Send(&percent, 1, MPI_INT32_T, 0, msg, MPI_COMM_WORLD));
- mutex_unlock(htrdr->mpi_mutex);
-}
-
-void
-fetch_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg)
-{
- struct timespec t;
- int32_t* progress = NULL;
- int iproc;
- ASSERT(htrdr && htrdr->mpi_rank == 0);
-
- t.tv_sec = 0;
- t.tv_nsec = 10000000; /* 10ms */
-
- switch(msg) {
- case HTRDR_MPI_PROGRESS_BUILD_OCTREE:
- progress = htrdr->mpi_progress_octree;
- break;
- case HTRDR_MPI_PROGRESS_RENDERING:
- progress = htrdr->mpi_progress_render;
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
-
- FOR_EACH(iproc, 1, htrdr->mpi_nprocs) {
- /* Flush the last sent percentage of the process `iproc' */
- for(;;) {
- MPI_Request req;
- int flag;
- int complete;
-
- mutex_lock(htrdr->mpi_mutex);
- MPI(Iprobe(iproc, msg, MPI_COMM_WORLD, &flag, MPI_STATUS_IGNORE));
- mutex_unlock(htrdr->mpi_mutex);
-
- if(flag == 0) break; /* No more message */
-
- mutex_lock(htrdr->mpi_mutex);
- MPI(Irecv(&progress[iproc], 1, MPI_INT32_T, iproc, msg, MPI_COMM_WORLD, &req));
- mutex_unlock(htrdr->mpi_mutex);
- for(;;) {
- mutex_lock(htrdr->mpi_mutex);
- MPI(Test(&req, &complete, MPI_STATUS_IGNORE));
- mutex_unlock(htrdr->mpi_mutex);
- if(complete) break;
- nanosleep(&t, NULL);
- }
- }
- }
-}
-
-void
-print_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg)
-{
- ASSERT(htrdr && htrdr->mpi_rank == 0);
-
- if(htrdr->mpi_nprocs == 1) {
- switch(msg) {
- case HTRDR_MPI_PROGRESS_BUILD_OCTREE:
- htrdr_fprintf(htrdr, stderr, "\033[2K\rBuilding octree: %3d%%",
- htrdr->mpi_progress_octree[0]);
- break;
- case HTRDR_MPI_PROGRESS_RENDERING:
- htrdr_fprintf(htrdr, stderr, "\033[2K\rRendering: %3d%%",
- htrdr->mpi_progress_render[0]);
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- htrdr_fflush(htrdr, stderr);
- } else {
- int iproc;
- FOR_EACH(iproc, 0, htrdr->mpi_nprocs) {
- switch(msg) {
- case HTRDR_MPI_PROGRESS_BUILD_OCTREE:
- htrdr_fprintf(htrdr, stderr,
- "\033[2K\rProcess %d -- building octree: %3d%%%c",
- iproc, htrdr->mpi_progress_octree[iproc],
- iproc == htrdr->mpi_nprocs - 1 ? '\r' : '\n');
- break;
- case HTRDR_MPI_PROGRESS_RENDERING:
- htrdr_fprintf(htrdr, stderr,
- "\033[2K\rProcess %d -- rendering: %3d%%%c",
- iproc, htrdr->mpi_progress_render[iproc],
- iproc == htrdr->mpi_nprocs - 1 ? '\r' : '\n');
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- }
- }
-}
-
-void
-clear_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg)
-{
- ASSERT(htrdr);
- (void)msg;
- if(htrdr->mpi_nprocs > 1) {
- htrdr_fprintf(htrdr, stderr, "\033[%dA", htrdr->mpi_nprocs-1);
- }
-}
-
-int
-total_mpi_progress(const struct htrdr* htrdr, const enum htrdr_mpi_message msg)
-{
- const int* progress = NULL;
- int total = 0;
- int iproc;
- ASSERT(htrdr && htrdr->mpi_rank == 0);
-
- switch(msg) {
- case HTRDR_MPI_PROGRESS_BUILD_OCTREE:
- progress = htrdr->mpi_progress_octree;
- break;
- case HTRDR_MPI_PROGRESS_RENDERING:
- progress = htrdr->mpi_progress_render;
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
-
- FOR_EACH(iproc, 0, htrdr->mpi_nprocs) {
- total += progress[iproc];
- }
- total = total / htrdr->mpi_nprocs;
- return total;
-}
-
diff --git a/src/htrdr.h b/src/htrdr.h
@@ -1,163 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_H
-#define HTRDR_H
-
-#include "htrdr_sensor.h"
-#include "htrdr_spectral.h"
-
-#include <rsys/logger.h>
-#include <rsys/ref_count.h>
-#include <rsys/str.h>
-
-/* Helper macro that asserts if the invocation of the htrdr function `Func'
- * returns an error. One should use this macro on htrdr function calls for
- * which no explicit error checking is performed */
-#ifndef NDEBUG
- #define HTRDR(Func) ASSERT(htrdr_ ## Func == RES_OK)
-#else
- #define HTRDR(Func) htrdr_ ## Func
-#endif
-
-/* Forward declarations */
-struct htsky;
-struct htrdr_args;
-struct htrdr_buffer;
-struct htrdr_cie_xyz;
-struct htrdr_materials;
-struct htrdr_rectangle;
-struct mem_allocator;
-struct mutext;
-struct s3d_device;
-struct s3d_scene;
-struct ssf_bsdf;
-struct ssf_phase;
-
-struct htrdr {
- struct s3d_device* s3d;
-
- struct htrdr_ground* ground;
- struct htrdr_materials* mats;
- struct htrdr_sun* sun;
- struct htrdr_cie_xyz* cie;
- struct htrdr_ran_wlen* ran_wlen;
-
- struct htrdr_sensor sensor;
- struct htrdr_buffer* buf;
-
- struct htsky* sky;
- const char* sky_mtl_name;
- enum htrdr_spectral_type spectral_type;
- double wlen_range_m[2]; /* Integration range in *meters* */
- double ref_temperature; /* Reference temperature in Kelvin */
-
- size_t spp; /* #samples per pixel */
- size_t width; /* Image width */
- size_t height; /* Image height */
-
- FILE* output;
- struct str output_name;
-
- unsigned grid_max_definition[3]; /* Max definition of the acceleration grids */
- unsigned nthreads; /* #threads of the process */
- int dump_vtk; /* Dump octree VTK */
- int verbose; /* Verbosity level */
-
- int mpi_rank; /* Rank of the process in the MPI group */
- int mpi_nprocs; /* Overall #processes in the MPI group */
- char* mpi_err_str; /* Temp buffer used to store MPI error string */
- int8_t* mpi_working_procs; /* Define the rank of active processes */
- size_t mpi_nworking_procs;
-
- /* Process progress percentage */
- int32_t* mpi_progress_octree;
- int32_t* mpi_progress_render;
-
- struct mutex* mpi_mutex; /* Protect MPI calls from concurrent threads */
-
- struct logger logger;
- struct mem_allocator* allocator;
- struct mem_allocator* lifo_allocators; /* Per thread lifo allocator */
-};
-
-extern LOCAL_SYM res_T
-htrdr_init
- (struct mem_allocator* allocator,
- const struct htrdr_args* args,
- struct htrdr* htrdr);
-
-extern LOCAL_SYM void
-htrdr_release
- (struct htrdr* htrdr);
-
-extern LOCAL_SYM res_T
-htrdr_run
- (struct htrdr* htrdr);
-
-extern LOCAL_SYM void
-htrdr_log
- (struct htrdr* htrdr,
- const char* msg,
- ...)
-#ifdef COMPILER_GCC
- __attribute((format(printf, 2, 3)))
-#endif
- ;
-
-extern LOCAL_SYM void
-htrdr_log_err
- (struct htrdr* htrdr,
- const char* msg,
- ...)
-#ifdef COMPILER_GCC
- __attribute((format(printf, 2, 3)))
-#endif
- ;
-
-extern LOCAL_SYM void
-htrdr_log_warn
- (struct htrdr* htrdr,
- const char* msg,
- ...)
-#ifdef COMPILER_GCC
- __attribute((format(printf, 2, 3)))
-#endif
- ;
-
-extern LOCAL_SYM const char*
-htrdr_mpi_error_string
- (struct htrdr* htrdr,
- const int mpi_err);
-
-extern LOCAL_SYM void
-htrdr_fprintf
- (struct htrdr* htrdr,
- FILE* stream,
- const char* msg,
- ...)
-#ifdef COMPILER_GCC
- __attribute((format(printf, 3, 4)))
-#endif
- ;
-
-extern LOCAL_SYM void
-htrdr_fflush
- (struct htrdr* htrdr,
- FILE* stream);
-
-#endif /* HTRDR_H */
-
diff --git a/src/htrdr_args.c b/src/htrdr_args.c
@@ -1,644 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#define _POSIX_C_SOURCE 2 /* strtok_r support */
-
-#include "htrdr_args.h"
-#include "htrdr_version.h"
-
-#include <rsys/cstr.h>
-#include <rsys/double3.h>
-
-#include <getopt.h>
-#include <string.h>
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static void
-print_help(const char* cmd)
-{
- ASSERT(cmd);
- printf("Usage: %s [OPION]... -a ATMOSPHERE\n", cmd);
- printf(
-"Render an image or compute a flux map for scenes composed of an\n"
-"atmospheric gas mixture, clouds and a ground.\n\n");
- printf(
-" -a ATMOSPHERE gas optical properties of the atmosphere.\n");
- printf(
-" -c CLOUDS properties of the clouds.\n");
- printf(
-" -C <camera> define the rendering point of view. Refer to the\n"
-" htrdr man page for the list of camera options.\n");
- printf(
-" -D AZIMUTH,ELEVATION\n"
-" direction in degrees toward the sun center. By default\n"
-" AZIMUTH is %g and ELEVATION is %g.\n",
- HTRDR_ARGS_DEFAULT.sun_azimuth,
- HTRDR_ARGS_DEFAULT.sun_elevation);
- printf(
-" -d dump octrees data to OUTPUT and exit.\n");
- printf(
-" -f overwrite the OUTPUT file if it already exists.\n");
- printf(
-" -g GROUND ground geometry.\n");
- printf(
-" -h display this help and exit.\n");
- printf(
-" -i <image> define the image to compute. Refer to the htrdr man\n"
-" page for the list of image options\n");
- printf(
-" -M MATERIALS file listing the ground materials.\n");
- printf(
-" -m MIE file of Mie's data.\n");
- printf(
-" -n SKY-NAME name used to identify the sky in the MATERIALS file.\n"
-" Its default value is `%s'.\n",
- HTRDR_ARGS_DEFAULT.sky_mtl_name);
- printf(
-" -O CACHE name of the cache file used to store/restore the sky\n"
-" data.\n");
- printf(
-" -o OUTPUT file where data are written. If not defined, data are\n"
-" written to standard output.\n");
- printf(
-" -p <rectangle> switch in flux computation by defining the rectangular\n"
-" sensor onto wich the flux is computed. Refer to the\n"
-" htrdr man page for the list of rectangle options.\n");
- printf(
-" -R infinitely repeat the ground along the X and Y axis.\n");
- printf(
-" -r infinitely repeat the clouds along the X and Y axis.\n");
- printf(
-" -s <spectral> define the type and range of the spectral\n"
-" integration. Refer to the htrdr man page for the list\n"
-" of spectral options\n");
- printf(
-" -T THRESHOLD optical thickness used as threshold during the octree\n"
-" building. By default its value is `%g'.\n",
- HTRDR_ARGS_DEFAULT.optical_thickness);
- printf(
-" -t THREADS hint on the number of threads to use. By default use\n"
-" as many threads as CPU cores.\n");
- printf(
-" -V X,Y,Z maximum definition of the cloud acceleration grids\n"
-" along the 3 axis. By default use the definition of\n"
-" the clouds\n");
- printf(
-" -v make the program verbose.\n");
- printf(
-" --version display version information and exit.\n");
- printf("\n");
- printf(
-"Copyright (C) 2018, 2019, 2020 |Meso|Star> <contact@meso-star.com>.\n"
-"Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier. htrdr is free\n"
-"software released under the GNU GPL license, version 3 or later. You\n"
-"are free to change or redistribute it under certain conditions\n"
-"<http://gnu.org/licenses/gpl.html>.\n");
-}
-
-static INLINE res_T
-parse_doubleX(const char* str, double* val, const size_t sz)
-{
- size_t len;
- res_T res = RES_OK;
- ASSERT(str && val);
- res = cstr_to_list_double(str, ',', val, &len, sz);
- if(res == RES_OK && len != sz) res = RES_BAD_ARG;
- return res;
-}
-
-static INLINE res_T
-parse_definition(const char* str, unsigned val[2])
-{
- size_t len;
- res_T res = RES_OK;
- ASSERT(str && val);
- res = cstr_to_list_uint(str, 'x', val, &len, 2);
- if(res != RES_OK) return res;
- if(len != 2) return RES_BAD_ARG;
- if(val[0] > 16384 || val[1] > 16384) return RES_BAD_ARG;
- return RES_OK;
-}
-
-static res_T
-parse_fov(const char* str, double* out_fov)
-{
- double fov;
- res_T res = RES_OK;
- ASSERT(str && out_fov);
-
- res = cstr_to_double(str, &fov);
- if(res != RES_OK) {
- fprintf(stderr, "Invalid field of view `%s'.\n", str);
- return RES_BAD_ARG;
- }
- if(fov <= 0 || fov >= 180) {
- fprintf(stderr, "The field of view %g is not in [30, 120].\n", fov);
- return RES_BAD_ARG;
- }
- *out_fov = fov;
- return RES_OK;
-}
-
-static res_T
-parse_image_parameter(struct htrdr_args* args, const char* str)
-{
- char buf[128];
- char* key;
- char* val;
- char* ctx;
- res_T res = RES_OK;
- ASSERT(str && args);
-
- if(strlen(str) >= sizeof(buf) -1/*NULL char*/) {
- fprintf(stderr,
- "Could not duplicate the image option string `%s'.\n", str);
- res = RES_MEM_ERR;
- goto error;
- }
- strncpy(buf, str, sizeof(buf));
-
- key = strtok_r(buf, "=", &ctx);
- val = strtok_r(NULL, "", &ctx);
-
- if(!val) {
- fprintf(stderr, "Missing a value to the image option `%s'.\n", key);
- res = RES_BAD_ARG;
- goto error;
- }
-
- #define PARSE(Name, Func) \
- res = Func; \
- if(res != RES_OK) { \
- fprintf(stderr, "Invalid image "Name" `%s'.\n", val); \
- goto error; \
- } (void)0
- if(!strcmp(key, "def")) {
- PARSE("definition", parse_definition(val, args->image.definition));
- } else if(!strcmp(key, "spp")) {
- PARSE("#samples per pixel", cstr_to_uint(val, &args->image.spp));
- } else {
- fprintf(stderr, "Invalid image parameter `%s'.\n", key);
- res = RES_BAD_ARG;
- goto error;
- }
- #undef PARSE
-
- if(!args->image.definition[0] || !args->image.definition[1]) {
- fprintf(stderr, "The image definition cannot be null.n");
- res = RES_BAD_ARG;
- goto error;
- }
- if(!args->image.spp) {
- fprintf(stderr, "The number of samples per pixel cannot be null.\n");
- res = RES_BAD_ARG;
- goto error;
- }
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-static res_T
-parse_camera_parameter(struct htrdr_args* args, const char* str)
-{
- char buf[128];
- char* key;
- char* val;
- char* ctx;
- res_T res = RES_OK;
- ASSERT(args);
-
- if(strlen(str) >= sizeof(buf) -1/*NULL char*/) {
- fprintf(stderr,
- "Could not duplicate the camera option string `%s'.\n", str);
- res = RES_MEM_ERR;
- goto error;
- }
- strncpy(buf, str, sizeof(buf));
-
- key = strtok_r(buf, "=", &ctx);
- val = strtok_r(NULL, "", &ctx);
-
- if(!val) {
- fprintf(stderr, "Missing value to the camera option `%s'.\n", key);
- res = RES_BAD_ARG;
- goto error;
- }
-
- #define PARSE(Name, Func) { \
- if(RES_OK != (res = Func)) { \
- fprintf(stderr, "Invalid camera "Name" `%s'.\n", val); \
- goto error; \
- } \
- } (void)0
- if(!strcmp(key, "pos")) {
- PARSE("position", parse_doubleX(val, args->camera.pos, 3));
- } else if(!strcmp(key, "tgt")) {
- PARSE("target", parse_doubleX(val, args->camera.tgt, 3));
- } else if(!strcmp(key, "up")) {
- PARSE("up vector", parse_doubleX(val, args->camera.up, 3));
- } else if(!strcmp(key, "fov")) {
- PARSE("field-of-view", parse_fov(val, &args->camera.fov_y));
- } else {
- fprintf(stderr, "Invalid camera parameter `%s'.\n", key);
- res = RES_BAD_ARG;
- goto error;
- }
- #undef PARSE
-exit:
- return res;
-error:
- goto exit;
-}
-
-static res_T
-parse_rectangle_parameter(struct htrdr_args* args, const char* str)
-{
- char buf[128];
- char* key;
- char* val;
- char* ctx;
- res_T res = RES_OK;
- ASSERT(args);
-
- if(strlen(str) >= sizeof(buf) -1/*NULL char*/) {
- fprintf(stderr,
- "Could not duplicate the rectangle option string `%s'.\n", str);
- res = RES_MEM_ERR;
- goto error;
- }
- strncpy(buf, str, sizeof(buf));
-
- /* pos=0,0,10.1; key <- pos, val <- 0,0,10 */
- key = strtok_r(buf, "=", &ctx);
- val = strtok_r(NULL, "", &ctx);
-
- if(!val) {
- fprintf(stderr, "Missing value to the rectangle option `%s'.\n", key);
- res = RES_BAD_ARG;
- goto error;
- }
-
- #define PARSE(Name, Func) { \
- if(RES_OK != (res = Func)) { \
- fprintf(stderr, "Invalid rectangle "Name" `%s'.\n", val); \
- goto error; \
- } \
- } (void)0
- if(!strcmp(key, "pos")) {
- PARSE("position", parse_doubleX(val, args->rectangle.pos, 3));
- } else if(!strcmp(key, "tgt")) {
- PARSE("target", parse_doubleX(val, args->rectangle.tgt, 3));
- } else if(!strcmp(key, "up")) {
- PARSE("up vector", parse_doubleX(val, args->rectangle.up, 3));
- } else if(!strcmp(key, "sz")) {
- PARSE("size", parse_doubleX(val, args->rectangle.sz, 2));
- } else {
- fprintf(stderr, "Invalid rectangle parameter `%s'.\n", key);
- res = RES_BAD_ARG;
- goto error;
- }
- #undef PARSE
-exit:
- return res;
-error:
- goto exit;
-}
-
-
-
-static res_T
-parse_spectral_range(const char* str, double wlen_range[2])
-{
- double range[2];
- size_t len;
- res_T res = RES_OK;
- ASSERT(wlen_range && str);
-
- res = cstr_to_list_double(str, ',', range, &len, 2);
- if(res == RES_OK && len != 2) res = RES_BAD_ARG;
- if(res == RES_OK && range[0] > range[1]) res = RES_BAD_ARG;
- if(res != RES_OK) {
- fprintf(stderr, "Invalid spectral range `%s'.\n", str);
- goto error;
- }
- wlen_range[0] = range[0];
- wlen_range[1] = range[1];
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-static res_T
-parse_spectral_parameter(struct htrdr_args* args, const char* str)
-{
- char buf[128];
- char* key;
- char* val;
- char* ctx;
- res_T res = RES_OK;
- ASSERT(args);
-
- if(strlen(str) >= sizeof(buf) -1/*NULL char*/) {
- fprintf(stderr,
- "Could not duplicate the spectral option string `%s'.\n", str);
- res = RES_MEM_ERR;
- goto error;
- }
- strncpy(buf, str, sizeof(buf));
-
- key = strtok_r(buf, "=", &ctx);
- val = strtok_r(NULL, "", &ctx);
-
- if(!strcmp(key, "cie_xyz")) {
- args->spectral_type = HTRDR_SPECTRAL_SW_CIE_XYZ;
- args->wlen_range[0] = HTRDR_CIE_XYZ_RANGE_DEFAULT[0];
- args->wlen_range[1] = HTRDR_CIE_XYZ_RANGE_DEFAULT[1];
- } else {
- if(!val) {
- fprintf(stderr, "Missing value to the spectral option `%s'.\n", key);
- res = RES_BAD_ARG;
- goto error;
- }
- if(!strcmp(key, "sw")) {
- args->spectral_type = HTRDR_SPECTRAL_SW;
- res = parse_spectral_range(val, args->wlen_range);
- if(res != RES_OK) goto error;
- } else if(!strcmp(key, "lw")) {
- args->spectral_type = HTRDR_SPECTRAL_LW;
- res = parse_spectral_range(val, args->wlen_range);
- if(res != RES_OK) goto error;
- } else if(!strcmp(key, "Tref")) {
- res = cstr_to_double(val, &args->ref_temperature);
- if(res == RES_OK && args->ref_temperature < 0) res = RES_BAD_ARG;
- if(res != RES_OK) {
- fprintf(stderr, "Invalid reference temperature Tref=%s.\n", val);
- goto error;
- }
- } else {
- fprintf(stderr, "Invalid spectral parameter `%s'.\n", key);
- res = RES_BAD_ARG;
- goto error;
- }
- }
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-static res_T
-parse_multiple_parameters
- (struct htrdr_args* args,
- const char* str,
- res_T (*parse_parameter)(struct htrdr_args* args, const char* str))
-{
- char buf[512];
- char* tk;
- char* ctx;
- res_T res = RES_OK;
- ASSERT(args && str);
-
- if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) {
- fprintf(stderr, "Could not duplicate the option string `%s'.\n", str);
- res = RES_MEM_ERR;
- goto error;
- }
- strncpy(buf, str, sizeof(buf));
-
- tk = strtok_r(buf, ":", &ctx);
- do {
- res = parse_parameter(args, tk);
- if(res != RES_OK) goto error;
- tk = strtok_r(NULL, ":", &ctx);
- } while(tk);
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-static res_T
-parse_sun_dir(struct htrdr_args* args, const char* str)
-{
- double angles[2];
- size_t len;
- res_T res = RES_OK;
- ASSERT(args && str);
-
- res = cstr_to_list_double(str, ',', angles, &len, 2);
- if(res == RES_OK && len != 2) res = RES_BAD_ARG;
- if(res != RES_OK) {
- fprintf(stderr, "Invalid direction `%s'.\n", str);
- goto error;
- }
-
- if(angles[0] < 0 || angles[0] >= 360) {
- fprintf(stderr,
- "Invalid azimuth angle `%g'. Azimuth must be in [0, 360[ degrees.\n",
- angles[0]);
- res = RES_BAD_ARG;
- goto error;
- }
-
- if(angles[1] < 0 || angles[1] > 90) {
- fprintf(stderr,
- "Invalid elevation angle `%g'. Elevation must be in [0, 90] degrees.\n",
- angles[1]);
- res = RES_BAD_ARG;
- goto error;
- }
-
- args->sun_azimuth = angles[0];
- args->sun_elevation = angles[1];
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-static res_T
-parse_grid_definition(struct htrdr_args* args, const char* str)
-{
- unsigned def[3];
- size_t len;
- res_T res = RES_OK;
- ASSERT(args && str);
-
- res = cstr_to_list_uint(str, ',', def, &len, 3);
- if(res == RES_OK && len != 3) res = RES_BAD_ARG;
- if(res != RES_OK) {
- fprintf(stderr, "Invalid grid definition `%s'.\n", str);
- goto error;
- }
-
- if(!def[0] || !def[1] || !def[2]) {
- fprintf(stderr,
- "Invalid null grid definition {%u, %u, %u}.\n", SPLIT3(def));
- res = RES_BAD_ARG;
- goto error;
- }
-
- args->grid_max_definition[0] = def[0];
- args->grid_max_definition[1] = def[1];
- args->grid_max_definition[2] = def[2];
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_args_init(struct htrdr_args* args, int argc, char** argv)
-{
- int opt;
- int i;
- res_T res = RES_OK;
- ASSERT(args && argc && argv);
-
- *args = HTRDR_ARGS_DEFAULT;
-
- FOR_EACH(i, 1, argc) {
- if(!strcmp(argv[i], "--version")) {
- printf("High-Tune: RenDeRer %d.%d.%d\n",
- HTRDR_VERSION_MAJOR,
- HTRDR_VERSION_MINOR,
- HTRDR_VERSION_PATCH);
- args->quit = 1;
- goto exit;
- }
- }
-
- while((opt = getopt(argc, argv, "a:C:c:D:dfg:hi:M:m:n:O:o:p:Rrs:T:t:V:v")) != -1) {
- switch(opt) {
- case 'a': args->filename_gas = optarg; break;
- case 'C':
- args->sensor_type = HTRDR_SENSOR_CAMERA;
- res = parse_multiple_parameters
- (args, optarg, parse_camera_parameter);
- break;
- case 'c': args->filename_les = optarg; break;
- case 'D': res = parse_sun_dir(args, optarg); break;
- case 'd': args->dump_vtk = 1; break;
- case 'f': args->force_overwriting = 1; break;
- case 'g': args->filename_obj = optarg; break;
- case 'h':
- print_help(argv[0]);
- htrdr_args_release(args);
- args->quit = 1;
- goto exit;
- case 'i':
- res = parse_multiple_parameters
- (args, optarg, parse_image_parameter);
- break;
- case 'M': args->filename_mtl = optarg; break;
- case 'm': args->filename_mie = optarg; break;
- case 'n': args->sky_mtl_name = optarg; break;
- case 'O': args->cache = optarg; break;
- case 'o': args->output = optarg; break;
- case 'p':
- args->sensor_type = HTRDR_SENSOR_RECTANGLE;
- res = parse_multiple_parameters
- (args, optarg, parse_rectangle_parameter);
- break;
- case 'r': args->repeat_clouds = 1; break;
- case 'R': args->repeat_ground = 1; break;
- case 's':
- res = parse_multiple_parameters
- (args, optarg, parse_spectral_parameter);
- break;
- case 'T':
- res = cstr_to_double(optarg, &args->optical_thickness);
- if(res == RES_OK && args->optical_thickness < 0) res = RES_BAD_ARG;
- break;
- case 't': /* Submit an hint on the number of threads to use */
- res = cstr_to_uint(optarg, &args->nthreads);
- if(res == RES_OK && !args->nthreads) res = RES_BAD_ARG;
- break;
- case 'V': res = parse_grid_definition(args, optarg); break;
- case 'v': args->verbose = 1; break;
- default: res = RES_BAD_ARG; break;
- }
- if(res != RES_OK) {
- if(optarg) {
- fprintf(stderr, "%s: invalid option argument '%s' -- '%c'\n",
- argv[0], optarg, opt);
- }
- goto error;
- }
- }
- if(!args->filename_gas) {
- fprintf(stderr,
- "Missing the path of the gas optical properties file -- option '-a'\n");
- res = RES_BAD_ARG;
- goto error;
- }
- if(args->filename_obj && !args->filename_mtl) {
- fprintf(stderr,
- "Missing the path of the file listing the ground materials -- option '-M'\n");
- res = RES_BAD_ARG;
- goto error;
- }
- if(args->filename_les && !args->filename_mie) {
- fprintf(stderr,
- "Missing the path toward the file of the Mie's data -- option '-m'\n");
- res = RES_BAD_ARG;
- goto error;
- }
-
- /* Setup default ref temperature if necessary */
- if(args->ref_temperature <= 0) {
- switch(args->spectral_type) {
- case HTRDR_SPECTRAL_LW:
- args->ref_temperature = HTRDR_DEFAULT_LW_REF_TEMPERATURE;
- break;
- case HTRDR_SPECTRAL_SW:
- args->ref_temperature = HTRDR_SUN_TEMPERATURE;
- break;
- case HTRDR_SPECTRAL_SW_CIE_XYZ:
- args->ref_temperature = -1; /* Unused */
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- }
-
-exit:
- return res;
-error:
- htrdr_args_release(args);
- goto exit;
-}
-
-void
-htrdr_args_release(struct htrdr_args* args)
-{
- ASSERT(args);
- *args = HTRDR_ARGS_DEFAULT;
-}
-
diff --git a/src/htrdr_args.h.in b/src/htrdr_args.h.in
@@ -1,127 +0,0 @@
-/* Copyright (C) 2018-2019 CNRS, Université Paul Sabatier, |Meso|Star>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_ARGS_H
-#define HTRDR_ARGS_H
-
-#include "htrdr_sensor.h"
-#include "htrdr_spectral.h"
-#include "htrdr_cie_xyz.h"
-
-#include <float.h>
-#include <limits.h>
-#include <rsys/rsys.h>
-
-struct htrdr_args {
- const char* filename_gas; /* Path of the gas file */
- const char* filename_les; /* Path of the HTCP file */
- const char* filename_mie; /* Path of the Mie properties */
- const char* filename_obj; /* Path of the 3D geometry */
- const char* filename_mtl; /* Path of the materials */
- const char* cache;
- const char* output;
- const char* sky_mtl_name;
-
- struct {
- double pos[3]; /* Center of the renctangle */
- double tgt[3]; /* Target */
- double up[3]; /* Up vector */
- double sz[2]; /* Plane size in world space */
- } rectangle;
-
- struct {
- double pos[3];
- double tgt[3];
- double up[3];
- double fov_y; /* In degrees */
- } camera;
-
- struct {
- unsigned definition[2]; /* #pixels in X and Y */
- unsigned spp; /* #samples per pixel */
- } image;
-
- double sun_azimuth; /* In degrees */
- double sun_elevation; /* In degrees */
- double optical_thickness; /* Threshold used during octree building */
- unsigned grid_max_definition[3]; /* Maximum definition of the grid */
-
- enum htrdr_spectral_type spectral_type;
- double wlen_range[2]; /* Spectral range of integration in nm */
- double ref_temperature; /* Planck reference temperature in Kelvin */
-
- enum htrdr_sensor_type sensor_type;
- unsigned nthreads; /* Hint on the number of threads to use */
- int force_overwriting;
- int dump_vtk; /* Dump the loaded cloud properties in a VTK file */
- int verbose; /* Verbosity level */
- int repeat_clouds; /* Make the clouds infinite in X and Y */
- int repeat_ground; /* Make the ground infinite in X and Y */
- int quit; /* Quit the application */
-};
-
-#define HTRDR_ARGS_DEFAULT__ { \
- NULL, /* Gas filename */ \
- NULL, /* LES filename */ \
- NULL, /* Mie filename */ \
- NULL, /* Obj filename */ \
- NULL, /* Mtl filename */ \
- NULL, /* Cache filename */ \
- NULL, /* Output filename */ \
- @HTRDR_ARGS_DEFAULT_SKY_MTL_NAME@, /* Sky mtl name */ \
- { \
- {@HTRDR_ARGS_DEFAULT_RECTANGLE_POS@}, /* Rectangle center */ \
- {@HTRDR_ARGS_DEFAULT_RECTANGLE_TGT@}, /* Rectangle target */ \
- {@HTRDR_ARGS_DEFAULT_RECTANGLE_UP@}, /* Rectangle up */ \
- {@HTRDR_ARGS_DEFAULT_RECTANGLE_SZ@}, /* Rectangle size */ \
- }, { \
- {@HTRDR_ARGS_DEFAULT_CAMERA_POS@}, /* Camera position */ \
- {@HTRDR_ARGS_DEFAULT_CAMERA_TGT@}, /* Camera target */ \
- {@HTRDR_ARGS_DEFAULT_CAMERA_UP@}, /* Camera up */ \
- @HTRDR_ARGS_DEFAULT_CAMERA_FOV@, /* Horizontal field of view */ \
- }, { \
- {@HTRDR_ARGS_DEFAULT_IMG_WIDTH@, @HTRDR_ARGS_DEFAULT_IMG_HEIGHT@}, \
- @HTRDR_ARGS_DEFAULT_IMG_SPP@ \
- }, \
- 0, /* Sun azimuth */ \
- 90, /* Sun elevation */ \
- @HTRDR_ARGS_DEFAULT_OPTICAL_THICKNESS_THRESHOLD@, /* Optical thickness */ \
- {UINT_MAX, UINT_MAX, UINT_MAX}, /* Maximum definition of the grid */ \
- HTRDR_SPECTRAL_SW_CIE_XYZ, /* Spectral type */ \
- HTRDR_CIE_XYZ_RANGE_DEFAULT__, /* Spectral range */ \
- -1, /* Reference temperature */ \
- HTRDR_SENSOR_CAMERA, /* sensor type */ \
- (unsigned)~0, /* #threads */ \
- 0, /* Force overwriting */ \
- 0, /* dump VTK */ \
- 0, /* Verbose flag */ \
- 0, /* Repeat clouds */ \
- 0, /* Repeat ground */ \
- 0 /* Quit the application */ \
-}
-static const struct htrdr_args HTRDR_ARGS_DEFAULT = HTRDR_ARGS_DEFAULT__;
-
-extern LOCAL_SYM res_T
-htrdr_args_init
- (struct htrdr_args* args,
- int argc,
- char** argv);
-
-extern LOCAL_SYM void
-htrdr_args_release
- (struct htrdr_args* args);
-
-#endif /* HTRDR_ARGS_H */
-
diff --git a/src/htrdr_buffer.c b/src/htrdr_buffer.c
@@ -1,167 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htrdr.h"
-#include "htrdr_buffer.h"
-
-#include <rsys/mem_allocator.h>
-#include <rsys/ref_count.h>
-
-struct htrdr_buffer {
- char* mem;
-
- size_t width;
- size_t height;
- size_t pitch;
- size_t elmtsz;
- size_t align;
-
- struct htrdr* htrdr;
- ref_T ref;
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static void
-buffer_release(ref_T* ref)
-{
- struct htrdr_buffer* buf = NULL;
- ASSERT(ref);
- buf = CONTAINER_OF(ref, struct htrdr_buffer, ref);
- if(buf->mem) MEM_RM(buf->htrdr->allocator, buf->mem);
- MEM_RM(buf->htrdr->allocator, buf);
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_buffer_create
- (struct htrdr* htrdr,
- const size_t width,
- const size_t height,
- const size_t pitch,
- const size_t elmtsz,
- const size_t align,
- struct htrdr_buffer** out_buf)
-{
- struct htrdr_buffer* buf = NULL;
- size_t memsz = 0;
- res_T res = RES_OK;
- ASSERT(htrdr && out_buf);
-
- if(!width || !height) {
- htrdr_log_err(htrdr, "invalid buffer definition %lux%lu.\n",
- (unsigned long)width, (unsigned long)height);
- res = RES_BAD_ARG;
- goto error;
- }
- if(pitch < width*elmtsz) {
- htrdr_log_err(htrdr,
- "invalid buffer pitch `%lu' wrt the buffer width `%lu'. "
- "The buffer pitch cannot be less than the buffer width.\n",
- (unsigned long)pitch, (unsigned long)width);
- res = RES_BAD_ARG;
- goto error;
- }
- if(!elmtsz) {
- htrdr_log_err(htrdr,
- "the size of the buffer's elements cannot be null.\n");
- res = RES_BAD_ARG;
- goto error;
- }
- if(!IS_POW2(align)) {
- htrdr_log_err(htrdr,
- "invalid buffer alignment `%lu'. It must be a power of 2.\n",
- (unsigned long)align);
- res = RES_BAD_ARG;
- goto error;
- }
-
- buf = MEM_CALLOC(htrdr->allocator, 1, sizeof(*buf));
- if(!buf) {
- res = RES_MEM_ERR;
- goto error;
- }
- ref_init(&buf->ref);
- buf->htrdr = htrdr;
- buf->width = width;
- buf->height = height;
- buf->pitch = pitch;
- buf->elmtsz = elmtsz;
- buf->align = align;
- buf->htrdr = htrdr;
-
- memsz = buf->pitch * buf->height;
- buf->mem = MEM_ALLOC_ALIGNED(htrdr->allocator, memsz, align);
- if(!buf->mem) {
- res = RES_MEM_ERR;
- goto error;
- }
-
-exit:
- *out_buf = buf;
- return res;
-error:
- if(buf) {
- htrdr_buffer_ref_put(buf);
- buf = NULL;
- }
- goto exit;
-}
-
-void
-htrdr_buffer_ref_get(struct htrdr_buffer* buf)
-{
- ASSERT(buf);
- ref_get(&buf->ref);
-}
-
-void
-htrdr_buffer_ref_put(struct htrdr_buffer* buf)
-{
- ASSERT(buf);
- ref_put(&buf->ref, buffer_release);
-}
-
-void
-htrdr_buffer_get_layout
- (const struct htrdr_buffer* buf,
- struct htrdr_buffer_layout* layout)
-{
- ASSERT(buf && layout);
- layout->width = buf->width;
- layout->height = buf->height;
- layout->pitch = buf->pitch;
- layout->elmt_size = buf->elmtsz;
- layout->alignment = buf->align;
-}
-
-void*
-htrdr_buffer_get_data(struct htrdr_buffer* buf)
-{
- ASSERT(buf);
- return buf->mem;
-}
-
-void*
-htrdr_buffer_at(struct htrdr_buffer* buf, const size_t x, const size_t y)
-{
- ASSERT(buf && x < buf->width && y < buf->height);
- return buf->mem + y*buf->pitch + x*buf->elmtsz;
-}
-
diff --git a/src/htrdr_buffer.h b/src/htrdr_buffer.h
@@ -1,74 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_BUFFER_H
-#define HTRDR_BUFFER_H
-
-#include <rsys/rsys.h>
-
-/*
- * Row major ordered 2D buffer
- */
-
-struct htrdr_buffer_layout {
- size_t width; /* #elements in X */
- size_t height; /* #elements in Y */
- size_t pitch; /* #Bytes between 2 consecutive line */
- size_t elmt_size; /* Size of an element in the buffer */
- size_t alignment; /* Alignement of the memory */
-};
-#define HTRDR_BUFFER_LAYOUT_NULL__ {0,0,0,0,0}
-static const struct htrdr_buffer_layout HTRDR_BUFFER_LAYOUT_NULL =
- HTRDR_BUFFER_LAYOUT_NULL__;
-
-/* Forward declarations */
-struct htrdr;
-struct htrdr_buffer;
-
-extern LOCAL_SYM res_T
-htrdr_buffer_create
- (struct htrdr* htrdr,
- const size_t width,
- const size_t height,
- const size_t pitch, /* #Bytes between 2 consecutive line */
- const size_t elmt_size, /* Size of an element in the buffer */
- const size_t alignment, /* Alignement of the buffer */
- struct htrdr_buffer** buf);
-
-extern LOCAL_SYM void
-htrdr_buffer_ref_get
- (struct htrdr_buffer* buf);
-
-extern LOCAL_SYM void
-htrdr_buffer_ref_put
- (struct htrdr_buffer* buf);
-
-extern LOCAL_SYM void
-htrdr_buffer_get_layout
- (const struct htrdr_buffer* buf,
- struct htrdr_buffer_layout* layout);
-
-extern LOCAL_SYM void*
-htrdr_buffer_get_data
- (struct htrdr_buffer* buf);
-
-extern LOCAL_SYM void*
-htrdr_buffer_at
- (struct htrdr_buffer* buf,
- const size_t x,
- const size_t y);
-
-#endif /* HTRDR_BUFFER_H */
diff --git a/src/htrdr_c.h b/src/htrdr_c.h
@@ -1,154 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_C_H
-#define HTRDR_C_H
-
-#include <rsys/rsys.h>
-
-#ifndef NDEBUG
- #define MPI(Func) ASSERT(MPI_##Func == MPI_SUCCESS)
-#else
- #define MPI(Func) MPI_##Func
-#endif
-
-enum htrdr_mpi_message {
- HTRDR_MPI_PROGRESS_BUILD_OCTREE,
- HTRDR_MPI_PROGRESS_RENDERING,
- HTRDR_MPI_STEAL_REQUEST,
- HTRDR_MPI_WORK_STEALING,
- HTRDR_MPI_TILE_DATA
-};
-
-struct htrdr;
-
-/* In nanometer */
-static FINLINE double
-wavenumber_to_wavelength(const double nu/*In cm^-1*/)
-{
- return 1.e7 / nu;
-}
-
-/* In cm^-1 */
-static FINLINE double
-wavelength_to_wavenumber(const double lambda/*In nanometer*/)
-{
- return wavenumber_to_wavelength(lambda);
-}
-
-static INLINE uint64_t
-morton3D_encode_u21(const uint32_t u21)
-{
- uint64_t u64 = u21 & ((1<<21) - 1);
- ASSERT(u21 <= ((1 << 21) - 1));
- u64 = (u64 | (u64 << 32)) & 0xFFFF00000000FFFF;
- u64 = (u64 | (u64 << 16)) & 0x00FF0000FF0000FF;
- u64 = (u64 | (u64 << 8)) & 0xF00F00F00F00F00F;
- u64 = (u64 | (u64 << 4)) & 0x30C30C30C30C30C3;
- u64 = (u64 | (u64 << 2)) & 0x9249249249249249;
- return u64;
-}
-
-static INLINE uint32_t
-morton3D_decode_u21(const uint64_t u64)
-{
- uint64_t tmp = (u64 & 0x9249249249249249);
- tmp = (tmp | (tmp >> 2)) & 0x30C30C30C30C30C3;
- tmp = (tmp | (tmp >> 4)) & 0xF00F00F00F00F00F;
- tmp = (tmp | (tmp >> 8)) & 0x00FF0000FF0000FF;
- tmp = (tmp | (tmp >> 16)) & 0xFFFF00000000FFFF;
- tmp = (tmp | (tmp >> 32)) & 0x00000000FFFFFFFF;
- ASSERT(tmp <= ((1<<21)-1));
- return (uint32_t)tmp;
-}
-
-static INLINE uint64_t
-morton_xyz_encode_u21(const uint32_t xyz[3])
-{
- return (morton3D_encode_u21(xyz[0]) << 2)
- | (morton3D_encode_u21(xyz[1]) << 1)
- | (morton3D_encode_u21(xyz[2]) << 0);
-}
-
-static INLINE void
-morton_xyz_decode_u21(const uint64_t code, uint32_t xyz[3])
-{
- ASSERT(xyz && code < ((1ull << 63)-1));
- xyz[0] = (uint32_t)morton3D_decode_u21(code >> 2);
- xyz[1] = (uint32_t)morton3D_decode_u21(code >> 1);
- xyz[2] = (uint32_t)morton3D_decode_u21(code >> 0);
-}
-
-/* Return the minimum length in nanometer of the sky spectral bands
- * clamped to in [range[0], range[1]]. */
-extern LOCAL_SYM double
-compute_sky_min_band_len
- (struct htsky* sky,
- const double range[2]);
-
-extern LOCAL_SYM res_T
-open_output_stream
- (struct htrdr* htrdr,
- const char* filename,
- const int read, /* Enable read access */
- int force_overwrite,
- FILE** out_fp);
-
-extern LOCAL_SYM void
-send_mpi_progress
- (struct htrdr* htrdr,
- const enum htrdr_mpi_message progress,
- const int32_t percent);
-
-extern LOCAL_SYM void
-fetch_mpi_progress
- (struct htrdr* htrdr,
- const enum htrdr_mpi_message progress);
-
-extern LOCAL_SYM void
-print_mpi_progress
- (struct htrdr* htrdr,
- const enum htrdr_mpi_message progress);
-
-extern LOCAL_SYM void
-clear_mpi_progress
- (struct htrdr* htrdr,
- const enum htrdr_mpi_message progress);
-
-extern int32_t
-total_mpi_progress
- (const struct htrdr* htrdr,
- const enum htrdr_mpi_message progress);
-
-static INLINE void
-update_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message progress)
-{
- ASSERT(htrdr);
- fetch_mpi_progress(htrdr, progress);
- clear_mpi_progress(htrdr, progress);
- print_mpi_progress(htrdr, progress);
-}
-
-static FINLINE int
-cmp_dbl(const void* a, const void* b)
-{
- const double d0 = *((const double*)a);
- const double d1 = *((const double*)b);
- return d0 < d1 ? -1 : (d0 > d1 ? 1 : 0);
-}
-
-#endif /* HTRDR_C_H */
-
diff --git a/src/htrdr_camera.c b/src/htrdr_camera.c
@@ -1,151 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htrdr.h"
-#include "htrdr_camera.h"
-
-#include <rsys/double3.h>
-#include <rsys/mem_allocator.h>
-#include <rsys/ref_count.h>
-
-struct htrdr_camera {
- /* Orthogonal basis of the camera */
- double axis_x[3];
- double axis_y[3];
- double axis_z[3];
-
- double position[3];
-
- ref_T ref;
- struct htrdr* htrdr;
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static void
-camera_release(ref_T* ref)
-{
- struct htrdr_camera* cam;
- ASSERT(ref);
- cam = CONTAINER_OF(ref, struct htrdr_camera, ref);
- MEM_RM(cam->htrdr->allocator, cam);
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_camera_create
- (struct htrdr* htrdr,
- const double position[3],
- const double target[3],
- const double up[3],
- const double proj_ratio,
- const double fov, /* In radian */
- struct htrdr_camera** out_cam)
-{
- double x[3], y[3], z[3];
- double img_plane_depth;
- struct htrdr_camera* cam = NULL;
- res_T res = RES_OK;
- ASSERT(htrdr && position && target && up && out_cam);
-
- cam = MEM_CALLOC(htrdr->allocator, 1, sizeof(*cam));
- if(!cam) {
- htrdr_log_err(htrdr, "could not allocate the camera data structure.\n");
- res = RES_MEM_ERR;
- goto error;
- }
- ref_init(&cam->ref);
- cam->htrdr = htrdr;
-
- if(fov <= 0 || fov >= PI) {
- htrdr_log_err(htrdr, "invalid horizontal camera field of view `%g'\n", fov);
- res = RES_BAD_ARG;
- goto error;
- }
-
- if(proj_ratio <= 0) {
- htrdr_log_err(htrdr, "invalid projection ratio `%g'\n", proj_ratio);
- res = RES_BAD_ARG;
- goto error;
- }
-
- if(d3_normalize(z, d3_sub(z, target, position)) <= 0
- || d3_normalize(x, d3_cross(x, z, up)) <= 0
- || d3_normalize(y, d3_cross(y, z, x)) <= 0) {
- htrdr_log_err(htrdr,
- "invalid camera point of view:\n"
- " position = %g %g %g\n"
- " target = %g %g %g\n"
- " up = %g %g %g\n",
- SPLIT3(position), SPLIT3(target), SPLIT3(up));
- res = RES_BAD_ARG;
- goto error;
- }
-
- img_plane_depth = 1.0/tan(fov*0.5);
- d3_muld(cam->axis_x, x, proj_ratio);
- d3_set(cam->axis_y, y);
- d3_muld(cam->axis_z, z, img_plane_depth);
- d3_set(cam->position, position);
-
-exit:
- *out_cam = cam;
- return res;
-error:
- if(cam) {
- htrdr_camera_ref_put(cam);
- cam = NULL;
- }
- goto exit;
-}
-
-void
-htrdr_camera_ref_get(struct htrdr_camera* cam)
-{
- ASSERT(cam);
- ref_get(&cam->ref);
-}
-
-void
-htrdr_camera_ref_put(struct htrdr_camera* cam)
-{
- ASSERT(cam);
- ref_put(&cam->ref, camera_release);
-}
-
-void
-htrdr_camera_ray
- (const struct htrdr_camera* cam,
- const double sample[2],
- double ray_org[3],
- double ray_dir[3])
-{
- double x[3], y[3], len;
- (void)len;
- ASSERT(cam && sample && ray_org && ray_dir);
- ASSERT(sample[0] >= 0 || sample[0] < 1);
- ASSERT(sample[1] >= 0 || sample[1] < 1);
- d3_muld(x, cam->axis_x, sample[0]*2-1);
- d3_muld(y, cam->axis_y, sample[1]*2-1);
- d3_add(ray_dir, d3_add(ray_dir, x, y), cam->axis_z);
- len = d3_normalize(ray_dir, ray_dir);
- ASSERT(len >= 1.e-6);
- d3_set(ray_org, cam->position);
-}
-
diff --git a/src/htrdr_camera.h b/src/htrdr_camera.h
@@ -1,52 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_CAMERA_H
-#define HTRDR_CAMERA_H
-
-#include <rsys/rsys.h>
-
-/* Forward declarations */
-struct htrdr;
-struct htrdr_camera;
-
-extern LOCAL_SYM res_T
-htrdr_camera_create
- (struct htrdr* htrdr,
- const double position[3],
- const double target[3],
- const double up[3],
- const double proj_ratio, /* Width / Height */
- const double fov, /* In radian */
- struct htrdr_camera** cam);
-
-extern LOCAL_SYM void
-htrdr_camera_ref_get
- (struct htrdr_camera* cam);
-
-extern LOCAL_SYM void
-htrdr_camera_ref_put
- (struct htrdr_camera* cam);
-
-extern LOCAL_SYM void
-htrdr_camera_ray
- (const struct htrdr_camera* cam,
- const double sample[2], /* In [0, 1[ */
- double ray_org[3],
- double ray_dir[3]);
-
-#endif /* HTRDR_CAMERA_H */
-
diff --git a/src/htrdr_cie_xyz.c b/src/htrdr_cie_xyz.c
@@ -1,396 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#define _POSIX_C_SOURCE 200112L /* nextafter */
-
-#include "htrdr.h"
-#include "htrdr_c.h"
-#include "htrdr_cie_xyz.h"
-
-#include <high_tune/htsky.h>
-
-#include <rsys/algorithm.h>
-#include <rsys/cstr.h>
-#include <rsys/dynamic_array_double.h>
-#include <rsys/mem_allocator.h>
-#include <rsys/ref_count.h>
-
-#include <math.h> /* nextafter */
-
-struct htrdr_cie_xyz {
- struct darray_double cdf_X;
- struct darray_double cdf_Y;
- struct darray_double cdf_Z;
- double rcp_integral_X;
- double rcp_integral_Y;
- double rcp_integral_Z;
- double range[2]; /* Boundaries of the handled CIE XYZ color space */
- double band_len; /* Length in nanometers of a band */
-
- struct htrdr* htrdr;
- ref_T ref;
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static INLINE double
-trapezoidal_integration
- (const double lambda_lo, /* Integral lower bound. In nanometer */
- const double lambda_hi, /* Integral upper bound. In nanometer */
- double (*f_bar)(const double lambda)) /* Function to integrate */
-{
- double dlambda;
- size_t i, n;
- double integral = 0;
- ASSERT(lambda_lo <= lambda_hi);
- ASSERT(lambda_lo > 0);
-
- n = (size_t)(lambda_hi - lambda_lo) + 1;
- dlambda = (lambda_hi - lambda_lo) / (double)n;
-
- FOR_EACH(i, 0, n) {
- const double lambda1 = lambda_lo + dlambda*(double)(i+0);
- const double lambda2 = lambda_lo + dlambda*(double)(i+1);
- const double f1 = f_bar(lambda1);
- const double f2 = f_bar(lambda2);
- integral += (f1 + f2)*dlambda*0.5;
- }
- return integral;
-}
-
-/* The following 3 functions are used to fit the CIE Xbar, Ybar and Zbar curved
- * has defined by the 1931 standard. These analytical fits are propsed by C.
- * Wyman, P. P. Sloan & P. Shirley in "Simple Analytic Approximations to the
- * CIE XYZ Color Matching Functions" - JCGT 2013. */
-static INLINE double
-fit_x_bar_1931(const double lambda)
-{
- const double a = (lambda - 442.0) * (lambda < 442.0 ? 0.0624 : 0.0374);
- const double b = (lambda - 599.8) * (lambda < 599.8 ? 0.0264 : 0.0323);
- const double c = (lambda - 501.1) * (lambda < 501.1 ? 0.0490 : 0.0382);
- return 0.362*exp(-0.5*a*a) + 1.056*exp(-0.5f*b*b) - 0.065*exp(-0.5*c*c);
-}
-
-static FINLINE double
-fit_y_bar_1931(const double lambda)
-{
- const double a = (lambda - 568.8) * (lambda < 568.8 ? 0.0213 : 0.0247);
- const double b = (lambda - 530.9) * (lambda < 530.9 ? 0.0613 : 0.0322);
- return 0.821*exp(-0.5*a*a) + 0.286*exp(-0.5*b*b);
-}
-
-static FINLINE double
-fit_z_bar_1931(const double lambda)
-{
- const double a = (lambda - 437.0) * (lambda < 437.0 ? 0.0845 : 0.0278);
- const double b = (lambda - 459.0) * (lambda < 459.0 ? 0.0385 : 0.0725);
- return 1.217*exp(-0.5*a*a) + 0.681*exp(-0.5*b*b);
-}
-
-static INLINE double
-sample_cie_xyz
- (const struct htrdr_cie_xyz* cie,
- const double* cdf,
- const size_t cdf_length,
- double (*f_bar)(const double lambda), /* Function to integrate */
- const double r0, /* Canonical number in [0, 1[ */
- const double r1) /* Canonical number in [0, 1[ */
-{
- double r0_next = nextafter(r0, DBL_MAX);
- double* find;
- double f_min, f_max; /* CIE 1931 value for the band boundaries */
- double lambda; /* Sampled wavelength */
- double lambda_min, lambda_max; /* Boundaries of the sampled band */
- double lambda_1, lambda_2; /* Solutions if the equation to solve */
- double a, b, c, d; /* Equation parameters */
- double delta, sqrt_delta;
- size_t iband; /* Index of the sampled band */
- ASSERT(cie && cdf && cdf_length);
- ASSERT(0 <= r0 && r0 < 1);
- ASSERT(0 <= r1 && r1 < 1);
-
- /* Use r_next rather than r in order to find the first entry that is not less
- * than *or equal* to r */
- find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl);
- ASSERT(find);
-
- /* Define and check the sampled band */
- iband = (size_t)(find - cdf);
- ASSERT(iband < cdf_length);
- ASSERT(cdf[iband] > r0 && (!iband || cdf[iband-1] <= r0));
-
- /* Define the boundaries of the sampled band */
- lambda_min = cie->range[0] + cie->band_len * (double)iband;
- lambda_max = lambda_min + cie->band_len;
-
- /* Define the value of the CIE 1931 function for the boudaries of the sampled
- * band */
- f_min = f_bar(lambda_min);
- f_max = f_bar(lambda_max);
-
- /* Compute the equation constants */
- a = 0.5 * (f_max - f_min) / cie->band_len;
- b = (lambda_max * f_min - lambda_min * f_max) / cie->band_len;
- c = -lambda_min * f_min + lambda_min*lambda_min * a;
- d = 0.5 * (f_max + f_min) * cie->band_len;
-
- delta = b*b - 4*a*(c-d*r1);
- if(delta < 0 && eq_eps(delta, 0, 1.e-6)) {
- delta = 0;
- }
- ASSERT(delta > 0);
- sqrt_delta = sqrt(delta);
-
- /* Compute the roots that solve the equation */
- lambda_1 = (-b - sqrt_delta) / (2*a);
- lambda_2 = (-b + sqrt_delta) / (2*a);
-
- /* Select the solution */
- if(lambda_min <= lambda_1 && lambda_1 < lambda_max) {
- lambda = lambda_1;
- } else if(lambda_min <= lambda_2 && lambda_2 < lambda_max) {
- lambda = lambda_2;
- } else {
- htrdr_log_warn(cie->htrdr,
- "%s: cannot sample a wavelength in [%g, %g[. The possible wavelengths"
- "were %g and %g.\n",
- FUNC_NAME, lambda_min, lambda_max, lambda_1, lambda_2);
- /* Arbitrarly choose the wavelength at the center of the sampled band */
- lambda = (lambda_min + lambda_max)*0.5;
- }
-
- return lambda;
-}
-
-static res_T
-setup_cie_xyz
- (struct htrdr_cie_xyz* cie,
- const char* func_name,
- const size_t nbands)
-{
- enum { X, Y, Z }; /* Helper constant */
- double* pdf[3] = {NULL, NULL, NULL};
- double* cdf[3] = {NULL, NULL, NULL};
- double sum[3] = {0, 0, 0};
- size_t i;
- res_T res = RES_OK;
-
- ASSERT(cie && func_name && nbands);
- ASSERT(cie->range[0] >= HTRDR_CIE_XYZ_RANGE_DEFAULT[0]);
- ASSERT(cie->range[1] <= HTRDR_CIE_XYZ_RANGE_DEFAULT[1]);
- ASSERT(cie->range[0] < cie->range[1]);
-
- /* Allocate and reset the memory space for the tristimulus CDF */
- #define SETUP_STIMULUS(Stimulus) { \
- res = darray_double_resize(&cie->cdf_ ## Stimulus, nbands); \
- if(res != RES_OK) { \
- htrdr_log_err(cie->htrdr, \
- "%s: Could not reserve the memory space for the CDF " \
- "of the "STR(X)" stimulus -- %s.\n", func_name, res_to_cstr(res)); \
- goto error; \
- } \
- cdf[Stimulus] = darray_double_data_get(&cie->cdf_ ## Stimulus); \
- pdf[Stimulus] = cdf[Stimulus]; \
- memset(cdf[Stimulus], 0, nbands*sizeof(double)); \
- } (void)0
- SETUP_STIMULUS(X);
- SETUP_STIMULUS(Y);
- SETUP_STIMULUS(Z);
- #undef SETUP_STIMULUS
-
- /* Compute the *unormalized* pdf of the tristimulus */
- FOR_EACH(i, 0, nbands) {
- const double lambda_lo = cie->range[0] + (double)i * cie->band_len;
- const double lambda_hi = MMIN(lambda_lo + cie->band_len, cie->range[1]);
- ASSERT(lambda_lo <= lambda_hi);
- ASSERT(lambda_lo >= cie->range[0]);
- ASSERT(lambda_hi <= cie->range[1]);
- pdf[X][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_x_bar_1931);
- pdf[Y][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_y_bar_1931);
- pdf[Z][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_z_bar_1931);
- sum[X] += pdf[X][i];
- sum[Y] += pdf[Y][i];
- sum[Z] += pdf[Z][i];
- }
- #define CHK_SUM(Sum, Range, Fit) \
- ASSERT(eq_eps(Sum, trapezoidal_integration(Range[0], Range[1], Fit), 1.e-3))
- CHK_SUM(sum[X], cie->range, fit_x_bar_1931);
- CHK_SUM(sum[Y], cie->range, fit_y_bar_1931);
- CHK_SUM(sum[Z], cie->range, fit_z_bar_1931);
- #undef CHK_SUM
- cie->rcp_integral_X = 1.0 / sum[X];
- cie->rcp_integral_Y = 1.0 / sum[Y];
- cie->rcp_integral_Z = 1.0 / sum[Z];
-
- FOR_EACH(i, 0, nbands) {
- /* Normalize the pdf */
- pdf[X][i] /= sum[X];
- pdf[Y][i] /= sum[Y];
- pdf[Z][i] /= sum[Z];
- /* Setup the cumulative */
- if(i == 0) {
- cdf[X][i] = pdf[X][i];
- cdf[Y][i] = pdf[Y][i];
- cdf[Z][i] = pdf[Z][i];
- } else {
- cdf[X][i] = pdf[X][i] + cdf[X][i-1];
- cdf[Y][i] = pdf[Y][i] + cdf[Y][i-1];
- cdf[Z][i] = pdf[Z][i] + cdf[Z][i-1];
- ASSERT(cdf[X][i] >= cdf[X][i-1]);
- ASSERT(cdf[Y][i] >= cdf[Y][i-1]);
- ASSERT(cdf[Z][i] >= cdf[Z][i-1]);
- }
- }
- ASSERT(eq_eps(cdf[X][nbands-1], 1, 1.e-6));
- ASSERT(eq_eps(cdf[Y][nbands-1], 1, 1.e-6));
- ASSERT(eq_eps(cdf[Z][nbands-1], 1, 1.e-6));
-
- /* Handle numerical issue */
- cdf[X][nbands-1] = 1.0;
- cdf[Y][nbands-1] = 1.0;
- cdf[Z][nbands-1] = 1.0;
-
-exit:
- return res;
-error:
- darray_double_clear(&cie->cdf_X);
- darray_double_clear(&cie->cdf_Y);
- darray_double_clear(&cie->cdf_Z);
- goto exit;
-}
-
-static void
-release_cie_xyz(ref_T* ref)
-{
- struct htrdr_cie_xyz* cie = NULL;
- ASSERT(ref);
- cie = CONTAINER_OF(ref, struct htrdr_cie_xyz, ref);
- darray_double_release(&cie->cdf_X);
- darray_double_release(&cie->cdf_Y);
- darray_double_release(&cie->cdf_Z);
- MEM_RM(cie->htrdr->allocator, cie);
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_cie_xyz_create
- (struct htrdr* htrdr,
- const double range[2], /* Must be included in [380, 780] nanometers */
- const size_t bands_count, /* # bands used to discretisze the CIE tristimulus */
- struct htrdr_cie_xyz** out_cie)
-{
- struct htrdr_cie_xyz* cie = NULL;
- double min_band_len = 0;
- size_t nbands = bands_count;
- res_T res = RES_OK;
- ASSERT(htrdr && range && nbands && out_cie);
-
- cie = MEM_CALLOC(htrdr->allocator, 1, sizeof(*cie));
- if(!cie) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "%s: could not allocate the CIE XYZ data structure -- %s.\n",
- FUNC_NAME, res_to_cstr(res));
- goto error;
- }
- ref_init(&cie->ref);
- cie->htrdr = htrdr;
- darray_double_init(htrdr->allocator, &cie->cdf_X);
- darray_double_init(htrdr->allocator, &cie->cdf_Y);
- darray_double_init(htrdr->allocator, &cie->cdf_Z);
- cie->range[0] = range[0];
- cie->range[1] = range[1];
-
- min_band_len = compute_sky_min_band_len(cie->htrdr->sky, range);
- cie->band_len = (range[1] - range[0]) / (double)nbands;
-
- /* Adjust the band length to ensure that each sky spectral interval is
- * overlapped by at least one band */
- if(cie->band_len > min_band_len) {
- cie->band_len = min_band_len;
- nbands = (size_t)ceil((range[1] - range[0]) / cie->band_len);
- printf("%lu\n", nbands);
- }
-
- res = setup_cie_xyz(cie, FUNC_NAME, nbands);
- if(res != RES_OK) goto error;
-
- htrdr_log(htrdr, "CIE XYZ spectral interval defined on [%g, %g] nanometers.\n",
- range[0], range[1]);
-
-exit:
- *out_cie = cie;
- return res;
-error:
- if(cie) htrdr_cie_xyz_ref_put(cie);
- goto exit;
-}
-
-void
-htrdr_cie_xyz_ref_get(struct htrdr_cie_xyz* cie)
-{
- ASSERT(cie);
- ref_get(&cie->ref);
-}
-
-void
-htrdr_cie_xyz_ref_put(struct htrdr_cie_xyz* cie)
-{
- ASSERT(cie);
- ref_put(&cie->ref, release_cie_xyz);
-}
-
-double
-htrdr_cie_xyz_sample_X
- (struct htrdr_cie_xyz* cie,
- const double r0,
- const double r1,
- double* pdf)
-{
- const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_X),
- darray_double_size_get(&cie->cdf_X), fit_x_bar_1931, r0, r1);
- if(pdf) *pdf = cie->rcp_integral_X;
- return wlen;
-}
-
-double
-htrdr_cie_xyz_sample_Y
- (struct htrdr_cie_xyz* cie,
- const double r0,
- const double r1,
- double* pdf)
-{
- const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Y),
- darray_double_size_get(&cie->cdf_Y), fit_y_bar_1931, r0, r1);
- if(pdf) *pdf = cie->rcp_integral_Y;
- return wlen;
-}
-
-double
-htrdr_cie_xyz_sample_Z
- (struct htrdr_cie_xyz* cie,
- const double r0,
- const double r1,
- double* pdf)
-{
- const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Z),
- darray_double_size_get(&cie->cdf_Z), fit_z_bar_1931, r0, r1);
- if(pdf) *pdf = cie->rcp_integral_Z;
- return wlen;
-}
-
diff --git a/src/htrdr_cie_xyz.h b/src/htrdr_cie_xyz.h
@@ -1,66 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_CIE_XYZ_H
-#define HTRDR_CIE_XYZ_H
-
-#include <rsys/rsys.h>
-
-struct htrdr;
-struct htrdr_cie_xyz;
-
-/* Wavelength boundaries of the CIE XYZ color space in nanometers */
-#define HTRDR_CIE_XYZ_RANGE_DEFAULT__ {380, 780}
-static const double HTRDR_CIE_XYZ_RANGE_DEFAULT[2] =
- HTRDR_CIE_XYZ_RANGE_DEFAULT__;
-
-extern LOCAL_SYM res_T
-htrdr_cie_xyz_create
- (struct htrdr* htrdr,
- const double range[2], /* Must be included in [380, 780] nanometers */
- const size_t nbands, /* # bands used to discretisze the CIE tristimulus s*/
- struct htrdr_cie_xyz** cie);
-
-extern LOCAL_SYM void
-htrdr_cie_xyz_ref_get
- (struct htrdr_cie_xyz* cie);
-
-extern LOCAL_SYM void
-htrdr_cie_xyz_ref_put
- (struct htrdr_cie_xyz* cie);
-
-/* Return a wavelength in nanometer */
-extern LOCAL_SYM double
-htrdr_cie_xyz_sample_X
- (struct htrdr_cie_xyz* cie,
- const double r0, const double r1, /* Canonical numbers in [0, 1[ */
- double* pdf); /* In nm^-1. May be NULL */
-
-/* Return a wavelength in nanometer */
-extern LOCAL_SYM double
-htrdr_cie_xyz_sample_Y
- (struct htrdr_cie_xyz* cie,
- const double r0, const double r1, /* Canonical number in [0, 1[ */
- double* pdf); /* In nm^-1. May be NULL */
-
-/* Return a wavelength in nanometer */
-extern LOCAL_SYM double
-htrdr_cie_xyz_sample_Z
- (struct htrdr_cie_xyz* cie,
- const double r0, const double r1, /* Canonical number in [0, 1[ */
- double* pdf); /* In nm^-1. May be NULL */
-
-#endif /* HTRDR_cie_xyz_H */
-
diff --git a/src/htrdr_compute_radiance_sw.c b/src/htrdr_compute_radiance_sw.c
@@ -1,497 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htrdr.h"
-#include "htrdr_c.h"
-#include "htrdr_interface.h"
-#include "htrdr_ground.h"
-#include "htrdr_solve.h"
-#include "htrdr_sun.h"
-
-#include <high_tune/htsky.h>
-
-#include <star/s3d.h>
-#include <star/ssf.h>
-#include <star/ssp.h>
-#include <star/svx.h>
-
-#include <rsys/double2.h>
-#include <rsys/float2.h>
-#include <rsys/float3.h>
-
-struct scattering_context {
- struct ssp_rng* rng;
- const struct htsky* sky;
- size_t iband; /* Index of the spectral band */
- size_t iquad; /* Index of the quadrature point into the band */
-
- double Ts; /* Sampled optical thickness */
- double traversal_dst; /* Distance traversed along the ray */
-};
-static const struct scattering_context SCATTERING_CONTEXT_NULL = {
- NULL, NULL, 0, 0, 0, 0
-};
-
-struct transmissivity_context {
- struct ssp_rng* rng;
- const struct htsky* sky;
- size_t iband; /* Index of the spectral */
- size_t iquad; /* Index of the quadrature point into the band */
-
- double Ts; /* Sampled optical thickness */
- double Tmin; /* Minimal optical thickness */
- double traversal_dst; /* Distance traversed along the ray */
-
- enum htsky_property prop;
-};
-static const struct transmissivity_context TRANSMISSION_CONTEXT_NULL = {
- NULL, NULL, 0, 0, 0, 0, 0, 0
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static int
-scattering_hit_filter
- (const struct svx_hit* hit,
- const double org[3],
- const double dir[3],
- const double range[2],
- void* context)
-{
- struct scattering_context* ctx = context;
- double ks_max;
- int pursue_traversal = 1;
- ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range);
- (void)range;
-
- ks_max = htsky_fetch_svx_voxel_property(ctx->sky, HTSKY_Ks,
- HTSKY_SVX_MAX, HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, &hit->voxel);
-
- ctx->traversal_dst = hit->distance[0];
-
- /* Iterate until a collision occurs into the voxel or until the ray
- * does not collide the voxel */
- for(;;) {
- /* Compute tau for the current leaf */
- const double vox_dst = hit->distance[1] - ctx->traversal_dst;
- const double T = vox_dst * ks_max;
-
- /* A collision occurs behind `vox_dst' */
- if(ctx->Ts > T) {
- ctx->Ts -= T;
- ctx->traversal_dst = hit->distance[1];
- pursue_traversal = 1;
- break;
-
- /* A real/null collision occurs before `vox_dst' */
- } else {
- double pos[3];
- double proba;
- double ks;
- const double collision_dst = ctx->Ts / ks_max;
-
- /* Compute the traversed distance up to the challenged collision */
- ctx->traversal_dst += collision_dst;
- ASSERT(ctx->traversal_dst >= hit->distance[0]);
- ASSERT(ctx->traversal_dst <= hit->distance[1]);
-
- /* Stop the ray whenever the traversal distance without any scattering
- * event is too high. It means the maximum scattering coefficient has a
- * very small value, and the returned radiance is null. This can only
- * happen when the voxel has a [quasi] infinite length in the propagation
- * direction. */
- if(ctx->traversal_dst > 1e9) break;
-
- /* Compute the world space position where a collision may occur */
- pos[0] = org[0] + ctx->traversal_dst * dir[0];
- pos[1] = org[1] + ctx->traversal_dst * dir[1];
- pos[2] = org[2] + ctx->traversal_dst * dir[2];
-
- ks = htsky_fetch_raw_property(ctx->sky, HTSKY_Ks,
- HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX);
-
- /* Handle the case that ks_max is not *really* the max */
- proba = ks / ks_max;
-
- if(ssp_rng_canonical(ctx->rng) < proba) {/* Collide <=> real scattering */
- pursue_traversal = 0;
- break;
- } else { /* Null collision */
- ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */
- }
- }
- }
- return pursue_traversal;
-}
-
-static int
-transmissivity_hit_filter
- (const struct svx_hit* hit,
- const double org[3],
- const double dir[3],
- const double range[2],
- void* context)
-{
- struct transmissivity_context* ctx = context;
- int comp_mask = HTSKY_CPNT_MASK_ALL;
- double k_max;
- double k_min;
- int pursue_traversal = 1;
- ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range);
- (void)range;
-
- k_min = htsky_fetch_svx_voxel_property(ctx->sky, ctx->prop,
- HTSKY_SVX_MIN, comp_mask, ctx->iband, ctx->iquad, &hit->voxel);
- k_max = htsky_fetch_svx_voxel_property(ctx->sky, ctx->prop,
- HTSKY_SVX_MAX, comp_mask, ctx->iband, ctx->iquad, &hit->voxel);
- ASSERT(k_min <= k_max);
-
- ctx->Tmin += (hit->distance[1] - hit->distance[0]) * k_min;
- ctx->traversal_dst = hit->distance[0];
-
- /* Iterate until a collision occurs into the voxel or until the ray
- * does not collide the voxel */
- for(;;) {
- const double vox_dst = hit->distance[1] - ctx->traversal_dst;
- const double Tdif = vox_dst * (k_max-k_min);
-
- /* A collision occurs behind `vox_dst' */
- if(ctx->Ts > Tdif) {
- ctx->Ts -= Tdif;
- ctx->traversal_dst = hit->distance[1];
- pursue_traversal = 1;
- break;
-
- /* A real/null collision occurs before `vox_dst' */
- } else {
- double x[3];
- double k;
- double proba;
- double collision_dst = ctx->Ts / (k_max - k_min);
-
- /* Compute the traversed distance up to the challenged collision */
- ctx->traversal_dst += collision_dst;
- ASSERT(ctx->traversal_dst >= hit->distance[0]);
- ASSERT(ctx->traversal_dst <= hit->distance[1]);
-
- /* Compute the world space position where a collision may occur */
- x[0] = org[0] + ctx->traversal_dst * dir[0];
- x[1] = org[1] + ctx->traversal_dst * dir[1];
- x[2] = org[2] + ctx->traversal_dst * dir[2];
-
- k = htsky_fetch_raw_property(ctx->sky, ctx->prop,
- comp_mask, ctx->iband, ctx->iquad, x, k_min, k_max);
- ASSERT(k >= k_min && k <= k_max);
-
- proba = (k - k_min) / (k_max - k_min);
-
- if(ssp_rng_canonical(ctx->rng) < proba) { /* Collide */
- pursue_traversal = 0;
- break;
- } else { /* Null collision */
- ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */
- }
- }
- }
- return pursue_traversal;
-}
-
-static double
-transmissivity
- (struct htrdr* htrdr,
- struct ssp_rng* rng,
- const enum htsky_property prop,
- const size_t iband,
- const size_t iquad,
- const double pos[3],
- const double dir[3],
- const double range[2])
-{
- struct svx_hit svx_hit;
- struct transmissivity_context transmissivity_ctx = TRANSMISSION_CONTEXT_NULL;
-
- ASSERT(htrdr && rng && pos && dir && range);
-
- transmissivity_ctx.rng = rng;
- transmissivity_ctx.sky = htrdr->sky;
- transmissivity_ctx.iband = iband;
- transmissivity_ctx.iquad = iquad;
- transmissivity_ctx.Ts = ssp_ran_exp(rng, 1); /* Sample an optical thickness */
- transmissivity_ctx.prop = prop;
-
- /* Compute the transmissivity */
- HTSKY(trace_ray(htrdr->sky, pos, dir, range, NULL,
- transmissivity_hit_filter, &transmissivity_ctx, iband, iquad, &svx_hit));
-
- if(SVX_HIT_NONE(&svx_hit)) {
- return transmissivity_ctx.Tmin ? exp(-transmissivity_ctx.Tmin) : 1.0;
- } else {
- return 0;
- }
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-double
-htrdr_compute_radiance_sw
- (struct htrdr* htrdr,
- const size_t ithread,
- struct ssp_rng* rng,
- const int cpnt_mask, /* Combination of enum htrdr_radiance_cpnt_flag */
- const double pos_in[3],
- const double dir_in[3],
- const double wlen, /* In nanometer */
- const size_t iband,
- const size_t iquad)
-{
- struct s3d_hit s3d_hit = S3D_HIT_NULL;
- struct s3d_hit s3d_hit_tmp = S3D_HIT_NULL;
- struct s3d_hit s3d_hit_prev = S3D_HIT_NULL;
- struct svx_hit svx_hit = SVX_HIT_NULL;
- struct ssf_phase* phase_hg = NULL;
- struct ssf_phase* phase_rayleigh = NULL;
-
- double pos[3];
- double dir[3];
- double range[2];
- double pos_next[3];
- double dir_next[3];
- double band_bounds[2]; /* In nanometers */
-
- double R;
- double r; /* Random number */
- double wo[3]; /* -dir */
- double pdf;
- double Tr; /* Overall transmissivity */
- double Tr_abs; /* Absorption transmissivity */
- double L_sun; /* Sun radiance in W.m^-2.sr^-1 */
- double sun_dir[3];
- double ksi = 1; /* Throughput */
- double w = 0; /* MC weight */
- double g = 0; /* Asymmetry parameter of the HG phase function */
-
- ASSERT(htrdr && rng && pos_in && dir_in && ithread < htrdr->nthreads);
- ASSERT(htrdr->spectral_type == HTRDR_SPECTRAL_SW
- || htrdr->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ);
-
- CHK(RES_OK == ssf_phase_create
- (&htrdr->lifo_allocators[ithread], &ssf_phase_hg, &phase_hg));
- CHK(RES_OK == ssf_phase_create
- (&htrdr->lifo_allocators[ithread], &ssf_phase_rayleigh, &phase_rayleigh));
-
- /* Setup the phase function for this wavelength */
- g = htsky_fetch_per_wavelength_particle_phase_function_asymmetry_parameter
- (htrdr->sky, wlen);
- SSF(phase_hg_setup(phase_hg, g));
-
- /* Fetch sun properties. Note that the sun spectral data are defined by bands
- * that, actually are the same of the SW spectral bands defined in the
- * default "ecrad_opt_prot.txt" file provided by the HTGOP project. */
- htsky_get_spectral_band_bounds(htrdr->sky, iband, band_bounds);
- ASSERT(band_bounds[0] <= wlen && wlen <= band_bounds[1]);
- L_sun = htrdr_sun_get_radiance(htrdr->sun, wlen);
- d3_set(pos, pos_in);
- d3_set(dir, dir_in);
-
- if((cpnt_mask & HTRDR_RADIANCE_DIRECT) /* Handle direct contribation */
- && htrdr_sun_is_dir_in_solar_cone(htrdr->sun, dir)) {
- /* Check that the ray is not occluded along the submitted range */
- d2(range, 0, FLT_MAX);
- HTRDR(ground_trace_ray(htrdr->ground, pos, dir, range, NULL, &s3d_hit_tmp));
- if(!S3D_HIT_NONE(&s3d_hit_tmp)) {
- Tr = 0;
- } else {
- Tr = transmissivity
- (htrdr, rng, HTSKY_Kext, iband, iquad , pos, dir, range);
- w = L_sun * Tr;
- }
- }
-
- if((cpnt_mask & HTRDR_RADIANCE_DIFFUSE) == 0)
- goto exit; /* Discard diffuse contribution */
-
- /* Radiative random walk */
- for(;;) {
- struct scattering_context scattering_ctx = SCATTERING_CONTEXT_NULL;
- struct ssf_bsdf* bsdf = NULL;
- struct ssf_phase* phase;
- double N[3];
- double bounce_reflectivity = 1;
- double sun_dir_pdf;
- int surface_scattering = 0; /* Define if hit a surface */
- int bsdf_type = 0;
-
- /* Find the first intersection with a surface */
- d2(range, 0, DBL_MAX);
- HTRDR(ground_trace_ray
- (htrdr->ground, pos, dir, range, &s3d_hit_prev, &s3d_hit));
-
- /* Sample an optical thickness */
- scattering_ctx.Ts = ssp_ran_exp(rng, 1);
-
- /* Setup the remaining scattering context fields */
- scattering_ctx.rng = rng;
- scattering_ctx.sky = htrdr->sky;
- scattering_ctx.iband = iband;
- scattering_ctx.iquad = iquad;
-
- /* Define if a scattering event occurs */
- d2(range, 0, s3d_hit.distance);
- HTSKY(trace_ray(htrdr->sky, pos, dir, range, NULL,
- scattering_hit_filter, &scattering_ctx, iband, iquad, &svx_hit));
-
- /* No scattering and no surface reflection. Stop the radiative random walk */
- if(S3D_HIT_NONE(&s3d_hit) && SVX_HIT_NONE(&svx_hit)) {
- break;
- }
- ASSERT(SVX_HIT_NONE(&svx_hit)
- || ( svx_hit.distance[0] <= scattering_ctx.traversal_dst
- && svx_hit.distance[1] >= scattering_ctx.traversal_dst));
-
- /* Negate the incoming dir to match the convention of the SSF library */
- d3_minus(wo, dir);
-
- /* Define if the scattering occurs at a surface */
- surface_scattering = SVX_HIT_NONE(&svx_hit);
-
- /* Compute the new position */
- pos_next[0] = pos[0] + dir[0]*scattering_ctx.traversal_dst;
- pos_next[1] = pos[1] + dir[1]*scattering_ctx.traversal_dst;
- pos_next[2] = pos[2] + dir[2]*scattering_ctx.traversal_dst;
-
- /* Define the previous hit surface used to avoid self hit */
- s3d_hit_prev = surface_scattering ? s3d_hit : S3D_HIT_NULL;
-
- /* Define the absorption transmissivity from the current position to the
- * next position */
- d2(range, 0, scattering_ctx.traversal_dst);
- Tr_abs = transmissivity
- (htrdr, rng, HTSKY_Ka, iband, iquad, pos, dir, range);
- if(Tr_abs <= 0) break;
-
- /* Sample the scattering direction */
- if(surface_scattering) { /* Scattering at a surface */
- struct htrdr_interface interf = HTRDR_INTERFACE_NULL;
- const struct htrdr_mtl* mtl = NULL;
-
- /* Fetch the hit interface materal and build its BSDF */
- htrdr_ground_get_interface(htrdr->ground, &s3d_hit, &interf);
- mtl = htrdr_interface_fetch_hit_mtl(&interf, dir, &s3d_hit);
- HTRDR(mtl_create_bsdf(htrdr, mtl, ithread, wlen, rng, &bsdf));
-
- /* Revert the normal if necessary to match the SSF convention */
- d3_normalize(N, d3_set_f3(N, s3d_hit.normal));
- if(d3_dot(N, wo) < 0) d3_minus(N, N);
-
- /* Sample scattering direction */
- bounce_reflectivity = ssf_bsdf_sample
- (bsdf, rng, wo, N, dir_next, &bsdf_type, &pdf);
- if(!(bsdf_type & SSF_REFLECTION)) { /* Handle only reflections */
- bounce_reflectivity = 0;
- }
-
- } else { /* Scattering in a volume */
- double ks_particle; /* Scattering coefficient of the particles */
- double ks_gas; /* Scattering coefficient of the gaz */
- double ks; /* Overall scattering coefficient */
-
- ks_gas = htsky_fetch_raw_property(htrdr->sky, HTSKY_Ks,
- HTSKY_CPNT_FLAG_GAS, iband, iquad, pos_next, -DBL_MAX, DBL_MAX);
- ks_particle = htsky_fetch_raw_property(htrdr->sky, HTSKY_Ks,
- HTSKY_CPNT_FLAG_PARTICLES, iband, iquad, pos_next, -DBL_MAX, DBL_MAX);
- ks = ks_particle + ks_gas;
-
- r = ssp_rng_canonical(rng);
- if(r < ks_gas / ks) { /* Gas scattering */
- phase = phase_rayleigh;
- } else { /* Cloud scattering */
- phase = phase_hg;
- }
-
- /* Sample scattering direction */
- ssf_phase_sample(phase, rng, wo, dir_next, NULL);
- ssf_phase_ref_get(phase);
- }
-
- /* Sample the direction of the direct contribution */
- if(surface_scattering && (bsdf_type & SSF_SPECULAR)) {
- if(!htrdr_sun_is_dir_in_solar_cone(htrdr->sun, dir_next)) {
- R = 0; /* No direct lightning */
- } else {
- sun_dir[0] = dir_next[0];
- sun_dir[1] = dir_next[1];
- sun_dir[2] = dir_next[2];
- R = d3_dot(N, sun_dir)<0/* Below the ground*/ ? 0 : bounce_reflectivity;
- }
- sun_dir_pdf = 1.0;
- } else {
- /* Sample a sun direction */
- sun_dir_pdf = htrdr_sun_sample_direction(htrdr->sun, rng, sun_dir);
- if(surface_scattering) {
- R = d3_dot(N, sun_dir) < 0/* Below the ground */
- ? 0 : ssf_bsdf_eval(bsdf, wo, N, sun_dir) * d3_dot(N, sun_dir);
- } else {
- R = ssf_phase_eval(phase, wo, sun_dir);
- }
- }
-
- /* The direct contribution to the scattering point is not null so we need
- * to compute the transmissivity from sun to scatt pt */
- if(R <= 0) {
- Tr = 0;
- } else {
- /* Check that the sun is visible from the new position */
- d2(range, 0, FLT_MAX);
- HTRDR(ground_trace_ray
- (htrdr->ground, pos_next, sun_dir, range, &s3d_hit_prev, &s3d_hit_tmp));
-
- /* Compute the sun transmissivity */
- if(!S3D_HIT_NONE(&s3d_hit_tmp)) {
- Tr = 0;
- } else {
- Tr = transmissivity
- (htrdr, rng, HTSKY_Kext, iband, iquad, pos_next, sun_dir, range);
- }
- }
-
- /* Release the scattering function */
- if(surface_scattering) {
- SSF(bsdf_ref_put(bsdf));
- } else {
- SSF(phase_ref_put(phase));
- }
-
- /* Update the MC weight */
- ksi *= Tr_abs;
- w += ksi * L_sun * Tr * R / sun_dir_pdf;
-
- /* Russian roulette wrt surface scattering */
- if(surface_scattering && ssp_rng_canonical(rng) >= bounce_reflectivity)
- break;
-
- /* Setup the next random walk state */
- d3_set(pos, pos_next);
- d3_set(dir, dir_next);
- }
-
-exit:
- SSF(phase_ref_put(phase_hg));
- SSF(phase_ref_put(phase_rayleigh));
- return w;
-}
-
diff --git a/src/htrdr_draw_map.c b/src/htrdr_draw_map.c
@@ -1,1207 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#define _POSIX_C_SOURCE 200112L /* nanosleep && nextafter */
-
-#include "htrdr.h"
-#include "htrdr_c.h"
-#include "htrdr_buffer.h"
-#include "htrdr_camera.h"
-#include "htrdr_cie_xyz.h"
-#include "htrdr_ran_wlen.h"
-#include "htrdr_rectangle.h"
-#include "htrdr_solve.h"
-#include "htrdr_sun.h"
-
-#include <high_tune/htsky.h>
-
-#include <rsys/algorithm.h>
-#include <rsys/clock_time.h>
-#include <rsys/cstr.h>
-#include <rsys/dynamic_array_u32.h>
-#include <rsys/math.h>
-#include <rsys/mutex.h>
-#include <star/ssp.h>
-
-#include <omp.h>
-#include <mpi.h>
-#include <time.h>
-#include <unistd.h>
-
-#define RNG_SEQUENCE_SIZE 10000
-
-#define TILE_MCODE_NULL UINT32_MAX
-#define TILE_SIZE 32 /* Definition in X & Y of a tile */
-STATIC_ASSERT(IS_POW2(TILE_SIZE), TILE_SIZE_must_be_a_power_of_2);
-
-enum pixel_format {
- PIXEL_XWAVE,
- PIXEL_IMAGE
-};
-
-union pixel {
- struct htrdr_pixel_flux flux;
- struct htrdr_pixel_xwave xwave;
- struct htrdr_pixel_image image;
-};
-
-/* Tile of row ordered image pixels */
-struct tile {
- struct list_node node;
- struct mem_allocator* allocator;
- ref_T ref;
-
- struct tile_data {
- uint16_t x, y; /* 2D coordinates of the tile in tile space */
- enum pixel_format format;
- /* Simulate the flexible array member of the C99 standard. */
- union pixel pixels[1/*Dummy element*/];
- } data;
-};
-
-/* List of tile to compute onto the MPI process. */
-struct proc_work {
- struct mutex* mutex;
- struct darray_u32 tiles; /* #tiles to render */
- size_t itile; /* Next tile to render in the above list of tiles */
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static FINLINE uint16_t
-morton2D_decode(const uint32_t u32)
-{
- uint32_t x = u32 & 0x55555555;
- x = (x | (x >> 1)) & 0x33333333;
- x = (x | (x >> 2)) & 0x0F0F0F0F;
- x = (x | (x >> 4)) & 0x00FF00FF;
- x = (x | (x >> 8)) & 0x0000FFFF;
- return (uint16_t)x;
-}
-
-static FINLINE uint32_t
-morton2D_encode(const uint16_t u16)
-{
- uint32_t u32 = u16;
- u32 = (u32 | (u32 << 8)) & 0x00FF00FF;
- u32 = (u32 | (u32 << 4)) & 0X0F0F0F0F;
- u32 = (u32 | (u32 << 2)) & 0x33333333;
- u32 = (u32 | (u32 << 1)) & 0x55555555;
- return u32;
-}
-
-static INLINE enum pixel_format
-spectral_type_to_pixfmt(const enum htrdr_spectral_type spectral_type)
-{
- enum pixel_format pixfmt;
- switch(spectral_type) {
- case HTRDR_SPECTRAL_LW: pixfmt = PIXEL_XWAVE; break;
- case HTRDR_SPECTRAL_SW: pixfmt = PIXEL_XWAVE; break;
- case HTRDR_SPECTRAL_SW_CIE_XYZ: pixfmt = PIXEL_IMAGE; break;
- default: FATAL("Unreachable code.\n"); break;
- }
- return pixfmt;
-}
-
-static FINLINE struct tile*
-tile_create(struct mem_allocator* allocator, const enum pixel_format fmt)
-{
- struct tile* tile;
- const size_t tile_sz =
- sizeof(struct tile) - sizeof(union pixel)/*rm dummy pixel*/;
- const size_t buf_sz = /* Flexiblbe array element */
- TILE_SIZE*TILE_SIZE*sizeof(union pixel);
- ASSERT(allocator);
-
- tile = MEM_ALLOC(allocator, tile_sz+buf_sz);
- if(!tile) return NULL;
-
- tile->data.format = fmt;
-
- ref_init(&tile->ref);
- list_init(&tile->node);
- tile->allocator = allocator;
- ASSERT(IS_ALIGNED(&tile->data.pixels, ALIGNOF(union pixel)));
-
- return tile;
-}
-
-static INLINE void
-tile_ref_get(struct tile* tile)
-{
- ASSERT(tile);
- tile_ref_get(tile);
-}
-
-static INLINE void
-release_tile(ref_T* ref)
-{
- struct tile* tile = CONTAINER_OF(ref, struct tile, ref);
- ASSERT(ref);
- MEM_RM(tile->allocator, tile);
-}
-
-static INLINE void
-tile_ref_put(struct tile* tile)
-{
- ASSERT(tile);
- ref_put(&tile->ref, release_tile);
-}
-
-static FINLINE union pixel*
-tile_at
- (struct tile* tile,
- const size_t x, /* In tile space */
- const size_t y) /* In tile space */
-{
- ASSERT(tile && x < TILE_SIZE && y < TILE_SIZE);
- return tile->data.pixels + (y*TILE_SIZE + x);
-}
-
-static void
-write_tile_data
- (struct htrdr* htrdr,
- const struct htrdr_sensor* sensor,
- struct htrdr_buffer* buf,
- const struct tile_data* tile_data)
-{
- struct htrdr_buffer_layout layout = HTRDR_BUFFER_LAYOUT_NULL;
- size_t icol, irow;
- size_t irow_tile;
- size_t ncols_tile, nrows_tile;
- char* buf_mem;
- ASSERT(htrdr && sensor && buf && tile_data);
- (void)htrdr, (void)sensor;
-
- htrdr_buffer_get_layout(buf, &layout);
- buf_mem = htrdr_buffer_get_data(buf);
- ASSERT(layout.elmt_size
- == htrdr_spectral_type_get_pixsz(htrdr->spectral_type, sensor->type));
-
- /* Compute the row/column of the tile origin into the buffer */
- icol = tile_data->x * (size_t)TILE_SIZE;
- irow = tile_data->y * (size_t)TILE_SIZE;
-
- /* Define the number of tile row/columns to write into the buffer */
- ncols_tile = MMIN(icol + TILE_SIZE, layout.width) - icol;
- nrows_tile = MMIN(irow + TILE_SIZE, layout.height) - irow;
-
- /* Copy the row ordered tile data */
- FOR_EACH(irow_tile, 0, nrows_tile) {
- char* buf_row = buf_mem + (irow + irow_tile) * layout.pitch;
- char* buf_col = buf_row + icol * layout.elmt_size;
- const union pixel* tile_row = tile_data->pixels + irow_tile*TILE_SIZE;
- size_t x;
-
- FOR_EACH(x, 0, ncols_tile) {
- switch(tile_data->format) {
- case PIXEL_XWAVE:
- ((struct htrdr_pixel_xwave*)buf_col)[x] = tile_row[x].xwave;
- break;
- case PIXEL_IMAGE:
- ((struct htrdr_pixel_image*)buf_col)[x] = tile_row[x].image;
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- }
- }
-}
-
-static INLINE void
-proc_work_init(struct mem_allocator* allocator, struct proc_work* work)
-{
- ASSERT(work);
- darray_u32_init(allocator, &work->tiles);
- work->itile = 0;
- CHK(work->mutex = mutex_create());
-}
-
-static INLINE void
-proc_work_release(struct proc_work* work)
-{
- darray_u32_release(&work->tiles);
- mutex_destroy(work->mutex);
-}
-
-static INLINE void
-proc_work_reset(struct proc_work* work)
-{
- ASSERT(work);
- mutex_lock(work->mutex);
- darray_u32_clear(&work->tiles);
- work->itile = 0;
- mutex_unlock(work->mutex);
-}
-
-static INLINE void
-proc_work_add_tile(struct proc_work* work, const uint32_t mcode)
-{
- mutex_lock(work->mutex);
- CHK(darray_u32_push_back(&work->tiles, &mcode) == RES_OK);
- mutex_unlock(work->mutex);
-}
-
-static INLINE uint32_t
-proc_work_get_tile(struct proc_work* work)
-{
- uint32_t mcode;
- ASSERT(work);
- mutex_lock(work->mutex);
- if(work->itile >= darray_u32_size_get(&work->tiles)) {
- mcode = TILE_MCODE_NULL;
- } else {
- mcode = darray_u32_cdata_get(&work->tiles)[work->itile];
- ++work->itile;
- }
- mutex_unlock(work->mutex);
- return mcode;
-}
-
-static INLINE size_t
-proc_work_get_ntiles(struct proc_work* work)
-{
- size_t sz = 0;
- ASSERT(work);
- mutex_lock(work->mutex);
- sz = darray_u32_size_get(&work->tiles);
- mutex_unlock(work->mutex);
- return sz;
-}
-
-static void
-mpi_wait_for_request(struct htrdr* htrdr, MPI_Request* req)
-{
- ASSERT(htrdr && req);
-
- /* Wait for process synchronisation */
- for(;;) {
- struct timespec t;
- int complete;
- t.tv_sec = 0;
- t.tv_nsec = 10000000; /* 10ms */
-
- mutex_lock(htrdr->mpi_mutex);
- MPI(Test(req, &complete, MPI_STATUS_IGNORE));
- mutex_unlock(htrdr->mpi_mutex);
- if(complete) break;
-
- nanosleep(&t, NULL);
- }
-}
-
-static void
-mpi_probe_thieves
- (struct htrdr* htrdr,
- struct proc_work* work,
- ATOMIC* probe_thieves)
-{
- uint32_t tiles[UINT8_MAX];
- struct timespec t;
- ASSERT(htrdr && work && probe_thieves);
-
- if(htrdr->mpi_nprocs == 1) /* The process is alone. No thief is possible */
- return;
-
- t.tv_sec = 0;
-
- /* Protect MPI calls of multiple invocations from concurrent threads */
- #define P_MPI(Func) { \
- mutex_lock(htrdr->mpi_mutex); \
- MPI(Func); \
- mutex_unlock(htrdr->mpi_mutex); \
- } (void)0
-
- while(ATOMIC_GET(probe_thieves)) {
- MPI_Status status;
- size_t itile;
- int msg;
-
- /* Probe if a steal request was submitted by any processes */
- P_MPI(Iprobe(MPI_ANY_SOURCE, HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD, &msg,
- &status));
-
- if(msg) { /* A steal request was posted */
- MPI_Request req;
- uint8_t ntiles_to_steal;
-
- /* Asynchronously receive the steal request */
- P_MPI(Irecv(&ntiles_to_steal, 1, MPI_UINT8_T, status.MPI_SOURCE,
- HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD, &req));
-
- /* Wait for the completion of the steal request */
- mpi_wait_for_request(htrdr, &req);
-
- /* Thief some tiles */
- FOR_EACH(itile, 0, ntiles_to_steal) {
- tiles[itile] = proc_work_get_tile(work);
- }
- P_MPI(Send(&tiles, ntiles_to_steal, MPI_UINT32_T, status.MPI_SOURCE,
- HTRDR_MPI_WORK_STEALING, MPI_COMM_WORLD));
- }
- t.tv_nsec = 500000000; /* 500ms */
- nanosleep(&t, NULL);
- }
- #undef P_MPI
-}
-
-static int
-mpi_sample_working_process(struct htrdr* htrdr, struct ssp_rng* rng)
-{
- int iproc, i;
- int dst_rank;
- ASSERT(htrdr && rng && htrdr->mpi_nworking_procs);
-
- /* Sample the index of the 1st active process */
- iproc = (int)(ssp_rng_canonical(rng) * (double)htrdr->mpi_nworking_procs);
-
- /* Find the rank of the sampled active process. Use a simple linear search
- * since the overall number of processes should be quite low; at most few
- * dozens. */
- i = 0;
- FOR_EACH(dst_rank, 0, htrdr->mpi_nprocs) {
- if(htrdr->mpi_working_procs[dst_rank] == 0) continue; /* Inactive process */
- if(i == iproc) break; /* The rank of the sampled process is found */
- ++i;
- }
- ASSERT(dst_rank < htrdr->mpi_nprocs);
- return dst_rank;
-}
-
-/* Return the number of stolen tiles */
-static size_t
-mpi_steal_work
- (struct htrdr* htrdr,
- struct ssp_rng* rng,
- struct proc_work* work)
-{
- MPI_Request req;
- size_t itile;
- size_t nthieves = 0;
- uint32_t tiles[UINT8_MAX]; /* Morton code of the stolen tile */
- int proc_to_steal; /* Process to steal */
- uint8_t ntiles_to_steal = MMIN((uint8_t)(htrdr->nthreads*2), 16);
- ASSERT(htrdr && rng && work && htrdr->nthreads < UINT8_MAX);
-
- /* Protect MPI calls of multiple invocations from concurrent threads */
- #define P_MPI(Func) { \
- mutex_lock(htrdr->mpi_mutex); \
- MPI(Func); \
- mutex_unlock(htrdr->mpi_mutex); \
- } (void)0
-
- /* No more working process => nohting to steal */
- if(!htrdr->mpi_nworking_procs) return 0;
-
- /* Sample a process to steal */
- proc_to_steal = mpi_sample_working_process(htrdr, rng);
-
- /* Send a steal request to the sampled process and wait for a response */
- P_MPI(Send(&ntiles_to_steal, 1, MPI_UINT8_T, proc_to_steal,
- HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD));
-
- /* Receive the stolen tile from the sampled process */
- P_MPI(Irecv(tiles, ntiles_to_steal, MPI_UINT32_T, proc_to_steal,
- HTRDR_MPI_WORK_STEALING, MPI_COMM_WORLD, &req));
-
- mpi_wait_for_request(htrdr, &req);
-
- FOR_EACH(itile, 0, ntiles_to_steal) {
- if(tiles[itile] == TILE_MCODE_NULL) {
- ASSERT(htrdr->mpi_working_procs[proc_to_steal] != 0);
- htrdr->mpi_working_procs[proc_to_steal] = 0;
- htrdr->mpi_nworking_procs--;
- break;
- }
- proc_work_add_tile(work, tiles[itile]);
- ++nthieves;
- }
- #undef P_MPI
- return nthieves;
-}
-
-static res_T
-mpi_gather_tiles
- (struct htrdr* htrdr,
- const struct htrdr_sensor* sensor,
- struct htrdr_buffer* buf,
- const size_t ntiles,
- struct list_node* tiles)
-{
- /* Compute the size of the tile_data */
- const size_t msg_sz =
- sizeof(struct tile_data) - sizeof(union pixel)/*dummy*/
- + TILE_SIZE*TILE_SIZE*sizeof(union pixel);
-
- struct list_node* node = NULL;
- struct tile* tile = NULL;
- res_T res = RES_OK;
- ASSERT(htrdr && tiles);
- ASSERT(htrdr->mpi_rank != 0 || buf);
- (void)ntiles;
-
- if(htrdr->mpi_rank != 0) { /* Non master process */
- /* Send the computed tile to the master process */
- LIST_FOR_EACH(node, tiles) {
- struct tile* t = CONTAINER_OF(node, struct tile, node);
- MPI(Send(&t->data, (int)msg_sz, MPI_CHAR, 0,
- HTRDR_MPI_TILE_DATA, MPI_COMM_WORLD));
- }
- } else { /* Master process */
- size_t itile = 0;
-
- LIST_FOR_EACH(node, tiles) {
- struct tile* t = CONTAINER_OF(node, struct tile, node);
- write_tile_data(htrdr, sensor, buf, &t->data);
- ++itile;
- }
-
- if(itile != ntiles) {
- enum pixel_format pixfmt;
- ASSERT(htrdr->mpi_nprocs > 1);
-
- /* Create a temporary tile to receive the tile data computed by the
- * concurrent MPI processes */
- pixfmt = spectral_type_to_pixfmt(htrdr->spectral_type);
- tile = tile_create(htrdr->allocator, pixfmt);
- if(!tile) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "could not allocate the temporary tile used to gather the process "
- "output data -- %s.\n", res_to_cstr(res));
- goto error;
- }
-
- /* Receive the tile data of the concurrent MPI processes */
- FOR_EACH(itile, itile, ntiles) {
- MPI(Recv(&tile->data, (int)msg_sz, MPI_CHAR, MPI_ANY_SOURCE,
- HTRDR_MPI_TILE_DATA, MPI_COMM_WORLD, MPI_STATUS_IGNORE));
- write_tile_data(htrdr, sensor, buf, &tile->data);
- }
- }
- }
-
-exit:
- if(tile) tile_ref_put(tile);
- return res;
-error:
- goto exit;
-}
-
-static void
-draw_pixel_image
- (struct htrdr* htrdr,
- const size_t ithread,
- const size_t ipix[2],
- const double pix_sz[2], /* Size of a pixel in the normalized image plane */
- const struct htrdr_camera* cam,
- const size_t spp,
- struct ssp_rng* rng,
- struct htrdr_pixel_image* pixel)
-{
- struct htrdr_accum XYZ[3]; /* X, Y, and Z */
- struct htrdr_accum time;
- size_t ichannel;
- ASSERT(ipix && ipix && pix_sz && cam && rng && pixel);
- ASSERT(htrdr->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ);
-
- /* Reset accumulators */
- XYZ[0] = HTRDR_ACCUM_NULL;
- XYZ[1] = HTRDR_ACCUM_NULL;
- XYZ[2] = HTRDR_ACCUM_NULL;
- time = HTRDR_ACCUM_NULL;
-
- FOR_EACH(ichannel, 0, 3) {
- size_t isamp;
-
- FOR_EACH(isamp, 0, spp) {
- struct time t0, t1;
- double pix_samp[2];
- double ray_org[3];
- double ray_dir[3];
- double weight;
- double r0, r1, r2;
- double wlen; /* Sampled wavelength into the spectral band */
- double pdf;
- size_t iband; /* Sampled spectral band */
- size_t iquad; /* Sampled quadrature point into the spectral band */
- double usec;
-
- /* Begin the registration of the time spent to in the realisation */
- time_current(&t0);
-
- /* Sample a position into the pixel, in the normalized image plane */
- pix_samp[0] = ((double)ipix[0] + ssp_rng_canonical(rng)) * pix_sz[0];
- pix_samp[1] = ((double)ipix[1] + ssp_rng_canonical(rng)) * pix_sz[1];
-
- /* Generate a ray starting from the pinhole camera and passing through the
- * pixel sample */
- htrdr_camera_ray(cam, pix_samp, ray_org, ray_dir);
-
- r0 = ssp_rng_canonical(rng);
- r1 = ssp_rng_canonical(rng);
- r2 = ssp_rng_canonical(rng);
-
- /* Sample a spectral band and a quadrature point */
- switch(ichannel) {
- case 0: wlen = htrdr_cie_xyz_sample_X(htrdr->cie, r0, r1, &pdf); break;
- case 1: wlen = htrdr_cie_xyz_sample_Y(htrdr->cie, r0, r1, &pdf); break;
- case 2: wlen = htrdr_cie_xyz_sample_Z(htrdr->cie, r0, r1, &pdf); break;
- default: FATAL("Unreachable code.\n"); break;
- }
-
- iband = htsky_find_spectral_band(htrdr->sky, wlen);
- iquad = htsky_spectral_band_sample_quadrature(htrdr->sky, r2, iband);
-
- /* Compute the radiance in W/m^2/sr/m */
- weight = htrdr_compute_radiance_sw(htrdr, ithread, rng,
- HTRDR_RADIANCE_ALL, ray_org, ray_dir, wlen, iband, iquad);
- ASSERT(weight >= 0);
-
- pdf *= 1.e9; /* Transform the pdf from nm^-1 to m^-1 */
- weight /= pdf; /* In W/m^2/sr */
-
- /* End the registration of the per realisation time */
- time_sub(&t0, time_current(&t1), &t0);
- usec = (double)time_val(&t0, TIME_NSEC) * 0.001;
-
- /* Update the pixel accumulator of the current channel */
- XYZ[ichannel].sum_weights += weight;
- XYZ[ichannel].sum_weights_sqr += weight*weight;
- XYZ[ichannel].nweights += 1;
-
- /* Update the pixel accumulator of per realisation time */
- time.sum_weights += usec;
- time.sum_weights_sqr += usec*usec;
- time.nweights += 1;
- }
- }
-
- /* Flush pixel data */
- htrdr_accum_get_estimation(XYZ+0, &pixel->X);
- htrdr_accum_get_estimation(XYZ+1, &pixel->Y);
- htrdr_accum_get_estimation(XYZ+2, &pixel->Z);
- pixel->time = time;
-}
-
-static void
-draw_pixel_flux
- (struct htrdr* htrdr,
- const size_t ithread,
- const size_t ipix[2],
- const double pix_sz[2], /* Size of a pixel in the normalized image plane */
- const struct htrdr_sensor* sensor,
- const size_t spp,
- struct ssp_rng* rng,
- struct htrdr_pixel_flux* pixel)
-{
- struct htrdr_accum flux;
- struct htrdr_accum time;
- size_t isamp;
- ASSERT(ipix && ipix && pix_sz && sensor && rng && pixel);
- ASSERT(sensor->type == HTRDR_SENSOR_RECTANGLE);
- ASSERT(htrdr->spectral_type == HTRDR_SPECTRAL_LW
- || htrdr->spectral_type == HTRDR_SPECTRAL_SW);
-
- /* Reset the pixel accumulators */
- flux = HTRDR_ACCUM_NULL;
- time = HTRDR_ACCUM_NULL;
-
- FOR_EACH(isamp, 0, spp) {
- struct time t0, t1;
- double ray_org[3];
- double ray_dir[3];
- double weight;
- double r0, r1, r2;
- double wlen;
- size_t iband;
- size_t iquad;
- double usec;
- double band_pdf;
- res_T res = RES_OK;
-
- /* Begin the registration of the time spent in the realisation */
- time_current(&t0);
-
- res = htrdr_sensor_sample_primary_ray(&htrdr->sensor, htrdr, ipix,
- pix_sz, rng, ray_org, ray_dir);
- if(res != RES_OK) continue; /* Reject the current sample */
-
- r0 = ssp_rng_canonical(rng);
- r1 = ssp_rng_canonical(rng);
- r2 = ssp_rng_canonical(rng);
-
- /* Sample a wavelength */
- wlen = htrdr_ran_wlen_sample(htrdr->ran_wlen, r0, r1, &band_pdf);
-
- /* Select the associated band and sample a quadrature point */
- iband = htsky_find_spectral_band(htrdr->sky, wlen);
- iquad = htsky_spectral_band_sample_quadrature(htrdr->sky, r2, iband);
-
- if(htrdr->spectral_type == HTRDR_SPECTRAL_LW) {
- weight = htrdr_compute_radiance_lw(htrdr, ithread, rng, ray_org,
- ray_dir, wlen, iband, iquad);
- weight *= PI / band_pdf; /* Transform weight from W/m^2/sr/m to W/m^2 */
- } else {
- double sun_dir[3];
- double N[3];
- double L_direct;
- double L_diffuse;
- double cos_N_sun_dir;
- double sun_solid_angle;
- ASSERT(htrdr->spectral_type == HTRDR_SPECTRAL_SW);
-
- /* Compute direct contribution if necessary */
- htrdr_sun_sample_direction(htrdr->sun, rng, sun_dir);
- htrdr_rectangle_get_normal(sensor->rectangle, N);
- cos_N_sun_dir = d3_dot(N, sun_dir);
-
- if(cos_N_sun_dir <= 0) {
- L_direct = 0;
- } else {
- L_direct = htrdr_compute_radiance_sw(htrdr, ithread, rng,
- HTRDR_RADIANCE_DIRECT, ray_org, sun_dir, wlen, iband, iquad);
- }
-
- /* Compute diffuse contribution */
- L_diffuse = htrdr_compute_radiance_sw(htrdr, ithread, rng,
- HTRDR_RADIANCE_DIFFUSE, ray_org, ray_dir, wlen, iband, iquad);
-
- sun_solid_angle = htrdr_sun_get_solid_angle(htrdr->sun);
-
- /* Compute the weight in W/m^2/m */
- weight = cos_N_sun_dir * sun_solid_angle * L_direct + PI * L_diffuse;
-
- /* Importance sampling: correct weight with pdf */
- weight /= band_pdf; /* In W/m^2 */
- }
-
- /* End the registration of the per realisation time */
- time_sub(&t0, time_current(&t1), &t0);
- usec = (double)time_val(&t0, TIME_NSEC) * 0.001;
-
- /* Update the pixel accumulator of the flux */
- flux.sum_weights += weight;
- flux.sum_weights_sqr += weight*weight;
- flux.nweights += 1;
-
- /* Update the pixel accumulator of per realisation time */
- time.sum_weights += usec;
- time.sum_weights_sqr += usec*usec;
- time.nweights += 1;
- }
-
- /* Save the per realisation integration time */
- pixel->flux = flux;
- pixel->time = time;
-}
-
-static INLINE double
-radiance_temperature
- (struct htrdr* htrdr,
- const double radiance) /* In W/m^2/sr */
-{
- double temperature = 0;
- double radiance_avg = radiance;
- res_T res = RES_OK;
- ASSERT(htrdr && radiance >= 0);
-
- /* From integrated radiance to average radiance in W/m^2/sr/m */
- if(htrdr->wlen_range_m[0] != htrdr->wlen_range_m[1]) { /* !monochromatic */
- radiance_avg /= (htrdr->wlen_range_m[1] - htrdr->wlen_range_m[0]);
- }
-
- res = brightness_temperature
- (htrdr,
- htrdr->wlen_range_m[0],
- htrdr->wlen_range_m[1],
- radiance_avg,
- &temperature);
- if(res != RES_OK) {
- htrdr_log_warn(htrdr,
- "Could not compute the brightness temperature for the radiance %g.\n",
- radiance_avg);
- temperature = 0;
- }
- return temperature;
-}
-
-static void
-draw_pixel_xwave
- (struct htrdr* htrdr,
- const size_t ithread,
- const size_t ipix[2],
- const double pix_sz[2], /* Size of a pixel in the normalized image plane */
- const struct htrdr_sensor* sensor,
- const size_t spp,
- struct ssp_rng* rng,
- struct htrdr_pixel_xwave* pixel)
-{
- struct htrdr_accum radiance;
- struct htrdr_accum time;
- size_t isamp;
- double temp_min, temp_max;
- ASSERT(ipix && ipix && pix_sz && sensor && rng && pixel);
- ASSERT(sensor->type == HTRDR_SENSOR_CAMERA);
- ASSERT(htrdr->spectral_type == HTRDR_SPECTRAL_LW
- || htrdr->spectral_type == HTRDR_SPECTRAL_SW);
-
- /* Reset the pixel accumulators */
- radiance = HTRDR_ACCUM_NULL;
- time = HTRDR_ACCUM_NULL;
-
- FOR_EACH(isamp, 0, spp) {
- struct time t0, t1;
- double ray_org[3];
- double ray_dir[3];
- double weight;
- double r0, r1, r2;
- double wlen;
- size_t iband;
- size_t iquad;
- double usec;
- double band_pdf;
- res_T res = RES_OK;
-
- /* Begin the registration of the time spent in the realisation */
- time_current(&t0);
-
- res = htrdr_sensor_sample_primary_ray(sensor, htrdr, ipix,
- pix_sz, rng, ray_org, ray_dir);
- if(res != RES_OK) continue; /* Reject the current sample */
-
- r0 = ssp_rng_canonical(rng);
- r1 = ssp_rng_canonical(rng);
- r2 = ssp_rng_canonical(rng);
-
- /* Sample a wavelength */
- wlen = htrdr_ran_wlen_sample(htrdr->ran_wlen, r0, r1, &band_pdf);
-
- /* Select the associated band and sample a quadrature point */
- iband = htsky_find_spectral_band(htrdr->sky, wlen);
- iquad = htsky_spectral_band_sample_quadrature(htrdr->sky, r2, iband);
-
- /* Compute the spectral radiance in W/m^2/sr/m */
- switch(htrdr->spectral_type) {
- case HTRDR_SPECTRAL_LW:
- weight = htrdr_compute_radiance_lw(htrdr, ithread, rng, ray_org,
- ray_dir, wlen, iband, iquad);
- break;
- case HTRDR_SPECTRAL_SW:
- weight = htrdr_compute_radiance_sw(htrdr, ithread, rng,
- HTRDR_RADIANCE_ALL, ray_org, ray_dir, wlen, iband, iquad);
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- ASSERT(weight >= 0);
- /* Importance sampling: correct weight with pdf */
- weight /= band_pdf; /* In W/m^2/sr */
-
- /* End the registration of the per realisation time */
- time_sub(&t0, time_current(&t1), &t0);
- usec = (double)time_val(&t0, TIME_NSEC) * 0.001;
-
- /* Update the pixel accumulator of the current channel */
- radiance.sum_weights += weight;
- radiance.sum_weights_sqr += weight*weight;
- radiance.nweights += 1;
-
- /* Update the pixel accumulator of per realisation time */
- time.sum_weights += usec;
- time.sum_weights_sqr += usec*usec;
- time.nweights += 1;
- }
-
- /* Compute the estimation of the pixel radiance */
- htrdr_accum_get_estimation(&radiance, &pixel->radiance);
-
- /* Save the per realisation integration time */
- pixel->time = time;
-
- /* Compute the brightness_temperature of the pixel and estimate its standard
- * error if the sources were in the medium (<=> longwave) */
- if(htrdr->spectral_type == HTRDR_SPECTRAL_LW) {
- pixel->radiance_temperature.E = radiance_temperature(htrdr, pixel->radiance.E);
- temp_min = radiance_temperature(htrdr, pixel->radiance.E - pixel->radiance.SE);
- temp_max = radiance_temperature(htrdr, pixel->radiance.E + pixel->radiance.SE);
- pixel->radiance_temperature.SE = temp_max - temp_min;
- }
-}
-
-static res_T
-draw_tile
- (struct htrdr* htrdr,
- const size_t ithread,
- const int64_t tile_mcode, /* For debug only */
- const size_t tile_org[2], /* Origin of the tile in pixel space */
- const size_t tile_sz[2], /* Definition of the tile */
- const double pix_sz[2], /* Size of a pixel in the normalized image plane */
- const struct htrdr_sensor* sensor,
- const size_t spp, /* #samples per pixel */
- struct ssp_rng* rng,
- struct tile* tile)
-{
- size_t npixels;
- size_t mcode; /* Morton code of tile pixel */
- ASSERT(htrdr && tile_org && tile_sz && pix_sz && sensor && spp && tile);
- (void)tile_mcode;
- /* Adjust the #pixels to process them wrt a morton order */
- npixels = round_up_pow2(MMAX(tile_sz[0], tile_sz[1]));
- npixels *= npixels;
-
- FOR_EACH(mcode, 0, npixels) {
- union pixel* pixel;
- size_t ipix_tile[2]; /* Pixel coord in the tile */
- size_t ipix[2]; /* Pixel coord in the buffer */
-
- ipix_tile[0] = morton2D_decode((uint32_t)(mcode>>0));
- if(ipix_tile[0] >= tile_sz[0]) continue; /* Pixel is out of tile */
- ipix_tile[1] = morton2D_decode((uint32_t)(mcode>>1));
- if(ipix_tile[1] >= tile_sz[1]) continue; /* Pixel is out of tile */
-
- /* Fetch and reset the pixel accumulator */
- pixel = tile_at(tile, ipix_tile[0], ipix_tile[1]);
-
- /* Compute the pixel coordinate */
- ipix[0] = tile_org[0] + ipix_tile[0];
- ipix[1] = tile_org[1] + ipix_tile[1];
-
- /* Draw the pixel */
- switch(sensor->type) {
- case HTRDR_SENSOR_RECTANGLE:
- draw_pixel_flux
- (htrdr, ithread, ipix, pix_sz, sensor, spp, rng, &pixel->flux);
- break;
- case HTRDR_SENSOR_CAMERA:
- switch(htrdr->spectral_type) {
- case HTRDR_SPECTRAL_LW:
- case HTRDR_SPECTRAL_SW:
- draw_pixel_xwave
- (htrdr, ithread, ipix, pix_sz, sensor, spp, rng, &pixel->xwave);
- break;
- case HTRDR_SPECTRAL_SW_CIE_XYZ:
- draw_pixel_image
- (htrdr, ithread, ipix, pix_sz, sensor->camera, spp, rng, &pixel->image);
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- }
- return RES_OK;
-}
-
-static res_T
-draw_image
- (struct htrdr* htrdr,
- const struct htrdr_sensor* sensor,
- const size_t width, /* Image width */
- const size_t height, /* Image height */
- const size_t spp,
- const size_t ntiles_x,
- const size_t ntiles_y,
- const size_t ntiles_adjusted,
- const double pix_sz[2], /* Pixel size in the normalized image plane */
- struct proc_work* work,
- struct list_node* tiles)
-{
- struct ssp_rng* rng_proc = NULL;
- size_t nthreads = 0;
- size_t nthieves = 0;
- size_t proc_ntiles = 0;
- enum pixel_format pixfmt;
- ATOMIC nsolved_tiles = 0;
- ATOMIC res = RES_OK;
- ASSERT(htrdr && sensor && spp && ntiles_adjusted && work && tiles);
- ASSERT(pix_sz && pix_sz[0] > 0 && pix_sz[1] > 0);
- ASSERT(width && height);
- (void)ntiles_x, (void)ntiles_y;
-
- res = ssp_rng_create(htrdr->allocator, &ssp_rng_mt19937_64, &rng_proc);
- if(res != RES_OK) {
- htrdr_log_err(htrdr, "could not create the RNG used to sample a process "
- "to steal -- %s.\n", res_to_cstr((res_T)res));
- goto error;
- }
-
- proc_ntiles = proc_work_get_ntiles(work);
- nthreads = MMIN(htrdr->nthreads, proc_ntiles);
-
- /* The process is not considered as a working process for himself */
- htrdr->mpi_working_procs[htrdr->mpi_rank] = 0;
- --htrdr->mpi_nworking_procs;
-
- pixfmt = spectral_type_to_pixfmt(htrdr->spectral_type);
-
- omp_set_num_threads((int)nthreads);
- #pragma omp parallel
- for(;;) {
- const int ithread = omp_get_thread_num();
- struct ssp_rng_proxy* rng_proxy = NULL;
- struct ssp_rng* rng;
- struct tile* tile;
- uint32_t mcode = TILE_MCODE_NULL;
- size_t tile_org[2];
- size_t tile_sz[2];
- size_t n;
- res_T res_local = RES_OK;
- int32_t pcent;
-
- /* Get a tile to draw */
- #pragma omp critical
- {
- mcode = proc_work_get_tile(work);
- if(mcode == TILE_MCODE_NULL) { /* No more work on this process */
- /* Try to steal works to concurrent processes */
- proc_work_reset(work);
- nthieves = mpi_steal_work(htrdr, rng_proc, work);
- if(nthieves != 0) {
- mcode = proc_work_get_tile(work);
- }
- }
- }
- if(mcode == TILE_MCODE_NULL) break; /* No more work */
-
- /* Decode the morton code to retrieve the tile index */
- tile_org[0] = morton2D_decode((uint32_t)(mcode>>0));
- tile_org[1] = morton2D_decode((uint32_t)(mcode>>1));
- ASSERT(tile_org[0] < ntiles_x && tile_org[1] < ntiles_y);
-
- /* Create the tile */
- tile = tile_create(htrdr->allocator, pixfmt);
- if(!tile) {
- ATOMIC_SET(&res, RES_MEM_ERR);
- htrdr_log_err(htrdr,
- "could not allocate the memory space of the tile (%lu, %lu) -- %s.\n",
- (unsigned long)tile_org[0], (unsigned long)tile_org[1],
- res_to_cstr((res_T)ATOMIC_GET(&res)));
- break;
- }
-
- /* Register the tile */
- #pragma omp critical
- list_add_tail(tiles, &tile->node);
-
- tile->data.x = (uint16_t)tile_org[0];
- tile->data.y = (uint16_t)tile_org[1];
-
- /* Define the tile origin in pixel space */
- tile_org[0] *= TILE_SIZE;
- tile_org[1] *= TILE_SIZE;
-
- /* Compute the size of the tile clamped by the borders of the buffer */
- tile_sz[0] = MMIN(TILE_SIZE, width - tile_org[0]);
- tile_sz[1] = MMIN(TILE_SIZE, height - tile_org[1]);
-
- /* Create a proxy RNG for the current tile. This proxy is used for the
- * current thread only and thus it has to manage only one RNG. This proxy
- * is initialised in order to ensure that an unique and predictable set of
- * random numbers is used for the current tile. */
- SSP(rng_proxy_create2
- (&htrdr->lifo_allocators[ithread],
- &ssp_rng_threefry,
- RNG_SEQUENCE_SIZE * (size_t)mcode, /* Offset */
- RNG_SEQUENCE_SIZE, /* Size */
- RNG_SEQUENCE_SIZE * (size_t)ntiles_adjusted, /* Pitch */
- 1, &rng_proxy));
- SSP(rng_proxy_create_rng(rng_proxy, 0, &rng));
-
- /* Launch the tile rendering */
- res_local = draw_tile(htrdr, (size_t)ithread, mcode, tile_org, tile_sz,
- pix_sz, sensor, spp, rng, tile);
-
- SSP(rng_proxy_ref_put(rng_proxy));
- SSP(rng_ref_put(rng));
-
- if(res_local != RES_OK) {
- ATOMIC_SET(&res, res_local);
- break;
- }
-
- /* Update the progress status */
- n = (size_t)ATOMIC_INCR(&nsolved_tiles);
- pcent = (int32_t)((double)n * 100.0 / (double)proc_ntiles + 0.5/*round*/);
-
- #pragma omp critical
- if(pcent > htrdr->mpi_progress_render[0]) {
- htrdr->mpi_progress_render[0] = pcent;
- if(htrdr->mpi_rank == 0) {
- update_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING);
- } else { /* Send the progress percentage to the master process */
- send_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING, pcent);
- }
- }
- }
-
- if(ATOMIC_GET(&res) != RES_OK) goto error;
-
- /* Asynchronously wait for processes completion. Use an asynchronous barrier to
- * avoid a dead lock with the `mpi_probe_thieves' thread that requires also
- * the `mpi_mutex'. */
- {
- MPI_Request req;
-
- mutex_lock(htrdr->mpi_mutex);
- MPI(Ibarrier(MPI_COMM_WORLD, &req));
- mutex_unlock(htrdr->mpi_mutex);
-
- mpi_wait_for_request(htrdr, &req);
- }
-
-exit:
- if(rng_proc) SSP(rng_ref_put(rng_proc));
- return (res_T)res;
-error:
- goto exit;
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_draw_map
- (struct htrdr* htrdr,
- const struct htrdr_sensor* sensor,
- const size_t width,
- const size_t height,
- const size_t spp,
- struct htrdr_buffer* buf)
-{
- char strbuf[128];
- struct time t0, t1;
- struct list_node tiles;
- size_t ntiles_x, ntiles_y, ntiles, ntiles_adjusted;
- size_t itile;
- struct proc_work work;
- struct htrdr_buffer_layout layout = HTRDR_BUFFER_LAYOUT_NULL;
- size_t proc_ntiles_adjusted;
- double pix_sz[2];
- ATOMIC probe_thieves = 1;
- ATOMIC res = RES_OK;
- ASSERT(htrdr && sensor && width && height);
- ASSERT(htrdr->mpi_rank != 0 || buf);
-
- list_init(&tiles);
- proc_work_init(htrdr->allocator, &work);
-
- if(htrdr->mpi_rank == 0) {
- const size_t pixsz = htrdr_spectral_type_get_pixsz
- (htrdr->spectral_type, sensor->type);
- const size_t pixal = htrdr_spectral_type_get_pixal
- (htrdr->spectral_type, sensor->type);
-
- htrdr_buffer_get_layout(buf, &layout);
- ASSERT(layout.width || layout.height || layout.elmt_size);
- ASSERT(layout.width == width && layout.height == height);
-
- if(layout.elmt_size != pixsz || layout.alignment < pixal) {
- htrdr_log_err(htrdr, "%s: invalid buffer layout.\n", FUNC_NAME);
- res = RES_BAD_ARG;
- goto error;
- }
- }
-
- /* Compute the overall number of tiles */
- ntiles_x = (width + (TILE_SIZE-1)/*ceil*/)/TILE_SIZE;
- ntiles_y = (height+ (TILE_SIZE-1)/*ceil*/)/TILE_SIZE;
- ntiles = ntiles_x * ntiles_y;
-
- /* Compute the pixel size in the normalized image plane */
- pix_sz[0] = 1.0 / (double)width;
- pix_sz[1] = 1.0 / (double)height;
-
- /* Adjust the #tiles for the morton-encoding procedure */
- ntiles_adjusted = round_up_pow2(MMAX(ntiles_x, ntiles_y));
- ntiles_adjusted *= ntiles_adjusted;
-
- /* Define the initial number of tiles of the current process */
- proc_ntiles_adjusted = ntiles_adjusted / (size_t)htrdr->mpi_nprocs;
- if(htrdr->mpi_rank == 0) { /* Affect the remaining tiles to the master proc */
- proc_ntiles_adjusted +=
- ntiles_adjusted - proc_ntiles_adjusted*(size_t)htrdr->mpi_nprocs;
- }
-
- /* Define the initial list of tiles of the process */
- FOR_EACH(itile, 0, proc_ntiles_adjusted) {
- uint32_t mcode;
- uint16_t tile_org[2];
-
- mcode = (uint32_t)itile*(uint32_t)htrdr->mpi_nprocs
- + (uint32_t)htrdr->mpi_rank;
-
- tile_org[0] = morton2D_decode(mcode>>0);
- if(tile_org[0] >= ntiles_x) continue;
- tile_org[1] = morton2D_decode(mcode>>1);
- if(tile_org[1] >= ntiles_y) continue;
- proc_work_add_tile(&work, mcode);
- }
-
- if(htrdr->mpi_rank == 0) {
- fetch_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING);
- print_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING);
- }
-
- time_current(&t0);
-
- omp_set_nested(1); /* Enable nested threads for draw_image */
- #pragma omp parallel sections num_threads(2)
- {
- #pragma omp section
- mpi_probe_thieves(htrdr, &work, &probe_thieves);
-
- #pragma omp section
- {
- draw_image(htrdr, sensor, width, height, spp, ntiles_x, ntiles_y,
- ntiles_adjusted, pix_sz, &work, &tiles);
- /* The processes have no more work to do. Stop probing for thieves */
- ATOMIC_SET(&probe_thieves, 0);
- }
- }
-
- if(htrdr->mpi_rank == 0) {
- update_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING);
- fprintf(stderr, "\n"); /* Add a new line after the progress statuses */
- }
-
- time_sub(&t0, time_current(&t1), &t0);
- time_dump(&t0, TIME_ALL, NULL, strbuf, sizeof(strbuf));
- htrdr_log(htrdr, "Rendering time: %s\n", strbuf);
-
- /* Gather accum buffers from the group of processes */
- time_current(&t0);
- res = mpi_gather_tiles(htrdr, sensor, buf, ntiles, &tiles);
- if(res != RES_OK) goto error;
- time_sub(&t0, time_current(&t1), &t0);
- time_dump(&t0, TIME_ALL, NULL, strbuf, sizeof(strbuf));
- htrdr_log(htrdr, "Image gathering time: %s\n", strbuf);
-
-exit:
- { /* Free allocated tiles */
- struct list_node* node;
- struct list_node* tmp;
- LIST_FOR_EACH_SAFE(node, tmp, &tiles) {
- struct tile* tile = CONTAINER_OF(node, struct tile, node);
- list_del(node);
- tile_ref_put(tile);
- }
- }
- proc_work_release(&work);
- return (res_T)res;
-error:
- goto exit;
-}
-
diff --git a/src/htrdr_grid.c b/src/htrdr_grid.c
@@ -1,366 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#define _POSIX_C_SOURCE 200809L /* mmap support */
-#define _DEFAULT_SOURCE 1 /* MAP_POPULATE support */
-#define _BSD_SOURCE 1 /* MAP_POPULATE for glibc < 2.19 */
-
-#include "htrdr.h"
-#include "htrdr_c.h"
-#include "htrdr_grid.h"
-
-#include <rsys/mem_allocator.h>
-#include <rsys/ref_count.h>
-
-#include <errno.h>
-#include <sys/mman.h> /* mmap/munmap */
-#include <fcntl.h>
-#include <unistd.h> /* sysconf */
-
-const int32_t GRID_VERSION = 0;
-const int32_t GRID_VERSION_NONE = -1;
-
-struct htrdr_grid {
- FILE* fp;
- char* data; /* Mapped data */
- size_t definition[3]; /* Submitted definition */
- size_t def_adjusted; /* Adjusted definition along the 3 dimensions */
- size_t cell_sz; /* Size in bytes of a grid cell */
- size_t pagesize; /* Page size in bytes */
- size_t data_sz; /* Size in bytes of the overall grid data + padding */
-
- ref_T ref;
- struct htrdr* htrdr;
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static void
-grid_release(ref_T* ref)
-{
- struct htrdr_grid* grid;
- ASSERT(ref);
- grid = CONTAINER_OF(ref, struct htrdr_grid, ref);
- if(grid->fp) {
- rewind(grid->fp);
- CHK(fwrite(&GRID_VERSION, sizeof(int), 1, grid->fp) == 1);
- fclose(grid->fp);
- }
- if(grid->data) {
- if(munmap(grid->data, grid->data_sz)) {
- htrdr_log_err(grid->htrdr, "error unmapping the grid data -- %s.\n",
- strerror(errno));
- ASSERT(0);
- }
- }
- MEM_RM(grid->htrdr->allocator, grid);
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_grid_create
- (struct htrdr* htrdr,
- const size_t definition[3],
- const size_t sizeof_cell, /* Size of an cell in Bytes */
- const char* filename,
- const int force_overwrite,
- struct htrdr_grid** out_grid)
-{
- const char byte = 0;
- struct htrdr_grid* grid = NULL;
- size_t mcode_max;
- long grid_offset;
- int n;
- res_T res = RES_OK;
- ASSERT(htrdr && out_grid && filename && definition);
-
- if(!definition[0] || !definition[1] || !definition[2]) {
- htrdr_log_err(htrdr, "%s: invalid definition [%lu, %lu, %lu].\n", FUNC_NAME,
- (unsigned long)definition[0],
- (unsigned long)definition[1],
- (unsigned long)definition[2]);
- res = RES_BAD_ARG;
- goto error;
- }
-
- if(!sizeof_cell) {
- htrdr_log_err(htrdr, "%s: invalid cell size `%lu'.\n", FUNC_NAME,
- (unsigned long)sizeof_cell);
- res = RES_BAD_ARG;
- goto error;
- }
-
- grid = MEM_CALLOC(htrdr->allocator, 1, sizeof(*grid));
- if(!grid) {
- res = RES_MEM_ERR;
- goto error;
- }
- ref_init(&grid->ref);
- grid->definition[0] = definition[0];
- grid->definition[1] = definition[1];
- grid->definition[2] = definition[2];
- grid->cell_sz = sizeof_cell;
- grid->pagesize = (size_t)sysconf(_SC_PAGESIZE);
- grid->htrdr = htrdr;
-
- res = open_output_stream(htrdr, filename, 1, force_overwrite, &grid->fp);
- if(res != RES_OK) goto error;
-
- #define WRITE(Var, N, Name) { \
- if(fwrite((Var), sizeof(*(Var)), (N), grid->fp) != (N)) { \
- htrdr_log_err(htrdr, "%s:%s: could not write `%s' -- %s.\n", \
- FUNC_NAME, filename, (Name), strerror(errno)); \
- res = RES_IO_ERR; \
- goto error; \
- } \
- } (void)0
- WRITE(&GRID_VERSION_NONE, 1, "version");
- WRITE(&grid->pagesize, 1, "pagesize");
- WRITE(&grid->cell_sz, 1, "cell_sz");
- WRITE(grid->definition, 3, "definition");
- WRITE(grid->definition, 3, "definition");
-
- /* Align the grid data on pagesize */
- n = fseek
- (grid->fp, ALIGN_SIZE(ftell(grid->fp),(off_t)grid->pagesize), SEEK_SET);
- if(n < 0) {
- htrdr_log_err(htrdr,
- "%s:%s: could not align the grid data on page size -- %s.\n",
- FUNC_NAME, filename, strerror(errno));
- res = RES_IO_ERR;
- goto error;
- }
-
- /* Adjust the grid definition in order to sort its data wrt the morton code
- * of its voxel */
- grid->def_adjusted = MMAX(MMAX(definition[0], definition[1]), definition[2]);
- grid->def_adjusted = round_up_pow2(grid->def_adjusted);
- mcode_max = grid->def_adjusted*grid->def_adjusted*grid->def_adjusted;
-
- /* Define the grid size */
- grid->data_sz = mcode_max * sizeof_cell;
- grid->data_sz = ALIGN_SIZE(grid->data_sz, grid->pagesize);
-
- /* Save the position of the grid data into the file */
- grid_offset = ftell(grid->fp);
-
- /* Reserve the space for the grid data */
- n = fseek(grid->fp, (long)grid->data_sz, SEEK_CUR);
- if(n < 0) {
- htrdr_log_err(htrdr,
- "%s:%s: could reserve the space to store the grid -- %s.\n", FUNC_NAME,
- filename, strerror(errno));
- res = RES_IO_ERR;
- goto error;
- }
-
- /* Write one char at the end of the file to position the EOF indicator */
- CHK(fseek(grid->fp, -1, SEEK_CUR) != -1);
- WRITE(&byte, 1, "Dummy Byte");
- #undef WRITE
-
- /* Avoid to be positionned on the EOF */
- rewind(grid->fp);
-
- /* Map the grid data */
- grid->data = mmap(NULL, grid->data_sz, PROT_READ|PROT_WRITE,
- MAP_SHARED|MAP_POPULATE, fileno(grid->fp), grid_offset);
-
- if(grid->data == MAP_FAILED) {
- htrdr_log_err(htrdr, "%s:%s: could not map the grid data -- %s.\n",
- FUNC_NAME, filename, strerror(errno));
- grid->data = NULL;
- res = RES_IO_ERR;
- goto error;
- }
-
-exit:
- *out_grid = grid;
- return res;
-error:
- if(grid) {
- htrdr_grid_ref_put(grid);
- grid = NULL;
- }
- goto exit;
-}
-
-res_T
-htrdr_grid_open
- (struct htrdr* htrdr,
- const char* filename,
- struct htrdr_grid** out_grid)
-{
- struct htrdr_grid* grid = NULL;
- size_t grid_offset;
- size_t pagesize;
- size_t mcode_max;
- int32_t version;
- int fd = -1;
- res_T res = RES_OK;
- ASSERT(htrdr && filename && out_grid);
-
- grid = MEM_CALLOC(htrdr->allocator, 1, sizeof(*grid));
- if(!grid) {
- res = RES_MEM_ERR;
- goto error;
- }
- ref_init(&grid->ref);
- grid->pagesize = (size_t)sysconf(_SC_PAGESIZE);
- grid->htrdr = htrdr;
-
- fd = open(filename, O_RDWR, 0);
- if(fd < 0) {
- htrdr_log_err(htrdr, "%s: could not open `%s' -- %s.\n", FUNC_NAME,
- filename, strerror(errno));
- res = RES_IO_ERR;
- goto error;
- }
- CHK(grid->fp = fdopen(fd, "w+"));
-
- #define READ(Var, N, Name) { \
- if(fread((Var), sizeof(*(Var)), (N), grid->fp) != (N)) { \
- htrdr_log_err(htrdr, "%s:%s: could not read `%s'.\n", \
- FUNC_NAME, filename, Name); \
- res = RES_IO_ERR; \
- goto error; \
- } \
- } (void)0
- READ(&version, 1, "version");
- if(version != GRID_VERSION) {
- htrdr_log_err(htrdr, "%s:%s: incompatible grid version. Loaded version is "
- "'%i' while the current version is '%i'.\n",
- FUNC_NAME, filename, version, GRID_VERSION);
- res = RES_BAD_ARG;
- goto error;
- }
-
- READ(&pagesize, 1, "pagesize");
- if(pagesize != grid->pagesize) {
- htrdr_log_err(htrdr, "%s:%s: invalid pagesize `%lu'.\n", FUNC_NAME,
- filename, (unsigned long)pagesize);
- res = RES_BAD_ARG;
- goto error;
- }
-
- READ(&grid->cell_sz, 1, "sizeof_cell");
- if(grid->cell_sz == 0) {
- htrdr_log_err(htrdr, "%s:%s: invalid cell size `%lu'.\n", FUNC_NAME,
- filename, (unsigned long)grid->cell_sz);
- res = RES_BAD_ARG;
- goto error;
- }
-
- READ(grid->definition, 3, "definition");
- if(!grid->definition[0] || !grid->definition[1] || !grid->definition[2]) {
- htrdr_log_err(htrdr, "%s:%s: invalid definition [%lu, %lu, %lu].\n",
- FUNC_NAME, filename,
- (unsigned long)grid->definition[0],
- (unsigned long)grid->definition[1],
- (unsigned long)grid->definition[2]);
- res = RES_BAD_ARG;
- goto error;
- }
- #undef READ
-
- grid_offset = ALIGN_SIZE((size_t)ftell(grid->fp), grid->pagesize);
- grid->def_adjusted = MMAX(grid->definition[0], grid->definition[1]);
- grid->def_adjusted = MMAX(grid->definition[2], grid->def_adjusted);
- grid->def_adjusted = round_up_pow2(grid->def_adjusted);
- mcode_max = grid->def_adjusted*grid->def_adjusted*grid->def_adjusted;
-
- /* Define the grid size */
- grid->data_sz = mcode_max * grid->cell_sz;
- grid->data_sz = ALIGN_SIZE(grid->data_sz, grid->pagesize);
-
- grid->data = mmap(NULL, grid->data_sz, PROT_READ|PROT_WRITE,
- MAP_SHARED|MAP_POPULATE, fileno(grid->fp), (off_t)grid_offset);
-
- if(grid->data == MAP_FAILED) {
- htrdr_log_err(htrdr, "%s:%s: could not map the grid data -- %s.\n",
- FUNC_NAME, filename, strerror(errno));
- grid->data = NULL;
- res = RES_IO_ERR;
- goto error;
- }
-
- rewind(grid->fp);
- CHK(fwrite(&GRID_VERSION_NONE, sizeof(int), 1, grid->fp) == 1);
-
-exit:
- *out_grid = grid;
- return res;
-error:
- if(grid) {
- htrdr_grid_ref_put(grid);
- grid = NULL;
- }
- goto exit;
-}
-
-void
-htrdr_grid_ref_get(struct htrdr_grid* grid)
-{
- ASSERT(grid);
- ref_get(&grid->ref);
-}
-
-void
-htrdr_grid_ref_put(struct htrdr_grid* grid)
-{
- ASSERT(grid);
- ref_put(&grid->ref, grid_release);
-}
-
-void*
-htrdr_grid_at(struct htrdr_grid* grid, const size_t xyz[3])
-{
- uint32_t coords[3];
- uint64_t mcode;
- ASSERT(grid && xyz);
- ASSERT(xyz[0] < grid->definition[0]);
- ASSERT(xyz[1] < grid->definition[1]);
- ASSERT(xyz[2] < grid->definition[2]);
- coords[0] = (uint32_t)xyz[0];
- coords[1] = (uint32_t)xyz[1];
- coords[2] = (uint32_t)xyz[2];
- mcode = morton_xyz_encode_u21(coords);
- return htrdr_grid_at_mcode(grid, mcode);
-}
-
-void*
-htrdr_grid_at_mcode(struct htrdr_grid* grid, const uint64_t mcode)
-{
- ASSERT(grid);
- ASSERT(mcode < grid->def_adjusted*grid->def_adjusted*grid->def_adjusted);
- ASSERT(morton3D_decode_u21(mcode>>2) < grid->definition[0]);
- ASSERT(morton3D_decode_u21(mcode>>1) < grid->definition[1]);
- ASSERT(morton3D_decode_u21(mcode>>0) < grid->definition[2]);
- return grid->data + mcode*grid->cell_sz;
-}
-
-void
-htrdr_grid_get_definition(struct htrdr_grid* grid, size_t definition[3])
-{
- ASSERT(grid && definition);
- definition[0] = grid->definition[0];
- definition[1] = grid->definition[1];
- definition[2] = grid->definition[2];
-}
-
diff --git a/src/htrdr_grid.h b/src/htrdr_grid.h
@@ -1,71 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_GRID_H
-#define HTRDR_GRID_H
-
-#include <rsys/rsys.h>
-
-/* Forwared declarations */
-struct htrdr;
-struct htrdr_grid;
-
-/*******************************************************************************
- * Out of core regular grid
- ******************************************************************************/
-extern LOCAL_SYM res_T
-htrdr_grid_create
- (struct htrdr* htrdr,
- const size_t definition[3], /* #voxels in X, Y and Z */
- const size_t sizeof_cell, /* Size of an cell in Bytes */
- const char* filename, /* Filename where the grid data are stored */
- const int force_overwrite, /* Force the overwrite of the grid data */
- struct htrdr_grid** grid);
-
-extern LOCAL_SYM res_T
-htrdr_grid_open
- (struct htrdr* htrdr,
- const char* filename,
- struct htrdr_grid** grid);
-
-extern LOCAL_SYM void
-htrdr_grid_ref_get
- (struct htrdr_grid* grid);
-
-extern LOCAL_SYM void
-htrdr_grid_ref_put
- (struct htrdr_grid* grid);
-
-/* Fetch the grid data from its 3D index */
-extern LOCAL_SYM void*
-htrdr_grid_at
- (struct htrdr_grid* grid,
- const size_t xyz[3]);
-
-/* Retrieve the voxel data from its morton code. The morton code is computed
- * from the 3D indices following the morton_xyz_encode_u21 convention. */
-extern LOCAL_SYM void*
-htrdr_grid_at_mcode
- (struct htrdr_grid* grid,
- const uint64_t mcode);
-
-extern LOCAL_SYM void
-htrdr_grid_get_definition
- (struct htrdr_grid* grid,
- size_t definition[3]);
-
-#endif /* HTRDR_GRID_H */
-
diff --git a/src/htrdr_ground.c b/src/htrdr_ground.c
@@ -1,753 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#define _POSIX_C_SOURCE 200112L /* strtok_r support */
-
-#include "htrdr.h"
-#include "htrdr_interface.h"
-#include "htrdr_ground.h"
-#include "htrdr_materials.h"
-#include "htrdr_slab.h"
-
-#include <aw.h>
-#include <rsys/clock_time.h>
-#include <rsys/cstr.h>
-#include <rsys/dynamic_array_double.h>
-#include <rsys/dynamic_array_size_t.h>
-#include <rsys/double2.h>
-#include <rsys/double3.h>
-#include <rsys/float2.h>
-#include <rsys/float3.h>
-#include <rsys/hash_table.h>
-
-#include <star/s3d.h>
-
-#include <string.h> /* strtok_r */
-
-/* Define the hash table that maps an Obj vertex id to its position into the
- * vertex buffer */
-#define HTABLE_NAME vertex
-#define HTABLE_KEY size_t /* Obj vertex id */
-#define HTABLE_DATA size_t
-#include <rsys/hash_table.h>
-
-/* Define the hash table that maps the Star-3D shape id to its interface */
-#define HTABLE_NAME interface
-#define HTABLE_KEY unsigned /* Star-3D shape id */
-#define HTABLE_DATA struct htrdr_interface
-#include <rsys/hash_table.h>
-
-struct mesh {
- const struct darray_double* positions;
- const struct darray_size_t* indices;
-};
-static const struct mesh MESH_NULL;
-
-struct ray_context {
- float range[2];
- struct s3d_hit hit_prev;
- int id[2];
-};
-#define RAY_CONTEXT_NULL__ {{0,INF}, S3D_HIT_NULL__, {0,0}}
-static const struct ray_context RAY_CONTEXT_NULL = RAY_CONTEXT_NULL__;
-
-struct trace_ground_context {
- struct s3d_scene_view* view;
- struct ray_context context;
- struct s3d_hit* hit;
-};
-static const struct trace_ground_context TRACE_GROUND_CONTEXT_NULL = {
- NULL, RAY_CONTEXT_NULL__, NULL
-};
-
-struct htrdr_ground {
- struct s3d_scene_view* view;
- float lower[3]; /* Ground lower bound */
- float upper[3]; /* Ground upper bound */
- int repeat; /* Make the ground infinite in X and Y */
-
- struct htable_interface interfaces; /* Map a Star3D shape to its interface */
-
- struct htrdr* htrdr;
- ref_T ref;
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-/* Check that `hit' roughly lies on an edge. For triangular primitives, a
- * simple but approximative way is to test that its position have at least one
- * barycentric coordinate roughly equal to 0 or 1. */
-static FINLINE int
-hit_on_edge(const struct s3d_hit* hit)
-{
- const float on_edge_eps = 1.e-4f;
- float w;
- ASSERT(hit && !S3D_HIT_NONE(hit));
- w = 1.f - hit->uv[0] - hit->uv[1];
- return eq_epsf(hit->uv[0], 0.f, on_edge_eps)
- || eq_epsf(hit->uv[0], 1.f, on_edge_eps)
- || eq_epsf(hit->uv[1], 0.f, on_edge_eps)
- || eq_epsf(hit->uv[1], 1.f, on_edge_eps)
- || eq_epsf(w, 0.f, on_edge_eps)
- || eq_epsf(w, 1.f, on_edge_eps);
-}
-
-static int
-ground_filter
- (const struct s3d_hit* hit,
- const float ray_org[3],
- const float ray_dir[3],
- void* ray_data,
- void* filter_data)
-{
- const struct ray_context* ray_ctx = ray_data;
- (void)ray_org, (void)ray_dir, (void)filter_data;
-
- if(!ray_ctx) return 0;
-
- if(S3D_PRIMITIVE_EQ(&hit->prim, &ray_ctx->hit_prev.prim)) return 1;
-
- if(!S3D_HIT_NONE(&ray_ctx->hit_prev) && eq_epsf(hit->distance, 0, 1.e-1f)) {
- /* If the targeted point is near of the origin, check that it lies on an
- * edge/vertex shared by the 2 primitives. */
- return hit_on_edge(&ray_ctx->hit_prev) && hit_on_edge(hit);
- }
-
- return hit->distance <= ray_ctx->range[0]
- || hit->distance >= ray_ctx->range[1];
-}
-
-static INLINE res_T
-trace_ground
- (const double org[3],
- const double dir[3],
- const double range[2],
- void* context,
- int* hit)
-{
- struct trace_ground_context* ctx = context;
- float ray_org[3];
- float ray_dir[3];
- float ray_range[2];
- res_T res = RES_OK;
- ASSERT(org && dir && range && context && hit);
-
- f3_set_d3(ray_org, org);
- f3_set_d3(ray_dir, dir);
- f2_set_d2(ray_range, range);
-
- res = s3d_scene_view_trace_ray
- (ctx->view, ray_org, ray_dir, ray_range, &ctx->context, ctx->hit);
- if(res != RES_OK) return res;
-
- *hit = !S3D_HIT_NONE(ctx->hit);
- return RES_OK;
-}
-
-static res_T
-parse_shape_interface
- (struct htrdr* htrdr,
- const char* name,
- struct htrdr_interface* interf)
-{
- struct str str;
- char* mtl_name0 = NULL;
- char* mtl_name1 = NULL;
- char* mtl_name2 = NULL;
- char* mtl_name_front = NULL;
- char* mtl_name_thin = NULL;
- char* mtl_name_back = NULL;
- char* tk_ctx = NULL;
- int has_front = 0;
- int has_thin = 0;
- int has_back = 0;
- res_T res = RES_OK;
- ASSERT(htrdr && name && interf);
-
- str_init(htrdr->allocator, &str);
-
- /* Locally copy the string to parse */
- res = str_set(&str, name);
- if(res != RES_OK) {
- htrdr_log_err(htrdr,
- "Could not locally copy the shape material string `%s' -- %s.\n",
- name, res_to_cstr(res));
- goto error;
- }
-
- /* Reset the interface */
- memset(interf, 0, sizeof(*interf));
-
- /* Parse the name of the front/back/thin materials */
- mtl_name0 = strtok_r(str_get(&str), ":", &tk_ctx);
- mtl_name1 = strtok_r(NULL, ":", &tk_ctx);
- mtl_name2 = strtok_r(NULL, ":", &tk_ctx);
- ASSERT(mtl_name0); /* This can't be NULL */
- mtl_name_front = mtl_name0;
- if(mtl_name2) {
- mtl_name_thin = mtl_name1;
- mtl_name_back = mtl_name2;
- } else {
- mtl_name_thin = NULL;
- mtl_name_back = mtl_name1;
- }
-
- if(!mtl_name_back) {
- htrdr_log_err(htrdr,
- "The back material name is missing `%s'.\n", name);
- res = RES_BAD_ARG;
- goto error;
- }
-
- /* Fetch the interface material if any */
- if(mtl_name_thin) {
- has_thin = htrdr_materials_find_mtl
- (htrdr->mats, mtl_name_thin, &interf->mtl_thin);
- if(!has_thin) {
- htrdr_log_err(htrdr,
- "Invalid interface `%s'. The interface material `%s' is unknown.\n",
- name, mtl_name_thin);
- res = RES_BAD_ARG;
- goto error;
- }
- }
-
- /* Fetch the front material */
- has_front = htrdr_materials_find_mtl
- (htrdr->mats, mtl_name_front, &interf->mtl_front);
- if(!has_front) {
- htrdr_log_err(htrdr,
- "Invalid interface `%s'. The front material `%s' is unknown.\n",
- name, mtl_name_front);
- res = RES_BAD_ARG;
- goto error;
- }
-
- /* Fetch the back material */
- has_back = htrdr_materials_find_mtl
- (htrdr->mats, mtl_name_back, &interf->mtl_back);
- if(!has_back) {
- htrdr_log_err(htrdr,
- "Invalid interface `%s'. The back material `%s' is unknown.\n",
- name, mtl_name_back);
- res = RES_BAD_ARG;
- goto error;
- }
-
-exit:
- str_release(&str);
- return res;
-error:
- *interf = HTRDR_INTERFACE_NULL;
- goto exit;
-}
-
-static res_T
-setup_mesh
- (struct htrdr* htrdr,
- const char* filename,
- struct aw_obj* obj,
- struct aw_obj_named_group* mtl,
- struct darray_double* positions,
- struct darray_size_t* indices,
- struct htable_vertex* vertices) /* Scratch data structure */
-{
- size_t iface;
- res_T res = RES_OK;
- ASSERT(htrdr && filename && obj && mtl && positions && indices && vertices);
-
- darray_double_clear(positions);
- darray_size_t_clear(indices);
- htable_vertex_clear(vertices);
-
- FOR_EACH(iface, mtl->face_id, mtl->face_id+mtl->faces_count) {
- struct aw_obj_face face;
- size_t ivertex;
-
- AW(obj_get_face(obj, iface, &face));
- if(face.vertices_count != 3) {
- htrdr_log_err(htrdr,
- "The obj `%s' has non-triangulated polygons "
- "while currently only triangular meshes are supported.\n",
- filename);
- res = RES_BAD_ARG;
- goto error;
- }
-
- FOR_EACH(ivertex, 0, face.vertices_count) {
- struct aw_obj_vertex vertex;
- size_t id;
- size_t* pid;
-
- AW(obj_get_vertex(obj, face.vertex_id + ivertex, &vertex));
- pid = htable_vertex_find(vertices, &vertex.position_id);
- if(pid) {
- id = *pid;
- } else {
- struct aw_obj_vertex_data vdata;
-
- id = darray_double_size_get(positions) / 3;
-
- res = darray_double_resize(positions, id*3 + 3);
- if(res != RES_OK) {
- htrdr_log_err(htrdr,
- "Could not locally copy the vertex position -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- AW(obj_get_vertex_data(obj, &vertex, &vdata));
- darray_double_data_get(positions)[id*3+0] = vdata.position[0];
- darray_double_data_get(positions)[id*3+1] = vdata.position[1];
- darray_double_data_get(positions)[id*3+2] = vdata.position[2];
-
- res = htable_vertex_set(vertices, &vertex.position_id, &id);
- if(res != RES_OK) {
- htrdr_log_err(htrdr,
- "Could not register the vertex position -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
- }
-
- res = darray_size_t_push_back(indices, &id);
- if(res != RES_OK) {
- htrdr_log_err(htrdr,
- "Could not locally copy the face index -- %s\n",
- res_to_cstr(res));
- goto error;
- }
- }
- }
-exit:
- return res;
-error:
- darray_double_clear(positions);
- darray_size_t_clear(indices);
- htable_vertex_clear(vertices);
- goto exit;
-}
-
-static void
-get_position(const unsigned ivert, float position[3], void* ctx)
-{
- const struct mesh* mesh = ctx;
- const double* pos = NULL;
- ASSERT(mesh);
- ASSERT(ivert < darray_double_size_get(mesh->positions) / 3);
- pos = darray_double_cdata_get(mesh->positions) + ivert*3;
- position[0] = (float)pos[0];
- position[1] = (float)pos[1];
- position[2] = (float)pos[2];
-}
-
-static void
-get_indices(const unsigned itri, unsigned indices[3], void* ctx)
-{
- const struct mesh* mesh = ctx;
- const size_t* ids = NULL;
- ASSERT(mesh);
- ASSERT(itri < darray_size_t_size_get(mesh->indices) / 3);
- ids = darray_size_t_cdata_get(mesh->indices) + itri*3;
- indices[0] = (unsigned)ids[0];
- indices[1] = (unsigned)ids[1];
- indices[2] = (unsigned)ids[2];
-}
-
-static res_T
-create_s3d_shape
- (struct htrdr* htrdr,
- const struct darray_double* positions,
- const struct darray_size_t* indices,
- struct s3d_shape** out_shape)
-{
- struct s3d_shape* shape = NULL;
- struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL;
- struct mesh mesh = MESH_NULL;
- res_T res = RES_OK;
- ASSERT(htrdr && positions && indices && out_shape);
- ASSERT(darray_double_size_get(positions) != 0);
- ASSERT(darray_size_t_size_get(indices) != 0);
- ASSERT(darray_double_size_get(positions)%3 == 0);
- ASSERT(darray_size_t_size_get(indices)%3 == 0);
-
- mesh.positions = positions;
- mesh.indices = indices;
-
- res = s3d_shape_create_mesh(htrdr->s3d, &shape);
- if(res != RES_OK) {
- htrdr_log_err(htrdr, "Error creating a Star-3D shape -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- vdata.usage = S3D_POSITION;
- vdata.type = S3D_FLOAT3;
- vdata.get = get_position;
-
- res = s3d_mesh_setup_indexed_vertices
- (shape, (unsigned int)(darray_size_t_size_get(indices)/3), get_indices,
- (unsigned int)(darray_double_size_get(positions)/3), &vdata, 1, &mesh);
- if(res != RES_OK){
- htrdr_log_err(htrdr, "Could not setup the Star-3D shape -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- res = s3d_mesh_set_hit_filter_function(shape, ground_filter, NULL);
- if(res != RES_OK) {
- htrdr_log_err(htrdr,
- "Could not setup the Star-3D hit filter function of the ground geometry "
- "-- %s.\n", res_to_cstr(res));
- goto error;
- }
-
-exit:
- *out_shape = shape;
- return res;
-error:
- if(shape) {
- S3D(shape_ref_put(shape));
- shape = NULL;
- }
- goto exit;
-}
-
-static res_T
-setup_ground(struct htrdr_ground* ground, const char* obj_filename)
-{
- struct aw_obj_desc desc;
- struct htable_vertex vertices;
- struct darray_double positions;
- struct darray_size_t indices;
- struct aw_obj* obj = NULL;
- struct s3d_shape* shape = NULL;
- struct s3d_scene* scene = NULL;
- size_t iusemtl;
-
- res_T res = RES_OK;
- ASSERT(obj_filename);
-
- htable_vertex_init(ground->htrdr->allocator, &vertices);
- darray_double_init(ground->htrdr->allocator, &positions);
- darray_size_t_init(ground->htrdr->allocator, &indices);
-
- res = aw_obj_create(&ground->htrdr->logger, ground->htrdr->allocator,
- ground->htrdr->verbose, &obj);
- if(res != RES_OK) {
- htrdr_log_err(ground->htrdr, "Could not create the obj loader -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- res = s3d_scene_create(ground->htrdr->s3d, &scene);
- if(res != RES_OK) {
- htrdr_log_err(ground->htrdr, "Could not create the Star-3D scene -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- /* Load the geometry data */
- res = aw_obj_load(obj, obj_filename);
- if(res != RES_OK) goto error;
-
- /* Fetch the descriptor of the loaded geometry */
- AW(obj_get_desc(obj, &desc));
-
- if(desc.usemtls_count == 0) {
- htrdr_log_err(ground->htrdr, "The obj `%s' has no material.\n", obj_filename);
- res = RES_BAD_ARG;
- goto error;
- }
-
- /* Setup the geometry */
- FOR_EACH(iusemtl, 0, desc.usemtls_count) {
- struct aw_obj_named_group mtl;
- struct htrdr_interface interf;
- unsigned ishape;
-
- AW(obj_get_mtl(obj, iusemtl , &mtl));
-
- res = parse_shape_interface(ground->htrdr, mtl.name, &interf);
- if(res != RES_OK) goto error;
-
- res = setup_mesh
- (ground->htrdr, obj_filename, obj, &mtl, &positions, &indices, &vertices);
- if(res != RES_OK) goto error;
-
- res = create_s3d_shape(ground->htrdr, &positions, &indices, &shape);
- if(res != RES_OK) goto error;
-
- S3D(shape_get_id(shape, &ishape));
- res = htable_interface_set(&ground->interfaces, &ishape, &interf);
- if(res != RES_OK) {
- htrdr_log_err(ground->htrdr,
- "Could not map the Star-3D shape to its Star-Materials -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- res = s3d_scene_attach_shape(scene, shape);
- if(res != RES_OK) {
- htrdr_log_err(ground->htrdr,
- "Could not attach a Star-3D shape to the Star-3D scene -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- S3D(shape_ref_put(shape));
- shape = NULL;
- }
-
- res = s3d_scene_view_create(scene, S3D_GET_PRIMITIVE|S3D_TRACE, &ground->view);
- if(res != RES_OK) {
- htrdr_log_err(ground->htrdr,
- "Could not create the Star-3D scene view -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
- res = s3d_scene_view_get_aabb(ground->view, ground->lower, ground->upper);
- if(res != RES_OK) {
- htrdr_log_err(ground->htrdr,
- "Could not get the bounding box of the geometry -- %s.\n",
- res_to_cstr(res));
- goto error;
- }
-
-exit:
- if(obj) AW(obj_ref_put(obj));
- if(shape) S3D(shape_ref_put(shape));
- if(scene) S3D(scene_ref_put(scene));
- htable_vertex_release(&vertices);
- darray_double_release(&positions);
- darray_size_t_release(&indices);
- return res;
-error:
- goto exit;
-}
-
-static void
-release_ground(ref_T* ref)
-{
- struct htrdr_ground* ground;
- ASSERT(ref);
- ground = CONTAINER_OF(ref, struct htrdr_ground, ref);
- if(ground->view) S3D(scene_view_ref_put(ground->view));
- htable_interface_release(&ground->interfaces);
- MEM_RM(ground->htrdr->allocator, ground);
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_ground_create
- (struct htrdr* htrdr,
- const char* obj_filename, /* May be NULL */
- const int repeat_ground, /* Infinitely repeat the ground in X and Y */
- struct htrdr_ground** out_ground)
-{
- char buf[128];
- struct htrdr_ground* ground = NULL;
- struct time t0, t1;
- res_T res = RES_OK;
- ASSERT(htrdr && out_ground);
-
- ground = MEM_CALLOC(htrdr->allocator, 1, sizeof(*ground));
- if(!ground) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "%s: could not allocate the ground data structure -- %s.\n",
- FUNC_NAME, res_to_cstr(res));
- goto error;
- }
- ref_init(&ground->ref);
- ground->htrdr = htrdr;
- ground->repeat = repeat_ground;
- f3_splat(ground->lower, (float)INF);
- f3_splat(ground->upper,-(float)INF);
- htable_interface_init(ground->htrdr->allocator, &ground->interfaces);
-
- if(!obj_filename) goto exit;
-
- htrdr_log(ground->htrdr, "Loading ground geometry from `%s'.\n",obj_filename);
- time_current(&t0);
- res = setup_ground(ground, obj_filename);
- if(res != RES_OK) goto error;
- time_sub(&t0, time_current(&t1), &t0);
- time_dump(&t0, TIME_ALL, NULL, buf, sizeof(buf));
- htrdr_log(ground->htrdr, "Setup ground in %s\n", buf);
-
-exit:
- *out_ground = ground;
- return res;
-error:
- if(ground) {
- htrdr_ground_ref_put(ground);
- ground = NULL;
- }
- goto exit;
-}
-
-void
-htrdr_ground_ref_get(struct htrdr_ground* ground)
-{
- ASSERT(ground);
- ref_get(&ground->ref);
-}
-
-void
-htrdr_ground_ref_put(struct htrdr_ground* ground)
-{
- ASSERT(ground);
- ref_put(&ground->ref, release_ground);
-}
-
-void
-htrdr_ground_get_interface
- (struct htrdr_ground* ground,
- const struct s3d_hit* hit,
- struct htrdr_interface* out_interface)
-{
- struct htrdr_interface* interf = NULL;
- ASSERT(ground && hit && out_interface);
-
- interf = htable_interface_find(&ground->interfaces, &hit->prim.geom_id);
- ASSERT(interf);
-
- *out_interface = *interf;
-}
-
-res_T
-htrdr_ground_trace_ray
- (struct htrdr_ground* ground,
- const double org[3],
- const double dir[3], /* Must be normalized */
- const double range[2],
- const struct s3d_hit* prev_hit,
- struct s3d_hit* hit)
-{
- res_T res = RES_OK;
- ASSERT(ground && org && dir && range && hit);
-
- if(!ground->view) { /* No ground geometry */
- *hit = S3D_HIT_NULL;
- goto exit;
- }
-
- if(!ground->repeat) {
- struct ray_context ray_ctx = RAY_CONTEXT_NULL;
- float ray_org[3];
- float ray_dir[3];
-
- f3_set_d3(ray_org, org);
- f3_set_d3(ray_dir, dir);
- f2_set_d2(ray_ctx.range, range);
- ray_ctx.hit_prev = prev_hit ? *prev_hit : S3D_HIT_NULL;
-
- res = s3d_scene_view_trace_ray
- (ground->view, ray_org, ray_dir, ray_ctx.range, &ray_ctx, hit);
- if(res != RES_OK) {
- htrdr_log_err(ground->htrdr,
- "%s: could not trace the ray against the ground geometry -- %s.\n",
- FUNC_NAME, res_to_cstr(res));
- goto error;
- }
- } else {
- struct trace_ground_context slab_ctx = TRACE_GROUND_CONTEXT_NULL;
- double low[3], upp[3];
-
- *hit = S3D_HIT_NULL;
- slab_ctx.view = ground->view;
- slab_ctx.context.range[0] = (float)range[0];
- slab_ctx.context.range[1] = (float)range[1];
- slab_ctx.context.hit_prev = prev_hit ? *prev_hit : S3D_HIT_NULL;
- slab_ctx.hit = hit;
-
- d3_set_f3(low, ground->lower);
- d3_set_f3(upp, ground->upper);
-
- res = htrdr_slab_trace_ray(ground->htrdr, org, dir, range, low, upp,
- trace_ground, 32, &slab_ctx);
- if(res != RES_OK) goto error;
- }
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-res_T
-htrdr_ground_find_closest_point
- (struct htrdr_ground* ground,
- const double pos[3],
- const double radius,
- struct s3d_hit* hit)
-{
- float query_pos[3];
- float query_radius;
- float ground_sz[3];
- res_T res = RES_OK;
- ASSERT(ground && pos && hit);
-
- if(!ground->view) { /* No ground geometry */
- *hit = S3D_HIT_NULL;
- goto exit;
- }
-
- query_radius = (float)radius;
- f3_set_d3(query_pos, pos);
-
- if(ground->repeat) {
- float translation[2] = {0, 0};
- int64_t xy[2];
- ground_sz[0] = ground->upper[0] - ground->lower[0];
- ground_sz[1] = ground->upper[1] - ground->lower[1];
- ground_sz[2] = ground->upper[2] - ground->lower[2];
-
- /* Define the 2D index of the current ground instance. (0,0) is the index
- * of the non instantiated ground */
- xy[0] = (int64_t)floor((query_pos[0] - ground->lower[0]) / ground_sz[0]);
- xy[1] = (int64_t)floor((query_pos[1] - ground->lower[1]) / ground_sz[1]);
-
- /* Define the translation along the X and Y axis from world space to local
- * ground geometry space */
- translation[0] = -(float)xy[0] * ground_sz[0];
- translation[1] = -(float)xy[1] * ground_sz[1];
-
- /* Transform the query pos in local ground geometry space */
- query_pos[0] += translation[0];
- query_pos[1] += translation[1];
- }
-
- /* Closest point query */
- res = s3d_scene_view_closest_point
- (ground->view, query_pos, query_radius, NULL, hit);
- if(res != RES_OK) {
- htrdr_log_err(ground->htrdr,
- "%s: could not query the closest point to the ground geometry -- %s.\n",
- FUNC_NAME, res_to_cstr(res));
- goto error;
- }
-
-exit:
- return res;
-error:
- goto exit;
-}
diff --git a/src/htrdr_ground.h b/src/htrdr_ground.h
@@ -1,78 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_GROUND_H
-#define HTRDR_GROUND_H
-
-#include <rsys/rsys.h>
-
-/* Forward declarations */
-struct htrdr;
-struct htrdr_ground;
-struct htrdr_interface;
-struct s3d_hit;
-struct ssf_bsdf;
-
-extern LOCAL_SYM res_T
-htrdr_ground_create
- (struct htrdr* htrdr,
- const char* obj_filename, /* May be NULL <=> No ground geometry */
- const int repeat_ground, /* Infinitely repeat the ground in X and Y */
- struct htrdr_ground** ground);
-
-extern LOCAL_SYM void
-htrdr_ground_ref_get
- (struct htrdr_ground* ground);
-
-extern LOCAL_SYM void
-htrdr_ground_ref_put
- (struct htrdr_ground* ground);
-
-extern LOCAL_SYM void
-htrdr_ground_get_interface
- (struct htrdr_ground* ground,
- const struct s3d_hit* hit,
- struct htrdr_interface* interface);
-
-extern LOCAL_SYM res_T
-htrdr_ground_create_bsdf
- (struct htrdr_ground* ground,
- const size_t ithread,
- const double wavelength,
- const double pos[3],
- const double dir[3], /* Incoming ray */
- const struct s3d_hit* hit,
- struct htrdr_interface* interf, /* NULL <=> do not return the interface */
- struct ssf_bsdf** bsdf);
-
-extern LOCAL_SYM res_T
-htrdr_ground_trace_ray
- (struct htrdr_ground* ground,
- const double ray_origin[3],
- const double ray_direction[3], /* Must be normalized */
- const double ray_range[2],
- const struct s3d_hit* prev_hit,/* Previous hit. Avoid self hit. May be NULL*/
- struct s3d_hit* hit);
-
-extern LOCAL_SYM res_T
-htrdr_ground_find_closest_point
- (struct htrdr_ground* ground,
- const double position[3],
- const double radius,
- struct s3d_hit* hit);
-
-#endif /* HTRDR_GROUND_H */
-
diff --git a/src/htrdr_main.c b/src/htrdr_main.c
@@ -1,91 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htrdr.h"
-#include "htrdr_args.h"
-
-#include <mpi.h>
-#include <rsys/mem_allocator.h>
-
-static const char*
-thread_support_string(const int val)
-{
- switch(val) {
- case MPI_THREAD_SINGLE: return "MPI_THREAD_SINGLE";
- case MPI_THREAD_FUNNELED: return "MPI_THREAD_FUNNELED";
- case MPI_THREAD_SERIALIZED: return "MPI_THREAD_SERIALIZED";
- case MPI_THREAD_MULTIPLE: return "MPI_THREAD_MULTIPLE";
- default: FATAL("Unreachable code.\n"); break;
- }
-}
-
-/*******************************************************************************
- * Program
- ******************************************************************************/
-int
-main(int argc, char** argv)
-{
- struct htrdr htrdr;
- struct htrdr_args args = HTRDR_ARGS_DEFAULT;
- size_t memsz = 0;
- int err = 0;
- int is_htrdr_init = 0;
- int thread_support = 0;
- res_T res = RES_OK;
-
- err = MPI_Init_thread(&argc, &argv, MPI_THREAD_SERIALIZED, &thread_support);
- if(err != MPI_SUCCESS) {
- fprintf(stderr, "Error initializing MPI.\n");
- goto error;
- }
-
- if(thread_support != MPI_THREAD_SERIALIZED) {
- fprintf(stderr, "The provided MPI implementation does not support "
- "serialized API calls from multiple threads. Provided thread support: "
- "%s.\n", thread_support_string(thread_support));
- goto error;
- }
-
- res = htrdr_args_init(&args, argc, argv);
- if(res != RES_OK) goto error;
- if(args.quit) goto exit;
-
- if(args.dump_vtk) {
- int rank;
- CHK(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS);
- if(rank != 0) goto exit; /* Nothing to do except for the master process */
- }
-
- res = htrdr_init(NULL, &args, &htrdr);
- if(res != RES_OK) goto error;
- is_htrdr_init = 1;
-
- res = htrdr_run(&htrdr);
- if(res != RES_OK) goto error;
-
-exit:
- MPI_Finalize();
- if(is_htrdr_init) htrdr_release(&htrdr);
- htrdr_args_release(&args);
- if((memsz = mem_allocated_size()) != 0) {
- fprintf(stderr, "Memory leaks: %lu Bytes\n", (unsigned long)memsz);
- err = -1;
- }
- return err;
-error:
- err = -1;
- goto exit;
-}
diff --git a/src/htrdr_materials.c b/src/htrdr_materials.c
@@ -1,449 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#define _POSIX_C_SOURCE 200112L /* strtok_r and wordexp support */
-
-#include "htrdr.h"
-#include "htrdr_materials.h"
-
-#include <modradurb/mrumtl.h>
-#include <star/ssf.h>
-#include <star/ssp.h>
-
-#include <rsys/cstr.h>
-#include <rsys/double3.h>
-#include <rsys/hash_table.h>
-#include <rsys/mem_allocator.h>
-#include <rsys/ref_count.h>
-#include <rsys/str.h>
-#include <rsys/text_reader.h>
-
-#include <string.h>
-#include <wordexp.h>
-
-struct mtl {
- struct mrumtl* mrumtl;
- double temperature; /* In Kelvin */
-};
-static const struct mtl MTL_NULL = {NULL, -1};
-
-/* Generate the hash table that maps a material name to its data */
-#define HTABLE_NAME name2mtl
-#define HTABLE_DATA struct mtl
-#define HTABLE_KEY struct str
-#define HTABLE_KEY_FUNCTOR_INIT str_init
-#define HTABLE_KEY_FUNCTOR_RELEASE str_release
-#define HTABLE_KEY_FUNCTOR_COPY str_copy
-#define HTABLE_KEY_FUNCTOR_COPY_AND_RELEASE str_copy_and_release
-#define HTABLE_KEY_FUNCTOR_HASH str_hash
-#define HTABLE_KEY_FUNCTOR_EQ str_eq
-#include <rsys/hash_table.h>
-
-struct htrdr_materials {
- struct htable_name2mtl name2mtl;
- struct htrdr* htrdr;
- ref_T ref;
-};
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-static res_T
-parse_material
- (struct htrdr_materials* mats,
- struct txtrdr* txtrdr,
- struct str* str) /* Scratch string */
-{
- wordexp_t wexp;
- char* tk = NULL;
- char* tk_ctx = NULL;
- struct mtl mtl = MTL_NULL;
- int err = 0;
- int wexp_is_allocated = 0;
- res_T res = RES_OK;
- ASSERT(mats && txtrdr);
-
- tk = strtok_r(txtrdr_get_line(txtrdr), " \t", &tk_ctx);
- ASSERT(tk);
-
- res = str_set(str, tk);
- if(res != RES_OK) {
- htrdr_log_err(mats->htrdr,
- "%s:%lu: could not copy the material name `%s' -- %s.\n",
- txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk,
- res_to_cstr(res));
- goto error;
- }
-
- tk = strtok_r(NULL, "", &tk_ctx);
- if(!tk) {
- htrdr_log_err(mats->htrdr,
- "%s:%lu: missing the MruMtl file for the material `%s'.\n",
- txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
- str_cget(str));
- res = RES_BAD_ARG;
- goto error;
- }
-
- err = wordexp(tk, &wexp, 0);
- if(err) {
- htrdr_log_err(mats->htrdr,
- "%s:%lu: error in word expension of the mrumtl path.\n",
- txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr));
- res = RES_BAD_ARG;
- goto error;
- }
- wexp_is_allocated = 1;
-
- if(wexp.we_wordc < 1) {
- htrdr_log_err(mats->htrdr,
- "%s:%lu: missing the MruMtl file for the material `%s'.\n",
- txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
- str_cget(str));
- res = RES_BAD_ARG;
- goto error;
- }
-
- /* Parse the mrumtl file if any */
- if(strcmp(wexp.we_wordv[0], "none")) {
- res = mrumtl_create(&mats->htrdr->logger, mats->htrdr->allocator,
- mats->htrdr->verbose, &mtl.mrumtl);
- if(res != RES_OK) {
- htrdr_log_err(mats->htrdr,
- "%s:%lu: error creating the MruMtl loader for the material `%s'-- %s.\n",
- txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
- str_cget(str), res_to_cstr(res));
- goto error;
- }
-
- res = mrumtl_load(mtl.mrumtl, wexp.we_wordv[0]);
- if(res != RES_OK) goto error;
- }
-
- if(wexp.we_wordc < 2) {
- if(mtl.mrumtl) {
- htrdr_log_err(mats->htrdr,
- "%s:%lu: missing temperature for the material `%s'.\n",
- txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
- str_cget(str));
- res = RES_BAD_ARG;
- goto error;
- }
- } else {
- /* Parse the temperature */
- res = cstr_to_double(wexp.we_wordv[1], &mtl.temperature);
- if(res != RES_OK) {
- htrdr_log_err(mats->htrdr,
- "%s:%lu: error parsing the temperature `%s' for the material `%s' "
- "-- %s.\n",
- txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
- wexp.we_wordv[1], str_cget(str), res_to_cstr(res));
- goto error;
- }
- }
-
- /* Register the material */
- res = htable_name2mtl_set(&mats->name2mtl, str, &mtl);
- if(res != RES_OK) {
- htrdr_log_err(mats->htrdr,
- "%s:%lu: could not register the material `%s' -- %s.\n",
- txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
- str_cget(str), res_to_cstr(res));
- goto error;
- }
-
- if(wexp.we_wordc > 2) {
- htrdr_log_warn(mats->htrdr, "%s:%lu: unexpected text `%s'.\n",
- txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr),
- wexp.we_wordv[2]);
- }
-
-exit:
- if(wexp_is_allocated) wordfree(&wexp);
- return res;
-error:
- if(mtl.mrumtl) MRUMTL(ref_put(mtl.mrumtl));
- goto exit;
-}
-
-static res_T
-parse_materials_list
- (struct htrdr_materials* mats,
- const char* filename,
- const char* func_name)
-{
- struct txtrdr* txtrdr = NULL;
- struct str str;
- res_T res = RES_OK;
- ASSERT(mats && filename && func_name);
-
- str_init(mats->htrdr->allocator, &str);
-
- res = txtrdr_file(mats->htrdr->allocator, filename, '#', &txtrdr);
- if(res != RES_OK) {
- htrdr_log_err(mats->htrdr,
- "%s: could not create the text reader for the material file `%s' -- %s.\n",
- func_name, filename, res_to_cstr(res));
- goto error;
- }
-
- for(;;) {
- res = txtrdr_read_line(txtrdr);
- if(res != RES_OK) {
- htrdr_log_err(mats->htrdr,
- "%s: error reading a line in the material file `%s' -- %s.\n",
- func_name, filename, res_to_cstr(res));
- goto error;
- }
-
- if(!txtrdr_get_cline(txtrdr)) break;
-
- res = parse_material(mats, txtrdr, &str);
- if(res != RES_OK) goto error;
- }
-
-exit:
- str_release(&str);
- if(txtrdr) txtrdr_ref_put(txtrdr);
- return res;
-error:
- goto exit;
-}
-
-static void
-materials_release(ref_T* ref)
-{
- struct htable_name2mtl_iterator it, it_end;
- struct htrdr_materials* mats;
- ASSERT(ref);
- mats = CONTAINER_OF(ref, struct htrdr_materials, ref);
-
- htable_name2mtl_begin(&mats->name2mtl, &it);
- htable_name2mtl_end(&mats->name2mtl, &it_end);
- while(!htable_name2mtl_iterator_eq(&it, &it_end)) {
- struct mtl* mtl = htable_name2mtl_iterator_data_get(&it);
- /* The mrumtl can be NULL for semi transparent materials */
- if(mtl->mrumtl) MRUMTL(ref_put(mtl->mrumtl));
- htable_name2mtl_iterator_next(&it);
- }
- htable_name2mtl_release(&mats->name2mtl);
- MEM_RM(mats->htrdr->allocator, mats);
-}
-
-static res_T
-create_bsdf_diffuse
- (struct htrdr* htrdr,
- const struct mrumtl_brdf* brdf,
- const size_t ithread,
- struct ssf_bsdf** out_bsdf)
-{
- struct ssf_bsdf* bsdf = NULL;
- double reflectivity = 0;
- res_T res = RES_OK;
- ASSERT(htrdr && brdf && out_bsdf);
- ASSERT(mrumtl_brdf_get_type(brdf) == MRUMTL_BRDF_LAMBERTIAN);
- ASSERT(ithread < htrdr->nthreads);
-
- res = ssf_bsdf_create
- (&htrdr->lifo_allocators[ithread], &ssf_lambertian_reflection, &bsdf);
- if(res != RES_OK) goto error;
-
- reflectivity = mrumtl_brdf_lambertian_get_reflectivity(brdf);
- res = ssf_lambertian_reflection_setup(bsdf, reflectivity);
- if(res != RES_OK) goto error;
-
-exit:
- *out_bsdf = bsdf;
- return res;
-error:
- if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; }
- goto exit;
-}
-
-static res_T
-create_bsdf_specular
- (struct htrdr* htrdr,
- const struct mrumtl_brdf* brdf,
- const size_t ithread,
- struct ssf_bsdf** out_bsdf)
-{
- struct ssf_bsdf* bsdf = NULL;
- struct ssf_fresnel* fresnel = NULL;
- double reflectivity = 0;
- res_T res = RES_OK;
- ASSERT(htrdr && brdf && out_bsdf);
- ASSERT(mrumtl_brdf_get_type(brdf) == MRUMTL_BRDF_SPECULAR);
- ASSERT(ithread < htrdr->nthreads);
-
- res = ssf_bsdf_create
- (&htrdr->lifo_allocators[ithread], &ssf_specular_reflection, &bsdf);
- if(res != RES_OK) goto error;
-
- res = ssf_fresnel_create
- (&htrdr->lifo_allocators[ithread], &ssf_fresnel_constant, &fresnel);
- if(res != RES_OK) goto error;
-
- reflectivity = mrumtl_brdf_specular_get_reflectivity(brdf);
- res = ssf_fresnel_constant_setup(fresnel, reflectivity);
- if(res != RES_OK) goto error;
-
- res = ssf_specular_reflection_setup(bsdf, fresnel);
- if(res != RES_OK) goto error;
-
-exit:
- if(fresnel) SSF(fresnel_ref_put(fresnel));
- *out_bsdf = bsdf;
- return res;
-error:
- if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; }
- goto exit;
-}
-
-/*******************************************************************************
- * Local symbol
- ******************************************************************************/
-res_T
-htrdr_materials_create
- (struct htrdr* htrdr,
- const char* filename,
- struct htrdr_materials** out_mtl)
-{
- struct htrdr_materials* mats = NULL;
- res_T res = RES_OK;
- ASSERT(htrdr && filename && out_mtl);
-
- mats = MEM_CALLOC(htrdr->allocator, 1, sizeof(*mats));
- if(!mats) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "%s: could not allocate the mats data structure -- %s.\n",
- FUNC_NAME, res_to_cstr(res));
- goto error;
- }
- ref_init(&mats->ref);
- mats->htrdr = htrdr;
- htable_name2mtl_init(htrdr->allocator, &mats->name2mtl);
-
- res = parse_materials_list(mats, filename, FUNC_NAME);
- if(res != RES_OK) goto error;
-
-exit:
- if(out_mtl) *out_mtl = mats;
- return res;
-error:
- if(mats) {
- htrdr_materials_ref_put(mats);
- mats = NULL;
- }
- goto exit;
-}
-
-void
-htrdr_materials_ref_get(struct htrdr_materials* mats)
-{
- ASSERT(mats);
- ref_get(&mats->ref);
-}
-
-void
-htrdr_materials_ref_put(struct htrdr_materials* mats)
-{
- ASSERT(mats);
- ref_put(&mats->ref, materials_release);
-}
-
-int
-htrdr_materials_find_mtl
- (struct htrdr_materials* mats,
- const char* name,
- struct htrdr_mtl* htrdr_mtl)
-{
- struct str str;
- struct htable_name2mtl_iterator it, it_end;
- int found = 0;
- ASSERT(mats && name && htrdr_mtl);
-
- str_init(mats->htrdr->allocator, &str);
- CHK(str_set(&str, name) == RES_OK);
-
- htable_name2mtl_find_iterator(&mats->name2mtl, &str, &it);
- htable_name2mtl_end(&mats->name2mtl, &it_end);
- if(htable_name2mtl_iterator_eq(&it, &it_end)) { /* No material found */
- *htrdr_mtl = HTRDR_MTL_NULL;
- found = 0;
- } else {
- struct mtl* mtl = htable_name2mtl_iterator_data_get(&it);
- ASSERT(mtl != NULL);
- htrdr_mtl->name = str_cget(htable_name2mtl_iterator_key_get(&it));
- htrdr_mtl->mrumtl = mtl->mrumtl;
- htrdr_mtl->temperature = mtl->temperature;
- found = 1;
- }
- str_release(&str);
-
- return found;
-}
-
-res_T
-htrdr_mtl_create_bsdf
- (struct htrdr* htrdr,
- const struct htrdr_mtl* mtl,
- const size_t ithread,
- const double wavelength,
- struct ssp_rng* rng,
- struct ssf_bsdf** out_bsdf)
-{
- struct ssf_bsdf* bsdf = NULL;
- const struct mrumtl_brdf* brdf = NULL;
- double r;
- res_T res = RES_OK;
- ASSERT(htrdr && mtl && wavelength && rng && out_bsdf);
- ASSERT(ithread < htrdr->nthreads);
-
- r = ssp_rng_canonical(rng);
-
- res = mrumtl_fetch_brdf2(mtl->mrumtl, wavelength, r, &brdf);
- if(res != RES_OK) {
- htrdr_log_err(htrdr,
- "%s: error retrieving the MruMtl BRDF for the wavelength %g.\n",
- FUNC_NAME, wavelength);
- res = RES_BAD_ARG;
- goto error;
- }
-
- switch(mrumtl_brdf_get_type(brdf)) {
- case MRUMTL_BRDF_LAMBERTIAN:
- res = create_bsdf_diffuse(htrdr, brdf, ithread, &bsdf);
- break;
- case MRUMTL_BRDF_SPECULAR:
- res = create_bsdf_specular(htrdr, brdf, ithread, &bsdf);
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- if(res != RES_OK) {
- htrdr_log_err(htrdr, "%s: could not create the BSDF -- %s.\n",
- FUNC_NAME, res_to_cstr(res));
- goto error;
- }
-
-exit:
- *out_bsdf = bsdf;
- return res;
-error:
- if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; }
- goto exit;
-}
-
diff --git a/src/htrdr_materials.h b/src/htrdr_materials.h
@@ -1,67 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_MATERIALS_H
-#define HTRDR_MATERIALS_H
-
-#include <rsys/rsys.h>
-
-/* Forward declarations */
-struct htrdr_materials;
-struct mrumtl;
-struct s3d_hit;
-struct ssf_bsdf;
-struct ssp_rng;
-
-struct htrdr_mtl {
- const char* name;
- const struct mrumtl* mrumtl;
- double temperature;
-};
-static const struct htrdr_mtl HTRDR_MTL_NULL;
-
-extern LOCAL_SYM res_T
-htrdr_materials_create
- (struct htrdr* htrdr,
- const char* filename,
- struct htrdr_materials** mats);
-
-extern LOCAL_SYM void
-htrdr_materials_ref_get
- (struct htrdr_materials* mats);
-
-extern LOCAL_SYM void
-htrdr_materials_ref_put
- (struct htrdr_materials* mats);
-
-/* Return 1 if the material exist and 0 otherwise */
-extern LOCAL_SYM int
-htrdr_materials_find_mtl
- (struct htrdr_materials* mats,
- const char* mtl_name,
- struct htrdr_mtl* mtl);
-
-extern LOCAL_SYM res_T
-htrdr_mtl_create_bsdf
- (struct htrdr* htrdr,
- const struct htrdr_mtl* mtl,
- const size_t ithread,
- const double wavelength,
- struct ssp_rng* rng,
- struct ssf_bsdf** bsdf);
-
-#endif /* HTRDR_MATERIALS_H */
-
diff --git a/src/htrdr_ran_wlen.c b/src/htrdr_ran_wlen.c
@@ -1,364 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#define _POSIX_C_SOURCE 200112L /* nextafter */
-
-#include "htrdr.h"
-#include "htrdr_c.h"
-#include "htrdr_ran_wlen.h"
-
-#include <high_tune/htsky.h>
-
-#include <rsys/algorithm.h>
-#include <rsys/cstr.h>
-#include <rsys/dynamic_array_double.h>
-#include <rsys/mem_allocator.h>
-#include <rsys/ref_count.h>
-
-#include <math.h> /* nextafter */
-
-struct htrdr_ran_wlen {
- struct darray_double pdf;
- struct darray_double cdf;
- double range[2]; /* Boundaries of the spectral integration interval */
- double band_len; /* Length in nanometers of a band */
- double ref_temperature; /* In Kelvin */
- size_t nbands; /* # bands */
-
- struct htrdr* htrdr;
- ref_T ref;
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static res_T
-setup_wlen_ran_cdf
- (struct htrdr_ran_wlen* wlen_ran,
- const char* func_name)
-{
- double* pdf = NULL;
- double* cdf = NULL;
- double sum = 0;
- size_t i;
- res_T res = RES_OK;
- ASSERT(wlen_ran && func_name && wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE);
-
- res = darray_double_resize(&wlen_ran->cdf, wlen_ran->nbands);
- if(res != RES_OK) {
- htrdr_log_err(wlen_ran->htrdr,
- "%s: Error allocating the CDF of the spectral bands -- %s.\n",
- func_name, res_to_cstr(res));
- goto error;
- }
- res = darray_double_resize(&wlen_ran->pdf, wlen_ran->nbands);
- if(res != RES_OK) {
- htrdr_log_err(wlen_ran->htrdr,
- "%s: Error allocating the pdf of the spectral bands -- %s.\n",
- func_name, res_to_cstr(res));
- goto error;
- }
-
- cdf = darray_double_data_get(&wlen_ran->cdf);
- pdf = darray_double_data_get(&wlen_ran->pdf); /* Now save pdf to correct weight */
-
- htrdr_log(wlen_ran->htrdr,
- "Number of bands of the spectrum cumulative: %lu\n",
- (unsigned long)wlen_ran->nbands);
-
- /* Compute the *unnormalized* probability to sample a small band */
- FOR_EACH(i, 0, wlen_ran->nbands) {
- double lambda_lo = wlen_ran->range[0] + (double)i * wlen_ran->band_len;
- double lambda_hi = MMIN(lambda_lo + wlen_ran->band_len, wlen_ran->range[1]);
- ASSERT(lambda_lo<= lambda_hi);
- ASSERT(lambda_lo > wlen_ran->range[0] || eq_eps(lambda_lo, wlen_ran->range[0], 1.e-6));
- ASSERT(lambda_lo < wlen_ran->range[1] || eq_eps(lambda_lo, wlen_ran->range[1], 1.e-6));
-
- /* Convert from nanometer to meter */
- lambda_lo *= 1.e-9;
- lambda_hi *= 1.e-9;
-
- /* Compute the probability of the current band */
- pdf[i] = blackbody_fraction(lambda_lo, lambda_hi, wlen_ran->ref_temperature);
-
- /* Update the norm */
- sum += pdf[i];
- }
-
- /* Compute the cumulative of the previously computed probabilities */
- FOR_EACH(i, 0, wlen_ran->nbands) {
- /* Normalize the probability */
- pdf[i] /= sum;
-
- /* Setup the cumulative */
- if(i == 0) {
- cdf[i] = pdf[i];
- } else {
- cdf[i] = pdf[i] + cdf[i-1];
- ASSERT(cdf[i] >= cdf[i-1]);
- }
- }
- ASSERT(eq_eps(cdf[wlen_ran->nbands-1], 1, 1.e-6));
- cdf[wlen_ran->nbands - 1] = 1.0; /* Handle numerical issue */
-
-exit:
- return res;
-error:
- darray_double_clear(&wlen_ran->cdf);
- darray_double_clear(&wlen_ran->pdf);
- goto exit;
-}
-
-static double
-wlen_ran_sample_continue
- (const struct htrdr_ran_wlen* wlen_ran,
- const double r,
- const double range[2], /* In nanometer */
- const char* func_name,
- double* pdf)
-{
- /* Numerical parameters of the dichotomy search */
- const size_t MAX_ITER = 100;
- const double EPSILON_LAMBDA_M = 1e-15;
- const double EPSILON_BF = 1e-6;
-
- /* Local variables */
- double bf = 0;
- double bf_prev = 0;
- double bf_min_max = 0;
- double lambda_m = 0;
- double lambda_m_prev = 0;
- double lambda_m_min = 0;
- double lambda_m_max = 0;
- double range_m[2] = {0, 0};
- size_t i;
-
- /* Check precondition */
- ASSERT(wlen_ran && func_name);
- ASSERT(range && range[0] < range[1]);
- ASSERT(0 <= r && r < 1);
-
- /* Convert the wavelength range in meters */
- range_m[0] = range[0] * 1.0e-9;
- range_m[1] = range[1] * 1.0e-9;
-
- /* Setup the dichotomy search */
- lambda_m_min = range_m[0];
- lambda_m_max = range_m[1];
- bf_min_max = blackbody_fraction
- (range_m[0], range_m[1], wlen_ran->ref_temperature);
-
- /* Numerically search the lambda corresponding to the submitted canonical
- * number */
- FOR_EACH(i, 0, MAX_ITER) {
- double r_test;
- lambda_m = (lambda_m_min + lambda_m_max) * 0.5;
- bf = blackbody_fraction(range_m[0], lambda_m, wlen_ran->ref_temperature);
-
- r_test = bf / bf_min_max;
- if(r_test < r) {
- lambda_m_min = lambda_m;
- } else {
- lambda_m_max = lambda_m;
- }
-
- if(fabs(lambda_m_prev - lambda_m) < EPSILON_LAMBDA_M
- || fabs(bf_prev - bf) < EPSILON_BF)
- break;
-
- lambda_m_prev = lambda_m;
- bf_prev = bf;
- }
- if(i >= MAX_ITER) {
- htrdr_log_warn(wlen_ran->htrdr,
- "%s: could not sample a wavelength in the range [%g, %g] nanometers "
- "for the reference temperature %g Kelvin.\n",
- func_name, SPLIT2(range), wlen_ran->ref_temperature);
- }
-
- if(pdf) {
- const double Tref = wlen_ran->ref_temperature;
- const double B_lambda = planck(lambda_m, lambda_m, Tref);
- const double B_mean = planck(range_m[0], range_m[1], Tref);
- *pdf = B_lambda / (B_mean * (range_m[1]-range_m[0]));
- }
-
- return lambda_m * 1.0e9; /* Convert in nanometers */
-}
-
-static double
-wlen_ran_sample_discrete
- (const struct htrdr_ran_wlen* wlen_ran,
- const double r0,
- const double r1,
- const char* func_name,
- double* pdf)
-{
- const double* cdf = NULL;
- const double* find = NULL;
- double r0_next = nextafter(r0, DBL_MAX);
- double band_range[2];
- double lambda = 0;
- double pdf_continue = 0;
- double pdf_band = 0;
- size_t cdf_length = 0;
- size_t i;
- ASSERT(wlen_ran && wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE);
- ASSERT(0 <= r0 && r0 < 1);
- ASSERT(0 <= r1 && r1 < 1);
- (void)func_name;
- (void)pdf_band;
-
- cdf = darray_double_cdata_get(&wlen_ran->cdf);
- cdf_length = darray_double_size_get(&wlen_ran->cdf);
- ASSERT(cdf_length > 0);
-
- /* Use r_next rather than r0 in order to find the first entry that is not less
- * than *or equal* to r0 */
- find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl);
- ASSERT(find);
-
- i = (size_t)(find - cdf);
- ASSERT(i < cdf_length && cdf[i] > r0 && (!i || cdf[i-1] <= r0));
-
- band_range[0] = wlen_ran->range[0] + (double)i*wlen_ran->band_len;
- band_range[1] = band_range[0] + wlen_ran->band_len;
-
- /* Fetch the pdf of the sampled band */
- pdf_band = darray_double_cdata_get(&wlen_ran->pdf)[i];
-
- /* Uniformly sample a wavelength in the sampled band */
- lambda = band_range[0] + (band_range[1] - band_range[0]) * r1;
- pdf_continue = 1.0 / ((band_range[1] - band_range[0])*1.e-9);
-
- if(pdf) {
- *pdf = pdf_band * pdf_continue;
- }
-
- return lambda;
-}
-
-static void
-release_wlen_ran(ref_T* ref)
-{
- struct htrdr_ran_wlen* wlen_ran = NULL;
- ASSERT(ref);
- wlen_ran = CONTAINER_OF(ref, struct htrdr_ran_wlen, ref);
- darray_double_release(&wlen_ran->cdf);
- darray_double_release(&wlen_ran->pdf);
- MEM_RM(wlen_ran->htrdr->allocator, wlen_ran);
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_ran_wlen_create
- (struct htrdr* htrdr,
- /* range must be included in [200,1000] nm for solar or in [1000, 100000]
- * nanometers for longwave (thermal)*/
- const double range[2],
- const size_t nbands, /* # bands used to discretized CDF */
- const double ref_temperature,
- struct htrdr_ran_wlen** out_wlen_ran)
-{
- struct htrdr_ran_wlen* wlen_ran = NULL;
- double min_band_len = 0;
- res_T res = RES_OK;
- ASSERT(htrdr && range && out_wlen_ran && ref_temperature > 0);
- ASSERT(ref_temperature > 0);
- ASSERT(range[0] <= range[1]);
-
- wlen_ran = MEM_CALLOC(htrdr->allocator, 1, sizeof(*wlen_ran));
- if(!wlen_ran) {
- res = RES_MEM_ERR;
- htrdr_log_err(htrdr,
- "%s: could not allocate longwave random variate data structure -- %s.\n",
- FUNC_NAME, res_to_cstr(res));
- goto error;
- }
- ref_init(&wlen_ran->ref);
- wlen_ran->htrdr = htrdr;
- darray_double_init(htrdr->allocator, &wlen_ran->cdf);
- darray_double_init(htrdr->allocator, &wlen_ran->pdf);
-
- wlen_ran->range[0] = range[0];
- wlen_ran->range[1] = range[1];
- wlen_ran->ref_temperature = ref_temperature;
- wlen_ran->nbands = nbands;
-
- min_band_len = compute_sky_min_band_len(wlen_ran->htrdr->sky, wlen_ran->range);
-
- if(nbands == HTRDR_WLEN_RAN_CONTINUE) {
- wlen_ran->band_len = 0;
- } else {
- wlen_ran->band_len = (range[1] - range[0]) / (double)wlen_ran->nbands;
-
- /* Adjust the band length to ensure that each sky spectral interval is
- * overlapped by at least one band */
- if(wlen_ran->band_len > min_band_len) {
- wlen_ran->band_len = min_band_len;
- wlen_ran->nbands = (size_t)ceil((range[1] - range[0]) / wlen_ran->band_len);
- }
-
- res = setup_wlen_ran_cdf(wlen_ran, FUNC_NAME);
- if(res != RES_OK) goto error;
- }
-
- htrdr_log(htrdr, "Spectral interval defined on [%g, %g] nanometers.\n",
- range[0], range[1]);
-
-exit:
- *out_wlen_ran = wlen_ran;
- return res;
-error:
- if(wlen_ran) htrdr_ran_wlen_ref_put(wlen_ran);
- goto exit;
-}
-
-void
-htrdr_ran_wlen_ref_get(struct htrdr_ran_wlen* wlen_ran)
-{
- ASSERT(wlen_ran);
- ref_get(&wlen_ran->ref);
-}
-
-void
-htrdr_ran_wlen_ref_put(struct htrdr_ran_wlen* wlen_ran)
-{
- ASSERT(wlen_ran);
- ref_put(&wlen_ran->ref, release_wlen_ran);
-}
-
-double
-htrdr_ran_wlen_sample
- (const struct htrdr_ran_wlen* wlen_ran,
- const double r0,
- const double r1,
- double* pdf)
-{
- ASSERT(wlen_ran);
- if(wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE) { /* Discrete */
- return wlen_ran_sample_discrete(wlen_ran, r0, r1, FUNC_NAME, pdf);
- } else if(eq_eps(wlen_ran->range[0], wlen_ran->range[1], 1.e-6)) {
- if(pdf) *pdf = 1;
- return wlen_ran->range[0];
- } else { /* Continue */
- return wlen_ran_sample_continue
- (wlen_ran, r0, wlen_ran->range, FUNC_NAME, pdf);
- }
-}
-
diff --git a/src/htrdr_ran_wlen.h b/src/htrdr_ran_wlen.h
@@ -1,54 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_RAN_WLEN_H
-#define HTRDR_RAN_WLEN_H
-
-#include <rsys/rsys.h>
-
-#define HTRDR_WLEN_RAN_CONTINUE 0
-
-struct htrdr;
-struct htrdr_ran_wlen;
-
-extern LOCAL_SYM res_T
-htrdr_ran_wlen_create
- (struct htrdr* htrdr,
- const double range[2],
- /* # bands used to discretisze the spectral domain. HTRDR_WLEN_RAN_CONTINUE
- * <=> no discretisation */
- const size_t nbands, /* Hint on #bands used to discretised th CDF */
- const double ref_temperature, /* Reference temperature */
- struct htrdr_ran_wlen** wlen_ran);
-
-extern LOCAL_SYM void
-htrdr_ran_wlen_ref_get
- (struct htrdr_ran_wlen* wlen_ran);
-
-extern LOCAL_SYM void
-htrdr_ran_wlen_ref_put
- (struct htrdr_ran_wlen* wlen_ran);
-
-/* Return a wavelength in nanometer */
-extern LOCAL_SYM double
-htrdr_ran_wlen_sample
- (const struct htrdr_ran_wlen* wlen_ran,
- const double r0, /* Canonical number in [0, 1[ */
- const double r1, /* Canonical number in [0, 1[ */
- double* pdf); /* May be NULL */
-
-#endif /* HTRDR_RAN_WLEN_H */
-
diff --git a/src/htrdr_rectangle.c b/src/htrdr_rectangle.c
@@ -1,142 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htrdr.h"
-#include "htrdr_rectangle.h"
-
-#include <rsys/double3.h>
-#include <rsys/mem_allocator.h>
-#include <rsys/ref_count.h>
-
-struct htrdr_rectangle {
- /* Frame of the rectangle in world space */
- double axis_x[3];
- double axis_y[3];
-
- double normal[3];
- double position[3]; /* Center of the rectangle */
- struct htrdr* htrdr;
- ref_T ref;
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static void
-rectangle_release(ref_T* ref)
-{
- struct htrdr_rectangle* rect;
- ASSERT(ref);
- rect = CONTAINER_OF(ref, struct htrdr_rectangle, ref);
- MEM_RM(rect->htrdr->allocator, rect);
-}
-
-/*******************************************************************************
- * Local fuuction
- ******************************************************************************/
-res_T
-htrdr_rectangle_create
- (struct htrdr* htrdr,
- const double sz[2],
- const double pos[3],
- const double tgt[3],
- const double up[3],
- struct htrdr_rectangle** out_rect)
-{
- struct htrdr_rectangle* rect = NULL;
- double x[3], y[3], z[3];
- res_T res = RES_OK;
- ASSERT(htrdr && pos && tgt && up && sz && out_rect);
-
- rect = MEM_CALLOC(htrdr->allocator, 1, sizeof(*rect));
- if(!rect) {
- htrdr_log_err(htrdr, "could not allocate the rectangle data structure.\n");
- res = RES_MEM_ERR;
- goto error;
- }
- ref_init(&rect->ref);
- rect->htrdr = htrdr;
-
- if(sz[0] <= 0 || sz[1] <= 0) {
- htrdr_log_err(htrdr,
- "invalid rectangle size `%g %g'. It must be strictly positive.\n",
- SPLIT2(sz));
- res = RES_BAD_ARG;
- goto error;
- }
-
- if(d3_normalize(z, d3_sub(z, tgt, pos)) <= 0
- || d3_normalize(x, d3_cross(x, z, up)) <= 0
- || d3_normalize(y, d3_cross(y, z, x)) <= 0) {
- htrdr_log_err(htrdr, "invalid rectangle frame:\n"
- "\tposition = %g %g %g\n"
- "\ttarget = %g %g %g\n"
- "\tup = %g %g %g\n",
- SPLIT3(pos), SPLIT3(tgt), SPLIT3(up));
- res = RES_BAD_ARG;
- goto error;
- }
-
- d3_muld(rect->axis_x, x, sz[0]*0.5);
- d3_muld(rect->axis_y, y, sz[1]*0.5);
- d3_set(rect->normal, z);
- d3_set(rect->position, pos);
-
-exit:
- *out_rect = rect;
- return res;
-error:
- if(rect) {
- htrdr_rectangle_ref_put(rect);
- rect = NULL;
- }
- goto exit;
-}
-
-void
-htrdr_rectangle_sample_pos
- (const struct htrdr_rectangle* rect,
- const double sample[2], /* In [0, 1[ */
- double pos[3])
-{
- double x[3], y[3];
- ASSERT(rect && sample && pos);
- d3_muld(x, rect->axis_x, sample[0]*2.0 - 1.0);
- d3_muld(y, rect->axis_y, sample[1]*2.0 - 1.0);
- d3_add(pos, d3_add(pos, rect->position, x), y);
-}
-
-void
-htrdr_rectangle_ref_get(struct htrdr_rectangle* rect)
-{
- ASSERT(rect);
- ref_get(&rect->ref);
-}
-
-void
-htrdr_rectangle_ref_put(struct htrdr_rectangle* rect)
-{
- ASSERT(rect);
- ref_put(&rect->ref, rectangle_release);
-}
-
-void
-htrdr_rectangle_get_normal(const struct htrdr_rectangle* rect, double normal[3])
-{
- ASSERT(rect && normal);
- d3_set(normal, rect->normal);
-}
-
diff --git a/src/htrdr_rectangle.h b/src/htrdr_rectangle.h
@@ -1,54 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_RECTANGLE_H
-#define HTRDR_RECTANGLE_H
-
-#include <rsys/rsys.h>
-
-struct htrdr;
-struct htrdr_rectangle; /* 2D rectangle transformed in 3D */
-
-extern LOCAL_SYM res_T
-htrdr_rectangle_create
- (struct htrdr* htrdr,
- const double sz[2], /* Size of the rectangle along its local X and Y axis */
- const double pos[3], /* World space position of the rectangle center */
- const double tgt[3], /* Vector orthognal to the rectangle plane */
- const double up[3], /* vector orthogonal to the rectangle X axis */
- struct htrdr_rectangle** rect);
-
-extern LOCAL_SYM void
-htrdr_rectangle_ref_get
- (struct htrdr_rectangle* rect);
-
-extern LOCAL_SYM void
-htrdr_rectangle_ref_put
- (struct htrdr_rectangle* rect);
-
-extern LOCAL_SYM void
-htrdr_rectangle_sample_pos
- (const struct htrdr_rectangle* rect,
- const double sample[2], /* In [0, 1[ */
- double pos[3]);
-
-extern LOCAL_SYM void
-htrdr_rectangle_get_normal
- (const struct htrdr_rectangle* rect,
- double normal[3]);
-
-#endif /* HTRDR_RECTANGLE_H */
-
diff --git a/src/htrdr_sensor.c b/src/htrdr_sensor.c
@@ -1,136 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htrdr.h"
-#include "htrdr_camera.h"
-#include "htrdr_ground.h"
-#include "htrdr_interface.h"
-#include "htrdr_rectangle.h"
-#include "htrdr_sensor.h"
-
-#include <star/s3d.h>
-#include <star/ssp.h>
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static res_T
-sample_camera_ray
- (struct htrdr_camera* cam,
- const size_t ipix[2],
- const double pix_sz[2],
- struct ssp_rng* rng,
- double ray_org[3],
- double ray_dir[3])
-{
- double pix_samp[2];
- ASSERT(cam && ipix && pix_sz && rng && ray_org && ray_dir);
-
- /* Sample a position into the pixel, in the normalized image plane */
- pix_samp[0] = ((double)ipix[0] + ssp_rng_canonical(rng)) * pix_sz[0];
- pix_samp[1] = ((double)ipix[1] + ssp_rng_canonical(rng)) * pix_sz[1];
-
- /* Generate a ray starting from the pinhole camera and passing through the
- * pixel sample */
- htrdr_camera_ray(cam, pix_samp, ray_org, ray_dir);
-
- return RES_OK;
-}
-
-static res_T
-sample_rectangle_ray
- (struct htrdr_rectangle* rect,
- struct htrdr* htrdr,
- const size_t ipix[2],
- const double pix_sz[2],
- struct ssp_rng* rng,
- double ray_org[3],
- double ray_dir[3])
-{
- struct s3d_hit hit = S3D_HIT_NULL;
- double pix_samp[2];
- const double up_dir[3] = {0,0,1};
- const double range[2] = {0, DBL_MAX};
- double normal[3];
- ASSERT(rect && htrdr && ipix && pix_sz && rng && ray_org && ray_dir);
-
- /* Sample a position into the pixel, in the normalized image plane */
- pix_samp[0] = ((double)ipix[0] + ssp_rng_canonical(rng)) * pix_sz[0];
- pix_samp[1] = ((double)ipix[1] + ssp_rng_canonical(rng)) * pix_sz[1];
-
- /* Retrieve the world space position of pix_samp */
- htrdr_rectangle_sample_pos(rect, pix_samp, ray_org);
-
- /* Check that `ray_org' is not included into a geometry */
- HTRDR(ground_trace_ray(htrdr->ground, ray_org, up_dir, range, NULL, &hit));
-
- /* Up direction is occluded. Check if the sample must be rejected, i.e. does it
- * lies inside a geometry? */
- if(!S3D_HIT_NONE(&hit)) {
- struct htrdr_interface interf = HTRDR_INTERFACE_NULL;
- const struct htrdr_mtl* mtl = NULL;
- float N[3]; /* Normalized normal of the hit */
- float wi[3];
- float cos_wi_N;
-
- /* Compute the cosine between the up direction and the hit normal */
- f3_set_d3(wi, up_dir);
- f3_normalize(N, hit.normal);
- cos_wi_N = f3_dot(wi, N);
-
- /* Fetch the hit interface and retrieve the material into which the ray was
- * traced */
- htrdr_ground_get_interface(htrdr->ground, &hit, &interf);
- mtl = cos_wi_N < 0 ? &interf.mtl_front : &interf.mtl_back;
-
- /* Reject the sample if the incident direction do not travel into the sky */
- if(strcmp(mtl->name, htrdr->sky_mtl_name) != 0) return RES_BAD_OP;
- }
-
- /* Sample a ray direction */
- htrdr_rectangle_get_normal(rect, normal);
- ssp_ran_hemisphere_cos(rng, normal, ray_dir, NULL);
-
- return RES_OK;
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_sensor_sample_primary_ray
- (const struct htrdr_sensor* sensor,
- struct htrdr* htrdr,
- const size_t ipix[2],
- const double pix_sz[2],
- struct ssp_rng* rng,
- double ray_org[3],
- double ray_dir[3])
-{
- res_T res = RES_OK;
- switch(sensor->type) {
- case HTRDR_SENSOR_CAMERA:
- res = sample_camera_ray(sensor->camera, ipix, pix_sz, rng, ray_org, ray_dir);
- break;
- case HTRDR_SENSOR_RECTANGLE:
- res = sample_rectangle_ray(sensor->rectangle, htrdr, ipix,
- pix_sz, rng, ray_org, ray_dir);
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- return res;
-}
-
diff --git a/src/htrdr_sensor.h b/src/htrdr_sensor.h
@@ -1,48 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_SENSOR_H
-#define HTRDR_SENSOR_H
-
-#include <rsys/rsys.h>
-
-/* Forward declarations */
-struct htrdr;
-struct ssp_rng;
-
-enum htrdr_sensor_type {
- HTRDR_SENSOR_CAMERA,
- HTRDR_SENSOR_RECTANGLE
-};
-
-struct htrdr_sensor {
- struct htrdr_camera* camera;
- struct htrdr_rectangle* rectangle;
- enum htrdr_sensor_type type;
-};
-
-extern LOCAL_SYM res_T
-htrdr_sensor_sample_primary_ray
- (const struct htrdr_sensor* sensor,
- struct htrdr* htrdr,
- const size_t ipix[2],
- const double pix_sz[2],
- struct ssp_rng* rng,
- double ray_org[3],
- double ray_dir[3]);
-
-#endif /* HTRDR_SENSOR_H */
-
diff --git a/src/htrdr_slab.h b/src/htrdr_slab.h
@@ -1,47 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_SLAB_H
-#define HTRDR_SLAB_H
-
-#include <rsys/rsys.h>
-
-/* Forward declaration */
-struct htrdr;
-
-typedef res_T
-(*htrdr_trace_cell_T)
- (const double org[3], /* Ray origin */
- const double dir[3], /* Ray direction. Must be normalized */
- const double range[2], /* Ray range */
- void* ctx, /* User defined data */
- int* hit); /* Hit something ? */
-
-/* Trace a ray into a slab composed of a cell infinitely repeated in X and Y */
-extern LOCAL_SYM res_T
-htrdr_slab_trace_ray
- (struct htrdr* htrdr,
- const double org[3],
- const double dir[3],
- const double range[2],
- const double cell_low[2],
- const double cell_upp[2],
- htrdr_trace_cell_T trace_cell,
- const size_t max_steps, /* Max traversed cell */
- void* trace_cell_context);
-
-#endif /* HTRDR_SLAB_H */
-
diff --git a/src/htrdr_solve.h b/src/htrdr_solve.h
@@ -1,185 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_SOLVE_H
-#define HTRDR_SOLVE_H
-
-#include <rsys/rsys.h>
-
-/* Define the radiance component */
-enum htrdr_radiance_cpnt_flag {
- HTRDR_RADIANCE_DIRECT = BIT(0),
- HTRDR_RADIANCE_DIFFUSE = BIT(1),
- HTRDR_RADIANCE_ALL = HTRDR_RADIANCE_DIRECT | HTRDR_RADIANCE_DIFFUSE
-};
-
-/* Monte carlo accumulator */
-struct htrdr_accum {
- double sum_weights; /* Sum of Monte-Carlo weights */
- double sum_weights_sqr; /* Sum of Monte-Carlo square weights */
- size_t nweights; /* #accumlated weights */
- size_t nfailures; /* #failures */
-};
-static const struct htrdr_accum HTRDR_ACCUM_NULL;
-
-/* Monte carlo estimate */
-struct htrdr_estimate {
- double E; /* Expected value */
- double SE; /* Standard error */
-};
-static const struct htrdr_estimate HTRDR_ESTIMATE_NULL;
-
-struct htrdr_pixel_xwave {
- struct htrdr_estimate radiance; /* In W/m^2/sr */
- struct htrdr_estimate radiance_temperature; /* In K */
- struct htrdr_accum time; /* In microseconds */
-};
-static const struct htrdr_pixel_xwave HTRDR_PIXEL_XWAVE_NULL;
-
-struct htrdr_pixel_flux {
- struct htrdr_accum flux;
- struct htrdr_accum time;
-};
-static const struct htrdr_pixel_flux HTRDR_PIXEL_FLUX;
-
-struct htrdr_pixel_image {
- struct htrdr_estimate X; /* In W/m^2/sr */
- struct htrdr_estimate Y; /* In W/m^2/sr */
- struct htrdr_estimate Z; /* In W/m^2/sr */
- struct htrdr_accum time; /* In microseconds */
-};
-static const struct htrdr_pixel_image HTRDR_PIXEL_IMAGE_NULL;
-
-/* Forward declarations */
-struct htrdr;
-struct htrdr_camera;
-struct s3d_hit;
-struct ssp_rng;
-
-/* Return the shortwave radiance in W/m^2/sr/m */
-extern LOCAL_SYM double
-htrdr_compute_radiance_sw
- (struct htrdr* htrdr,
- const size_t ithread,
- struct ssp_rng* rng,
- const int cpnt_mask, /* Combination of enum htrdr_radiance_cpnt_flag */
- const double pos[3],
- const double dir[3],
- const double wlen, /* In nanometer */
- const size_t iband, /* Index of the spectral band */
- const size_t iquad); /* Index of the quadrature point into the band */
-
-/* Return the longwave radiance in W/m^2/sr/m */
-extern LOCAL_SYM double
-htrdr_compute_radiance_lw
- (struct htrdr* htrdr,
- const size_t ithread,
- struct ssp_rng* rng,
- const double pos[3],
- const double dir[3],
- const double wlen, /* In nanometer */
- const size_t iband, /* Index of the spectral band */
- const size_t iquad); /* Index of the quadrature point into the band */
-
-extern LOCAL_SYM res_T
-htrdr_draw_map
- (struct htrdr* htrdr,
- const struct htrdr_sensor* sensor,
- const size_t width, /* Image width */
- const size_t height, /* Image height */
- const size_t spp, /* #samples per pixel, i.e. #realisations */
- /* Buffer of struct htrdr_accum[4]. May be NULL on non master processes */
- struct htrdr_buffer* buf);
-
-extern LOCAL_SYM int
-htrdr_ground_filter
- (const struct s3d_hit* hit,
- const float ray_dorg[3],
- const float ray_dir[3],
- void* ray_data,
- void* filter_data);
-
-static FINLINE void
-htrdr_accum_get_estimation
- (const struct htrdr_accum* acc,
- struct htrdr_estimate* estimate)
-{
- ASSERT(acc && estimate);
-
- if(!acc->nweights) {
- estimate->E = 0;
- estimate->SE = 0;
- } else {
- const double N = (double)acc->nweights;
- double E, V, SE;
- E = acc->sum_weights / N;
- V = MMAX(acc->sum_weights_sqr / N - E*E, 0);
- SE = sqrt(V/N);
-
- estimate->E = E;
- estimate->SE = SE;
- }
-}
-
-static INLINE size_t
-htrdr_spectral_type_get_pixsz
- (const enum htrdr_spectral_type spectral_type,
- const enum htrdr_sensor_type sensor_type)
-{
- size_t sz = 0;
- if(sensor_type == HTRDR_SENSOR_RECTANGLE) {
- sz = sizeof(struct htrdr_pixel_flux);
- } else {
- ASSERT(sensor_type == HTRDR_SENSOR_CAMERA);
- switch(spectral_type) {
- case HTRDR_SPECTRAL_LW:
- case HTRDR_SPECTRAL_SW:
- sz = sizeof(struct htrdr_pixel_xwave);
- break;
- case HTRDR_SPECTRAL_SW_CIE_XYZ:
- sz = sizeof(struct htrdr_pixel_image);
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- }
- return sz;
-}
-
-static INLINE size_t
-htrdr_spectral_type_get_pixal
- (const enum htrdr_spectral_type spectral_type,
- const enum htrdr_sensor_type sensor_type)
-{
- size_t al = 0;
- if(sensor_type == HTRDR_SENSOR_RECTANGLE) {
- al = ALIGNOF(struct htrdr_pixel_flux);
- } else {
- ASSERT(sensor_type == HTRDR_SENSOR_CAMERA);
- switch(spectral_type) {
- case HTRDR_SPECTRAL_LW:
- case HTRDR_SPECTRAL_SW:
- al = ALIGNOF(struct htrdr_pixel_xwave);
- break;
- case HTRDR_SPECTRAL_SW_CIE_XYZ:
- al = ALIGNOF(struct htrdr_pixel_image);
- break;
- default: FATAL("Unreachable code.\n"); break;
- }
- }
- return al;
-}
-
-#endif /* HTRDR_SOLVE_H */
diff --git a/src/htrdr_spectral.c b/src/htrdr_spectral.c
@@ -1,79 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htrdr.h"
-#include "htrdr_spectral.h"
-
-res_T
-brightness_temperature
- (struct htrdr* htrdr,
- const double lambda_min,
- const double lambda_max,
- const double radiance, /* In W/m2/sr/m */
- double* temperature)
-{
- const size_t MAX_ITER = 100;
- const double epsilon_T = 1e-4; /* In K */
- const double epsilon_B = radiance * 1e-8;
- double T, T0, T1, T2;
- double B, B0;
- size_t i;
- res_T res = RES_OK;
- ASSERT(temperature && lambda_min <= lambda_max);
-
- /* Search for a brightness temperature whose radiance is greater than or
- * equal to the estimated radiance */
- T2 = 200;
- FOR_EACH(i, 0, MAX_ITER) {
- const double B2 = planck(lambda_min, lambda_max, T2);
- if(B2 >= radiance) break;
- T2 *= 2;
- }
- if(i >= MAX_ITER) { res = RES_BAD_OP; goto error; }
-
- B0 = T0 = T1 = 0;
- FOR_EACH(i, 0, MAX_ITER) {
- T = (T1+T2)*0.5;
- B = planck(lambda_min, lambda_max, T);
-
- if(B < radiance) {
- T1 = T;
- } else {
- T2 = T;
- }
-
- if(fabs(T-T0) < epsilon_T || fabs(B-B0) < epsilon_B)
- break;
-
- T0 = T;
- B0 = B;
- }
- if(i >= MAX_ITER) { res = RES_BAD_OP; goto error; }
-
- *temperature = T;
-
-exit:
- return res;
-error:
- htrdr_log_err(htrdr,
- "Could not compute the brightness temperature for the estimated radiance %g "
- "averaged over [%g, %g] nanometers.\n",
- radiance,
- lambda_min*1e9,
- lambda_max*1e9);
- goto exit;
-}
-
diff --git a/src/htrdr_spectral.h b/src/htrdr_spectral.h
@@ -1,137 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_SPECTRAL_H
-#define HTRDR_SPECTRAL_H
-
-#include <rsys/rsys.h>
-#include <rsys/math.h> /* PI support */
-
-#define HTRDR_SUN_TEMPERATURE 5778 /* In K */
-#define HTRDR_DEFAULT_LW_REF_TEMPERATURE 290 /* Default longwave temperature in K */
-
-enum htrdr_spectral_type {
- HTRDR_SPECTRAL_LW, /* Longwave */
- HTRDR_SPECTRAL_SW, /* Shortwave */
- HTRDR_SPECTRAL_SW_CIE_XYZ /* Shortwave wrt the CIE XYZ tristimulus */
-};
-
-struct htrdr;
-
-static INLINE double
-wiebelt(const double v)
-{
- int m;
- double w, v2, v4;
- /*.153989717364e+00;*/
- const double fifteen_over_pi_power_4 = 15.0/(PI*PI*PI*PI);
- const double z0 = 1.0/3.0;
- const double z1 = 1.0/8.0;
- const double z2 = 1.0/60.0;
- const double z4 = 1.0/5040.0;
- const double z6 = 1.0/272160.0;
- const double z8 = 1.0/13305600.0;
-
- if(v >= 2.) {
- w = 0;
- for(m=1; m<6 ;m++)
- w+=exp(-m*v)/(m*m*m*m) * (((m*v+3)*m*v+6)*m*v+6);
- w = w * fifteen_over_pi_power_4;
- } else {
- v2 = v*v;
- v4 = v2*v2;
- w = z0 - z1*v + z2*v2 - z4*v2*v2 + z6*v4*v2 - z8*v4*v4;
- w = 1. - fifteen_over_pi_power_4*v2*v*w;
- }
- ASSERT(w >= 0.0 && w <= 1.0);
- return w;
-}
-
-static INLINE double
-blackbody_fraction
- (const double lambda0, /* In meters */
- const double lambda1, /* In meters */
- const double temperature) /* In Kelvin */
-{
- const double C2 = 1.43877735e-2; /* m.K */
- double x0 = C2 / lambda0;
- double x1 = C2 / lambda1;
- double v0 = x0 / temperature;
- double v1 = x1 / temperature;
- double w0 = wiebelt(v0);
- double w1 = wiebelt(v1);
- return w1 - w0;
-}
-
-/* Return the Planck value in W/m^2/sr/m at a given wavelength */
-static INLINE double
-planck_monochromatic
- (const double lambda, /* In meters */
- const double temperature) /* In Kelvin */
-{
- const double c = 299792458; /* m/s */
- const double h = 6.62607015e-34; /* J.s */
- const double k = 1.380649e-23; /* J/K */
- const double lambda2 = lambda*lambda;
- const double lambda5 = lambda2*lambda2*lambda;
- const double B = ((2.0 * h * c*c) / lambda5) /* W/m^2/sr/m */
- / (exp(h*c/(lambda*k*temperature))-1.0);
- ASSERT(temperature > 0);
- return B;
-}
-
-/* Return the average Planck value in W/m^2/sr/m over the
- * [lambda_min, lambda_max] interval. */
-static INLINE double
-planck_interval
- (const double lambda_min, /* In meters */
- const double lambda_max, /* In meters */
- const double temperature) /* In Kelvin */
-{
- const double T2 = temperature*temperature;
- const double T4 = T2*T2;
- const double BOLTZMANN_CONSTANT = 5.6696e-8; /* W/m^2/K^4 */
- ASSERT(lambda_min < lambda_max && temperature > 0);
- return blackbody_fraction(lambda_min, lambda_max, temperature)
- * BOLTZMANN_CONSTANT * T4 / (PI * (lambda_max-lambda_min)); /* In W/m^2/sr/m */
-}
-
-/* Invoke planck_monochromatic or planck_interval whether the submitted
- * interval is null or not, respectively. The returned value is in W/m^2/sr/m */
-static FINLINE double
-planck
- (const double lambda_min, /* In meters */
- const double lambda_max, /* In meters */
- const double temperature) /* In Kelvin */
-{
- ASSERT(lambda_min <= lambda_max && temperature > 0);
- if(lambda_min == lambda_max) {
- return planck_monochromatic(lambda_min, temperature);
- } else {
- return planck_interval(lambda_min, lambda_max, temperature);
- }
-}
-
-extern LOCAL_SYM res_T
-brightness_temperature
- (struct htrdr* htrdr,
- const double lambda_min, /* In meters */
- const double lambda_max, /* In meters */
- /* Averaged over [lambda_min, lambda_max]. In W/m^2/sr/m */
- const double radiance,
- double* temperature);
-
-#endif /* HTRDR_SPECTRAL_H */
diff --git a/src/htrdr_sun.c b/src/htrdr_sun.c
@@ -1,144 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htrdr.h"
-#include "htrdr_c.h"
-#include "htrdr_sun.h"
-
-#include <rsys/algorithm.h>
-#include <rsys/double33.h>
-#include <rsys/ref_count.h>
-#include <rsys/math.h>
-
-#include <star/ssp.h>
-
-struct htrdr_sun {
- double half_angle; /* In radian */
- double cos_half_angle;
- double solid_angle; /* In sr; solid_angle = 2*PI*(1 - cos(half_angle)) */
- double frame[9];
- double temperature; /* In K */
-
- ref_T ref;
- struct htrdr* htrdr;
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static void
-release_sun(ref_T* ref)
-{
- struct htrdr_sun* sun;
- ASSERT(ref);
- sun = CONTAINER_OF(ref, struct htrdr_sun, ref);
- MEM_RM(sun->htrdr->allocator, sun);
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-res_T
-htrdr_sun_create(struct htrdr* htrdr, struct htrdr_sun** out_sun)
-{
- const double main_dir[3] = {0, 0, 1}; /* Default main sun direction */
- struct htrdr_sun* sun = NULL;
- res_T res = RES_OK;
- ASSERT(htrdr && out_sun);
-
- sun = MEM_CALLOC(htrdr->allocator, 1, sizeof(*sun));
- if(!sun) {
- htrdr_log_err(htrdr, "could not allocate the sun data structure.\n");
- res = RES_MEM_ERR;
- goto error;
- }
- ref_init(&sun->ref);
- sun->htrdr = htrdr;
- sun->half_angle = 4.6524e-3;
- sun->temperature = 5778;
- sun->cos_half_angle = cos(sun->half_angle);
- sun->solid_angle = 2*PI*(1-sun->cos_half_angle);
- d33_basis(sun->frame, main_dir);
-
-exit:
- *out_sun = sun;
- return res;
-error:
- if(sun) {
- htrdr_sun_ref_put(sun);
- sun = NULL;
- }
- goto exit;
-}
-
-void
-htrdr_sun_ref_get(struct htrdr_sun* sun)
-{
- ASSERT(sun);
- ref_get(&sun->ref);
-}
-
-void
-htrdr_sun_ref_put(struct htrdr_sun* sun)
-{
- ASSERT(sun);
- ref_put(&sun->ref, release_sun);
-}
-
-void
-htrdr_sun_set_direction(struct htrdr_sun* sun, const double dir[3])
-{
- ASSERT(sun && dir && d3_is_normalized(dir));
- d33_basis(sun->frame, dir);
-}
-
-double
-htrdr_sun_sample_direction
- (struct htrdr_sun* sun,
- struct ssp_rng* rng,
- double dir[3])
-{
- ASSERT(sun && rng && dir);
- ssp_ran_sphere_cap_uniform_local(rng, sun->cos_half_angle, dir, NULL);
- d33_muld3(dir, sun->frame, dir);
- return 1.0 / htrdr_sun_get_solid_angle(sun);
-}
-
-double
-htrdr_sun_get_solid_angle(const struct htrdr_sun* sun)
-{
- ASSERT(sun);
- return sun->solid_angle;
-}
-
-double
-htrdr_sun_get_radiance(const struct htrdr_sun* sun, const double wlen/*In nm*/)
-{
- return planck_monochromatic(wlen*1.e-9/*From nm to m*/, sun->temperature);
-}
-
-int
-htrdr_sun_is_dir_in_solar_cone(const struct htrdr_sun* sun, const double dir[3])
-{
- const double* main_dir;
- double dot;
- ASSERT(sun && dir && d3_is_normalized(dir));
- ASSERT(d3_is_normalized(sun->frame + 6));
- main_dir = sun->frame + 6;
- dot = d3_dot(dir, main_dir);
- return dot >= sun->cos_half_angle;
-}
-
diff --git a/src/htrdr_sun.h b/src/htrdr_sun.h
@@ -1,67 +0,0 @@
-/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com)
- * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTRDR_SUN_H
-#define HTRDR_SUN_H
-
-#include <rsys/rsys.h>
-
-/* Forward declaration */
-struct htrdr;
-struct htrdr_sun;
-struct ssp_rng;
-
-extern LOCAL_SYM res_T
-htrdr_sun_create
- (struct htrdr* htrdr,
- struct htrdr_sun** out_sun);
-
-extern LOCAL_SYM void
-htrdr_sun_ref_get
- (struct htrdr_sun* sun);
-
-extern LOCAL_SYM void
-htrdr_sun_ref_put
- (struct htrdr_sun* sun);
-
-/* Setup the direction *toward* the sun "center" */
-extern LOCAL_SYM void
-htrdr_sun_set_direction
- (struct htrdr_sun* sun,
- const double direction[3]); /* Must be normalized */
-
-/* Return a pdf of the sampled dir */
-extern LOCAL_SYM double
-htrdr_sun_sample_direction
- (struct htrdr_sun* sun,
- struct ssp_rng* rng,
- double dir[3]);
-
-extern LOCAL_SYM double
-htrdr_sun_get_solid_angle
- (const struct htrdr_sun* sun);
-
-extern LOCAL_SYM double /* W/m^2/sr/m */
-htrdr_sun_get_radiance
- (const struct htrdr_sun* sun,
- const double wavelength);
-
-extern LOCAL_SYM int
-htrdr_sun_is_dir_in_solar_cone
- (const struct htrdr_sun* sun,
- const double dir[3]);
-
-#endif /* HTRDR_SUN_H */