htrdr

Solving radiative transfer in heterogeneous media
git clone git://git.meso-star.fr/htrdr.git
Log | Files | Refs | README | LICENSE

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:
Asrc/atmosphere/htrdr_atmosphere.c | 537+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_args.c | 288+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_args.h.in | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_c.h | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/htrdr_compute_radiance_lw.c -> src/atmosphere/htrdr_atmosphere_compute_radiance_lw.c | 0
Asrc/atmosphere/htrdr_atmosphere_compute_radiance_sw.c | 504+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_draw_map.c | 492+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_ground.c | 757+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_ground.h | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_main.c | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_sun.c | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_sun.h | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr.c | 659+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr.h | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_accum.h | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_args.c | 419+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_args.h | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_buffer.c | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_buffer.h | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_c.h | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_camera.c | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_camera.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_cie_xyz.c | 399+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_cie_xyz.h | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_draw_map.c | 810+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_draw_map.h | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/htrdr_interface.h -> src/core/htrdr_interface.h | 0
Asrc/core/htrdr_log.c | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_log.h | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_main.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_materials.c | 454+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_materials.h | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_ran_wlen.c | 368+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_ran_wlen.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_rectangle.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_rectangle.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_sensor.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_sensor.h | 38++++++++++++++++++++++++++++++++++++++
Rsrc/htrdr_slab.c -> src/core/htrdr_slab.c | 0
Asrc/core/htrdr_slab.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_spectral.c | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_spectral.h | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/htrdr_version.h.in -> src/core/htrdr_version.h.in | 0
Dsrc/htrdr.c | 1034-------------------------------------------------------------------------------
Dsrc/htrdr.h | 163-------------------------------------------------------------------------------
Dsrc/htrdr_args.c | 644-------------------------------------------------------------------------------
Dsrc/htrdr_args.h.in | 127-------------------------------------------------------------------------------
Dsrc/htrdr_buffer.c | 167-------------------------------------------------------------------------------
Dsrc/htrdr_buffer.h | 74--------------------------------------------------------------------------
Dsrc/htrdr_c.h | 154-------------------------------------------------------------------------------
Dsrc/htrdr_camera.c | 151------------------------------------------------------------------------------
Dsrc/htrdr_camera.h | 52----------------------------------------------------
Dsrc/htrdr_cie_xyz.c | 396-------------------------------------------------------------------------------
Dsrc/htrdr_cie_xyz.h | 66------------------------------------------------------------------
Dsrc/htrdr_compute_radiance_sw.c | 497-------------------------------------------------------------------------------
Dsrc/htrdr_draw_map.c | 1207-------------------------------------------------------------------------------
Dsrc/htrdr_grid.c | 366-------------------------------------------------------------------------------
Dsrc/htrdr_grid.h | 71-----------------------------------------------------------------------
Dsrc/htrdr_ground.c | 753-------------------------------------------------------------------------------
Dsrc/htrdr_ground.h | 78------------------------------------------------------------------------------
Dsrc/htrdr_main.c | 91-------------------------------------------------------------------------------
Dsrc/htrdr_materials.c | 449-------------------------------------------------------------------------------
Dsrc/htrdr_materials.h | 67-------------------------------------------------------------------
Dsrc/htrdr_ran_wlen.c | 364-------------------------------------------------------------------------------
Dsrc/htrdr_ran_wlen.h | 54------------------------------------------------------
Dsrc/htrdr_rectangle.c | 142-------------------------------------------------------------------------------
Dsrc/htrdr_rectangle.h | 54------------------------------------------------------
Dsrc/htrdr_sensor.c | 136-------------------------------------------------------------------------------
Dsrc/htrdr_sensor.h | 48------------------------------------------------
Dsrc/htrdr_slab.h | 47-----------------------------------------------
Dsrc/htrdr_solve.h | 185-------------------------------------------------------------------------------
Dsrc/htrdr_spectral.c | 79-------------------------------------------------------------------------------
Dsrc/htrdr_spectral.h | 137-------------------------------------------------------------------------------
Dsrc/htrdr_sun.c | 144-------------------------------------------------------------------------------
Dsrc/htrdr_sun.h | 67-------------------------------------------------------------------
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 */