htrdr

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

commit 15fc38e1a53f458034471fb9e0e68caa82a80237
parent dfbf43fc51669a2ca706b3acd5ac0322dace7053
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Thu, 15 Apr 2021 15:33:50 +0200

Merge branch 'feature_combustion' into develop

Diffstat:
Mcmake/CMakeLists.txt | 2++
Acmake/combustion/CMakeLists.txt | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcmake/commands/CMakeLists.txt | 8+++++++-
Msrc/atmosphere/htrdr_atmosphere.c | 5+++--
Msrc/atmosphere/htrdr_atmosphere_c.h | 18+++++-------------
Msrc/atmosphere/htrdr_atmosphere_draw_map.c | 8+++++---
Asrc/combustion/htrdr_combustion.c | 448+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/combustion/htrdr_combustion.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/combustion/htrdr_combustion_args.c | 307+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/combustion/htrdr_combustion_args.h | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/combustion/htrdr_combustion_c.h | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/combustion/htrdr_combustion_compute_radiance_sw.c | 712+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/combustion/htrdr_combustion_draw_map.c | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/combustion/htrdr_combustion_laser.c | 266+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/combustion/htrdr_combustion_laser.h | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/combustion/htrdr_combustion_main.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/combustion/test_htrdr_combustion_laser.c | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/commands/htrdr_cmd.c | 4+++-
Asrc/commands/htrdr_combustion_cmd.c | 24++++++++++++++++++++++++
Msrc/core/htrdr_args.c | 125++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/core/htrdr_args.h.in | 24+++++++++++++++++++++---
Msrc/core/htrdr_buffer.h | 8++++++++
Msrc/core/htrdr_draw_map.c | 2+-
Msrc/core/htrdr_draw_map.h | 2--
Msrc/core/htrdr_ran_wlen.c | 5+++--
Msrc/core/htrdr_ran_wlen.h | 2+-
Msrc/core/htrdr_rectangle.c | 62+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/core/htrdr_rectangle.h | 17++++++++++++++++-
28 files changed, 2884 insertions(+), 62 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -17,6 +17,7 @@ cmake_minimum_required(VERSION 3.1) project(htrdr C) +enable_testing() set(HTRDR_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src) set(HTRDR_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}) @@ -32,6 +33,7 @@ set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) add_subdirectory(core) add_subdirectory(atmosphere) add_subdirectory(commands) +add_subdirectory(combustion) add_subdirectory(doc) ################################################################################ diff --git a/cmake/combustion/CMakeLists.txt b/cmake/combustion/CMakeLists.txt @@ -0,0 +1,103 @@ +# 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/>. + +cmake_minimum_required(VERSION 3.1) +project(htrdr-combustion) + +################################################################################ +# Check dependencies +################################################################################ +find_package(AtrSTM REQUIRED) +find_package(RCMake 0.3 REQUIRED) +find_package(RSys 0.11 REQUIRED) +find_package(Star3D 0.7.1 REQUIRED) +find_package(StarSF 0.6 REQUIRED) +find_package(StarSP 0.8 REQUIRED) +find_package(StarVX 0.1 REQUIRED) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) +include(rcmake) +include(rcmake_runtime) + +include_directories( + ${AtrSTM_INCLUDE_DIR} + ${RSys_INCLUDE_DIR} + ${Star3D_INCLUDE_DIR} + ${StarSF_INCLUDE_DIR} + ${StarSP_INCLUDE_DIR} + ${StarVX_INCLUDE_DIR} + ${HTRDR_BUILD_DIR} + ${HTRDR_SOURCE_DIR}) + +################################################################################ +# Configure and define targets +################################################################################ +set(HTRDR_COMBUSTION_FILES_SRC + htrdr_combustion.c + htrdr_combustion_args.c + htrdr_combustion_draw_map.c + htrdr_combustion_compute_radiance_sw.c + htrdr_combustion_laser.c + htrdr_combustion_main.c) + +set(HTRDR_COMBUSTION_FILES_INC + htrdr_combustion.h + htrdr_combustion_c.h + htrdr_combustion_args.h + htrdr_combustion_laser.h) + +# Prepend each file in the `HTRDR_FILES_<SRC|INC>' list by `HTRDR_SOURCE_DIR' +rcmake_prepend_path(HTRDR_COMBUSTION_FILES_SRC ${HTRDR_SOURCE_DIR}/combustion) +rcmake_prepend_path(HTRDR_COMBUSTION_FILES_INC ${HTRDR_SOURCE_DIR}/combustion) + +# Atmosphere library +add_library(htrdr-combustion SHARED + ${HTRDR_COMBUSTION_FILES_SRC} + ${HTRDR_COMBUSTION_FILES_INC}) +target_link_libraries(htrdr-combustion htrdr-core AtrSTM RSys Star3D StarSF StarSP) + +set_target_properties(htrdr-combustion PROPERTIES + DEFINE_SYMBOL HTRDR_SHARED_BUILD + VERSION ${VERSION} + SOVERSION ${VERSION_MAJOR}) + +################################################################################ +# Add tests +################################################################################ +if(NOT NO_TEST) + function(build_test _name) + add_executable(${_name} + ${HTRDR_SOURCE_DIR}/combustion/${_name}.c) + target_link_libraries(${_name} htrdr-combustion ${ARGN}) + endfunction() + + function(new_test _name) + build_test(${_name} ${ARGN}) + add_test(${_name} ${_name}) + endfunction() + + new_test(test_htrdr_combustion_laser) +endif() + +################################################################################ +# Define output & install directories +################################################################################ +install(TARGETS htrdr-core + ARCHIVE DESTINATION bin + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + diff --git a/cmake/commands/CMakeLists.txt b/cmake/commands/CMakeLists.txt @@ -30,7 +30,7 @@ include_directories(${RSys_INCLUDE_DIR}) # Configure and define targets ################################################################################ add_executable(htrdr_cmd ${HTRDR_SOURCE_DIR}/commands/htrdr_cmd.c) -target_link_libraries(htrdr_cmd htrdr-atmosphere) +target_link_libraries(htrdr_cmd htrdr-atmosphere htrdr-combustion) set_target_properties(htrdr_cmd PROPERTIES OUTPUT_NAME htrdr) @@ -40,6 +40,12 @@ target_link_libraries(htrdr_atmosphere_cmd htrdr-atmosphere) set_target_properties(htrdr_atmosphere_cmd PROPERTIES OUTPUT_NAME htrdr-atmosphere) +add_executable(htrdr_combustion_cmd + ${HTRDR_SOURCE_DIR}/commands/htrdr_combustion_cmd.c) +target_link_libraries(htrdr_combustion_cmd htrdr-combustion) +set_target_properties(htrdr_combustion_cmd PROPERTIES + OUTPUT_NAME htrdr-combustion) + ################################################################################ # Define output & install directories ################################################################################ diff --git a/src/atmosphere/htrdr_atmosphere.c b/src/atmosphere/htrdr_atmosphere.c @@ -391,7 +391,7 @@ htrdr_atmosphere_create if(!cmd->dump_volumetric_acceleration_structure) { - struct atmosphere_pixel_format pixfmt = ATMOSPHERE_PIXEL_FORMAT_NULL; + struct htrdr_pixel_format pixfmt = HTRDR_PIXEL_FORMAT_NULL; atmosphere_get_pixel_format(cmd, &pixfmt); /* Setup the buffer layout */ @@ -459,8 +459,9 @@ error: void atmosphere_get_pixel_format (const struct htrdr_atmosphere* cmd, - struct atmosphere_pixel_format* fmt) + struct htrdr_pixel_format* fmt) { + ASSERT(cmd && fmt); switch(cmd->sensor.type) { case HTRDR_SENSOR_RECTANGLE: fmt->size = sizeof(struct atmosphere_pixel_flux); diff --git a/src/atmosphere/htrdr_atmosphere_c.h b/src/atmosphere/htrdr_atmosphere_c.h @@ -36,14 +36,6 @@ enum atmosphere_radiance_cpnt_flag { | ATMOSPHERE_RADIANCE_DIFFUSE }; -struct atmosphere_pixel_format { - size_t size; /* In bytes */ - size_t alignment; /* Power of two, in Bytes */ -}; -#define ATMOSPHERE_PIXEL_FORMAT_NULL__ {0, 0} -static const struct atmosphere_pixel_format ATMOSPHERE_PIXEL_FORMAT_NULL = - ATMOSPHERE_PIXEL_FORMAT_NULL__; - struct atmosphere_pixel_xwave { struct htrdr_estimate radiance; /* In W/m^2/sr */ struct htrdr_estimate radiance_temperature; /* In K */ @@ -52,7 +44,7 @@ struct atmosphere_pixel_xwave { #define ATMOSPHERE_PIXEL_XWAVE_NULL__ { \ HTRDR_ESTIMATE_NULL__, /* Radiance */ \ HTRDR_ESTIMATE_NULL__, /* Radiance temperature */ \ - HTRDR_ACCUM_NULL /* Time */ \ + HTRDR_ACCUM_NULL__ /* Time */ \ } static const struct atmosphere_pixel_xwave ATMOSPHERE_PIXEL_XWAVE_NULL = ATMOSPHERE_PIXEL_XWAVE_NULL__; @@ -62,8 +54,8 @@ struct atmosphere_pixel_flux { struct htrdr_accum time; }; #define ATMOSPHERE_PIXEL_FLUX_NULL__ { \ - HTRDR_ACCUM_NULL, \ - HTRDR_ACCUM_NULL \ + HTRDR_ACCUM_NULL__, \ + HTRDR_ACCUM_NULL__ \ } static const struct atmosphere_pixel_flux ATMOSPHERE_PIXEL_FLUX_NULL = ATMOSPHERE_PIXEL_FLUX_NULL__; @@ -78,7 +70,7 @@ struct atmosphere_pixel_image { HTRDR_ESTIMATE_NULL__, /* X */ \ HTRDR_ESTIMATE_NULL__, /* Y */ \ HTRDR_ESTIMATE_NULL__, /* Z */ \ - HTRDR_ACCUM_NULL /* Time */ \ + HTRDR_ACCUM_NULL__ /* Time */ \ } static const struct atmosphere_pixel_image ATMOSPHERE_PIXEL_IMAGE_NULL = ATMOSPHERE_PIXEL_IMAGE_NULL__; @@ -129,7 +121,7 @@ struct htrdr_atmosphere { extern LOCAL_SYM void atmosphere_get_pixel_format (const struct htrdr_atmosphere* cmd, - struct atmosphere_pixel_format* fmt); + struct htrdr_pixel_format* fmt); extern LOCAL_SYM res_T atmosphere_draw_map diff --git a/src/atmosphere/htrdr_atmosphere_draw_map.c b/src/atmosphere/htrdr_atmosphere_draw_map.c @@ -165,6 +165,7 @@ draw_pixel_image case 2: wlen = htrdr_cie_xyz_sample_Z(cmd->cie, r0, r1, &pdf); break; default: FATAL("Unreachable code.\n"); break; } + pdf *= 1.e9; /* Transform the pdf from nm^-1 to m^-1 */ iband = htsky_find_spectral_band(cmd->sky, wlen); iquad = htsky_spectral_band_sample_quadrature(cmd->sky, r2, iband); @@ -174,7 +175,6 @@ draw_pixel_image ATMOSPHERE_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 */ @@ -250,6 +250,7 @@ draw_pixel_flux /* Sample a wavelength */ wlen = htrdr_ran_wlen_sample(cmd->ran_wlen, r0, r1, &band_pdf); + band_pdf *= 1.e9; /* Transform the pdf from nm^-1 to m^-1 */ /* Select the associated band and sample a quadrature point */ iband = htsky_find_spectral_band(cmd->sky, wlen); @@ -370,6 +371,7 @@ draw_pixel_xwave /* Sample a wavelength */ wlen = htrdr_ran_wlen_sample(cmd->ran_wlen, r0, r1, &band_pdf); + band_pdf *= 1.e9; /* Transform the pdf from nm^-1 to m^-1 */ /* Select the associated band and sample a quadrature point */ iband = htsky_find_spectral_band(cmd->sky, wlen); @@ -395,7 +397,7 @@ draw_pixel_xwave time_sub(&t0, time_current(&t1), &t0); usec = (double)time_val(&t0, TIME_NSEC) * 0.001; - /* Update the pixel accumulator of the current channel */ + /* Update the pixel accumulator */ radiance.sum_weights += weight; radiance.sum_weights_sqr += weight*weight; radiance.nweights += 1; @@ -540,7 +542,7 @@ dump_buffer struct htrdr_accum* flux_acc, /* May be NULL */ FILE* stream) { - struct atmosphere_pixel_format pixfmt; + struct htrdr_pixel_format pixfmt; struct htrdr_buffer_layout layout; size_t x, y; ASSERT(cmd && buf && stream); diff --git a/src/combustion/htrdr_combustion.c b/src/combustion/htrdr_combustion.c @@ -0,0 +1,448 @@ +/* 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 "combustion/htrdr_combustion.h" +#include "combustion/htrdr_combustion_args.h" +#include "combustion/htrdr_combustion_c.h" +#include "combustion/htrdr_combustion_laser.h" + +#include "core/htrdr.h" +#include "core/htrdr_camera.h" +#include "core/htrdr_log.h" +#include "core/htrdr_geometry.h" +#include "core/htrdr_materials.h" +#include "core/htrdr_rectangle.h" + +#include <astoria/atrstm.h> + +#include <star/ssf.h> + +#include <rsys/cstr.h> +#include <rsys/mem_allocator.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +release_phase_functions + (struct htrdr_combustion* cmd, + struct ssf_phase* phases[]) +{ + size_t i; + ASSERT(cmd); + + if(!phases) return; /* Nothing to release */ + + FOR_EACH(i, 0, htrdr_get_threads_count(cmd->htrdr)) { + if(phases[i]) { + SSF(phase_ref_put(phases[i])); + } + } + MEM_RM(htrdr_get_allocator(cmd->htrdr), phases); +} + +static res_T +setup_output + (struct htrdr_combustion* cmd, + const struct htrdr_combustion_args* args) +{ + const char* output_name = NULL; + res_T res = RES_OK; + ASSERT(cmd && args); + + if(htrdr_get_mpi_rank(cmd->htrdr) != 0) { + /* No output stream on non master processes */ + cmd->output = NULL; + output_name = "<null>"; + + } else if(!args->path_output) { + /* Write results to standard output when no destination file is defined */ + cmd->output = stdout; + output_name = "<stdout>"; + + } else { + /* Open the output stream */ + res = htrdr_open_output_stream(cmd->htrdr, args->path_output, + 0/*read*/, args->force_overwriting, &cmd->output); + if(res != RES_OK) goto error; + output_name = args->path_output; + } + + /* Setup the output name */ + str_set(&cmd->output_name, output_name); + if(res != RES_OK) { + htrdr_log_err(cmd->htrdr, + "Could not store the name of the output stream `%s' -- %s.\n", + output_name, res_to_cstr(res)); + goto error; + } + +exit: + return res; +error: + str_clear(&cmd->output_name); + if(cmd->output && cmd->output != stdout) { + CHK(fclose(cmd->output) == 0); + cmd->output = NULL; + } + goto exit; +} + +static res_T +setup_geometry + (struct htrdr_combustion* cmd, + const struct htrdr_combustion_args* args) +{ + res_T res = RES_OK; + + if(!args->geom.path_obj) goto exit; + ASSERT(args->geom.path_mats); + + res = htrdr_materials_create(cmd->htrdr, args->geom.path_mats, &cmd->mats); + if(res != RES_OK) goto error; + + res = htrdr_geometry_create + (cmd->htrdr, args->geom.path_obj, cmd->mats, &cmd->geom); + if(res != RES_OK) goto error; + +exit: + return res; +error: + if(cmd->mats) { htrdr_materials_ref_put(cmd->mats); cmd->mats = NULL; } + if(cmd->geom) { htrdr_geometry_ref_put(cmd->geom); cmd->geom = NULL; } + goto exit; +} + +static res_T +setup_camera + (struct htrdr_combustion* cmd, + const struct htrdr_combustion_args* args) +{ + double proj_ratio = 0; + ASSERT(cmd && args && args->image.definition[0] && args->image.definition[1]); + + proj_ratio = + (double)args->image.definition[0] + / (double)args->image.definition[1]; + + return htrdr_camera_create + (cmd->htrdr, + args->camera.position, + args->camera.target, + args->camera.up, + proj_ratio, + MDEG2RAD(args->camera.fov_y), + &cmd->camera); +} + +static res_T +setup_laser + (struct htrdr_combustion* cmd, + const struct htrdr_combustion_args* args) +{ + struct htrdr_combustion_laser_create_args laser_args = + HTRDR_COMBUSTION_LASER_CREATE_ARGS_DEFAULT; + ASSERT(cmd && args); + cmd->wavelength = args->wavelength; + laser_args.surface = args->laser; + laser_args.wavelength = args->wavelength; + laser_args.flux_density = args->laser_flux_density; + return htrdr_combustion_laser_create(cmd->htrdr, &laser_args, &cmd->laser); +} + +static res_T +setup_phase_functions + (struct htrdr_combustion* cmd, + const struct ssf_phase_type* phase_type, + struct ssf_phase** out_phases[]) +{ + struct mem_allocator* allocator = NULL; + struct ssf_phase** phases = NULL; + size_t nthreads; + size_t i; + res_T res = RES_OK; + ASSERT(cmd && phase_type && out_phases); + + nthreads = htrdr_get_threads_count(cmd->htrdr); + allocator = htrdr_get_allocator(cmd->htrdr); + + /* Allocate the list of per thread phase function */ + phases = MEM_CALLOC(allocator, nthreads, sizeof(*phases)); + if(!phases) { + htrdr_log_err(cmd->htrdr, + "Could not allocate the per thread RDG-FA phase function.\n"); + res = RES_MEM_ERR; + goto error; + } + + /* Create the per thread phase function */ + FOR_EACH(i, 0, nthreads) { + res = ssf_phase_create(allocator, phase_type, phases+i); + if(res != RES_OK) { + htrdr_log_err(cmd->htrdr, + "Could not create the phase function for the thread %lu -- %s.\n", + (unsigned long)i, res_to_cstr(res)); + goto error; + } + } + +exit: + *out_phases = phases; + return res; +error: + release_phase_functions(cmd, phases); + phases = NULL; + goto exit; +} + +static res_T +setup_buffer + (struct htrdr_combustion* cmd, + const struct htrdr_combustion_args* args) +{ + struct htrdr_pixel_format pixfmt = HTRDR_PIXEL_FORMAT_NULL; + res_T res = RES_OK; + ASSERT(cmd && args); + + if(cmd->dump_volumetric_acceleration_structure) goto exit; + + combustion_get_pixel_format(cmd, &pixfmt); + + /* Setup the buffer layout */ + cmd->buf_layout.width = args->image.definition[0]; + cmd->buf_layout.height = args->image.definition[1]; + cmd->buf_layout.pitch = args->image.definition[0] * pixfmt.size; + cmd->buf_layout.elmt_size = pixfmt.size; + cmd->buf_layout.alignment = pixfmt.alignment; + + /* Create the image buffer only on the master process; the image parts + * rendered by the others processes are gathered onto the master process */ + if(htrdr_get_mpi_rank(cmd->htrdr) != 0) goto exit; + + res = htrdr_buffer_create(cmd->htrdr, &cmd->buf_layout, &cmd->buf); + if(res != RES_OK) goto error; + +exit: + return res; +error: + if(cmd->buf) { htrdr_buffer_ref_put(cmd->buf); cmd->buf = NULL; } + goto exit; +} + +static res_T +setup_medium + (struct htrdr_combustion* cmd, + const struct htrdr_combustion_args* args) +{ + struct atrstm_args atrstm_args = ATRSTM_ARGS_DEFAULT; + res_T res = RES_OK; + ASSERT(cmd && args); + + /* Setup the semi-transaprent medium arguments */ + atrstm_args.sth_filename = args->path_tetra; + atrstm_args.atrtp_filename = args->path_therm_props; + atrstm_args.atrri_filename = args->path_refract_ids; + atrstm_args.cache_filename = args->path_cache; + atrstm_args.spectral_type = ATRSTM_SPECTRAL_SW; + atrstm_args.wlen_range[0] = args->wavelength; + atrstm_args.wlen_range[1] = args->wavelength; + atrstm_args.fractal_prefactor = args->fractal_prefactor; + atrstm_args.fractal_dimension = args->fractal_dimension; + atrstm_args.optical_thickness = args->optical_thickness; + atrstm_args.precompute_normals = args->precompute_normals; + atrstm_args.nthreads = args->nthreads; + atrstm_args.verbose = args->verbose; + + switch(args->grid.type) { + case HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_AUTO: + atrstm_args.auto_grid_definition = 1; + atrstm_args.auto_grid_definition_hint = args->grid.definition.hint; + break; + case HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_FIXED: + atrstm_args.auto_grid_definition = 0; + atrstm_args.grid_max_definition[0] = args->grid.definition.fixed[0]; + atrstm_args.grid_max_definition[1] = args->grid.definition.fixed[1]; + atrstm_args.grid_max_definition[2] = args->grid.definition.fixed[2]; + break; + default: FATAL("Unreachable code.\n"); break; + } + + /* Here we go! Create the semi-transparent medium */ + res = atrstm_create + (htrdr_get_logger(cmd->htrdr), + htrdr_get_allocator(cmd->htrdr), + &atrstm_args, + &cmd->medium); + if(res != RES_OK) goto error; + +exit: + return res; +error: + if(cmd->medium) { ATRSTM(ref_put(cmd->medium)); cmd->medium = NULL; } + goto exit; +} + +static res_T +dump_volumetric_acceleration_structure(struct htrdr_combustion* cmd) +{ + struct atrstm_dump_svx_octree_args args = ATRSTM_DUMP_SVX_OCTREE_ARGS_DEFAULT; + res_T res = RES_OK; + ASSERT(cmd); + + /* Nothing to do on non master process */ + if(htrdr_get_mpi_rank(cmd->htrdr) != 0) goto exit; + + htrdr_log(cmd->htrdr, "Write volumetric acceleration structure to '%s'.\n", + str_cget(&cmd->output_name)); + + res = atrstm_dump_svx_octree(cmd->medium, &args, cmd->output); + if(res != RES_OK) goto error; + +exit: + return res; +error: + goto exit; +} + +static void +combustion_release(ref_T* ref) +{ + struct htrdr_combustion* cmd = CONTAINER_OF(ref, struct htrdr_combustion, ref); + struct htrdr* htrdr = NULL; + ASSERT(ref); + + if(cmd->geom) htrdr_geometry_ref_put(cmd->geom); + if(cmd->mats) htrdr_materials_ref_put(cmd->mats); + if(cmd->medium) ATRSTM(ref_put(cmd->medium)); + if(cmd->camera) htrdr_camera_ref_put(cmd->camera); + if(cmd->laser) htrdr_combustion_laser_ref_put(cmd->laser); + if(cmd->buf) htrdr_buffer_ref_put(cmd->buf); + if(cmd->output && cmd->output != stdout) CHK(fclose(cmd->output) == 0); + release_phase_functions(cmd, cmd->rdgfa_phase_functions); + release_phase_functions(cmd, cmd->hg_phase_functions); + str_release(&cmd->output_name); + + htrdr = cmd->htrdr; + MEM_RM(htrdr_get_allocator(htrdr), cmd); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +htrdr_combustion_create + (struct htrdr* htrdr, + const struct htrdr_combustion_args* args, + struct htrdr_combustion** out_cmd) +{ + struct htrdr_combustion* cmd = NULL; + res_T res = RES_OK; + ASSERT(htrdr && args && out_cmd); + + cmd = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*cmd)); + if(!cmd) { + htrdr_log_err(htrdr, "Could not allocate the htrdr_combustion data.\n"); + res = RES_BAD_ARG; + goto error; + } + ref_init(&cmd->ref); + str_init(htrdr_get_allocator(htrdr), &cmd->output_name); + + /* Get the ownership on the htrdr structure */ + htrdr_ref_get(htrdr); + cmd->htrdr = htrdr; + + cmd->spp = args->image.spp; + cmd->dump_volumetric_acceleration_structure = + args->dump_volumetric_acceleration_structure; + + res = setup_output(cmd, args); + if(res != RES_OK) goto error; + res = setup_geometry(cmd, args); + if(res != RES_OK) goto error; + res = setup_camera(cmd, args); + if(res != RES_OK) goto error; + res = setup_laser(cmd, args); + if(res != RES_OK) goto error; + res = setup_phase_functions(cmd, &ssf_phase_rdgfa, &cmd->rdgfa_phase_functions); + if(res != RES_OK) goto error; + res = setup_phase_functions(cmd, &ssf_phase_hg, &cmd->hg_phase_functions); + if(res != RES_OK) goto error; + res = setup_buffer(cmd, args); + if(res != RES_OK) goto error; + res = setup_medium(cmd, args); + if(res != RES_OK) goto error; + +exit: + *out_cmd = cmd; + return res; +error: + if(cmd) { + htrdr_combustion_ref_put(cmd); + cmd = NULL; + } + goto exit; +} + +void +htrdr_combustion_ref_get(struct htrdr_combustion* cmd) +{ + ASSERT(cmd); + ref_get(&cmd->ref); +} + +void +htrdr_combustion_ref_put(struct htrdr_combustion* cmd) +{ + ASSERT(cmd); + ref_put(&cmd->ref, combustion_release); +} + +res_T +htrdr_combustion_run(struct htrdr_combustion* cmd) +{ + res_T res = RES_OK; + ASSERT(cmd); + + if(cmd->dump_volumetric_acceleration_structure) { + res = dump_volumetric_acceleration_structure(cmd); + if(res != RES_OK) goto error; + } else { + res = combustion_draw_map(cmd); + if(res != RES_OK) goto error; + } + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +void +combustion_get_pixel_format + (const struct htrdr_combustion* cmd, + struct htrdr_pixel_format* fmt) +{ + ASSERT(cmd && fmt); + (void)cmd; + fmt->size = sizeof(struct combustion_pixel); + fmt->alignment = ALIGNOF(struct combustion_pixel); +} diff --git a/src/combustion/htrdr_combustion.h b/src/combustion/htrdr_combustion.h @@ -0,0 +1,55 @@ +/* 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_COMBUSTION_H +#define HTRDR_COMBUSTION_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +struct htrdr; +struct htrdr_combustion; +struct htrdr_combustion_args; + +BEGIN_DECLS + +HTRDR_API res_T +htrdr_combustion_create + (struct htrdr* htrdr, + const struct htrdr_combustion_args* args, + struct htrdr_combustion** cmd); + +HTRDR_API void +htrdr_combustion_ref_get + (struct htrdr_combustion* cmd); + +HTRDR_API void +htrdr_combustion_ref_put + (struct htrdr_combustion* cmd); + +HTRDR_API res_T +htrdr_combustion_run + (struct htrdr_combustion* cmd); + +HTRDR_API int +htrdr_combustion_main + (int argc, + char** argv); + +END_DECLS + +#endif /* HTRDR_COMBUSTION_H */ diff --git a/src/combustion/htrdr_combustion_args.c b/src/combustion/htrdr_combustion_args.c @@ -0,0 +1,307 @@ +/* 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/>. */ + +#define _POSIX_C_SOURCE 200112L /* strtok_r support */ + +#include "combustion/htrdr_combustion_args.h" + +#include <rsys/cstr.h> + +#include <getopt.h> +#include <string.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +print_help(const char* cmd) +{ + ASSERT(cmd); + printf( +"Usage: %s [<options>] -m TETRAHEDRA -p THERMOPROPS -r REFRACT_IDS\n", + cmd); + printf( +"Render a monochromatic image within a sooting flame described according\n" +"to the RDG-FA theory and lightened by a laser source.\n\n"); + + printf( +" -C <camera> define the rendering point of view. Refer to the\n" +" man page for the list of camera options.\n"); + printf( +" -D FLUX_DENSITY\n" +" flux density of the laser in W/m^2. By default the\n" +" flux density is %g W/m^2.\n", + HTRDR_COMBUSTION_ARGS_DEFAULT.laser_flux_density); + printf( +" -d dump volumetric acceleration structures to OUTPUT\n" +" and exit.\n"); + printf( +" -F <fractal-coefs>\n" +" value of the fractal prefactor and fractal dimension\n" +" to use in the RDG-FA model. Refer to the man page\n" +" for the syntax of the <fractal-coefs> option. Default\n" +" fractal prefactor is %g and the default fractal\n" +" dimension is %g.\n", + HTRDR_COMBUSTION_ARGS_DEFAULT.fractal_prefactor, + HTRDR_COMBUSTION_ARGS_DEFAULT.fractal_dimension); + printf( +" -f overwrite the OUTPUT file if it already exists.\n"); + printf( +" -g <geometry> define the combustion chamber geometry. Refer to the\n" +" man page for the list of geometry options.\n"); + printf( +" -h display this help and exit.\n"); + printf( +" -i <image> define the image to compute. Refer to the man\n" +" page for the list of image options.\n"); + printf( +" -l <laser> define the geometry of the laser sheet. Refer to the\n" +" man page for the list of laser options.\n"); + printf( +" -m TETRAHEDRA path toward the volumetric mesh.\n"); + printf( +" -N precompute the tetrahedra normals.\n"); + printf( +" -O CACHE path of the cache file used to store/restore the\n" +" 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 THERMOPROPS path toward the thermodynamic properties.\n"); + printf( +" -r REFRACT_ID path toward the per wavelength refractive\n" +" indices.\n"); + printf( +" -T THRESHOLD optical thickness used as threshold during the octree\n" +" building. By default its value is %g.\n", + HTRDR_COMBUSTION_ARGS_DEFAULT.optical_thickness); + printf( +" -t NTHREADS hint on the number of threads to use. By default use\n" +" as many threads as CPU cores.\n"); + printf( +" -V <HINT|X,Y,Z>\n" +" definition of the volumetric acceleration grids along\n" +" the 3 axis. By default it is computed automatically\n" +" with a hint on the expected definition set to %u.\n", + HTRDR_COMBUSTION_ARGS_DEFAULT.grid.definition.hint); + printf( +" -v make the command verbose.\n"); + printf( +" -w WAVELENGTH wavelength definition of the laser in nanometer.\n" +" By default its value is %g.\n", + HTRDR_COMBUSTION_ARGS_DEFAULT.wavelength); + + printf("\n"); + + htrdr_fprint_copyright(cmd, stdout); + htrdr_fprint_license(cmd, stdout); +} + +static res_T +parse_grid_definition + (struct htrdr_combustion_args_grid_definition* grid_def, + const char* str) +{ + unsigned def[3]; + size_t len; + res_T res = RES_OK; + ASSERT(grid_def && str); + + res = cstr_to_list_uint(str, ',', def, &len, 3); + if(res == RES_OK && len == 2) res = RES_BAD_ARG; + if(res != RES_OK) { + fprintf(stderr, "Invalid grid definition `%s'.\n", str); + goto error; + } + + if(len == 1) { + if(!def[0]) { + fprintf(stderr, "Invalid null grid definition %u.\n", def[0]); + res = RES_BAD_ARG; + goto error; + } + grid_def->type = HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_AUTO; + grid_def->definition.hint = def[0]; + + } else { + 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; + } + grid_def->type = HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_FIXED; + grid_def->definition.fixed[0] = def[0]; + grid_def->definition.fixed[1] = def[1]; + grid_def->definition.fixed[2] = def[2]; + } + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_fractal_parameters(const char* str, void* ptr) +{ + char buf[128]; + struct htrdr_combustion_args* args = ptr; + char* key; + char* val; + char* ctx; + res_T res = RES_OK; + ASSERT(ptr && str); + + if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { + fprintf(stderr, + "Could not duplicate the fractal 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, "prefactor")) { + res = cstr_to_double(val, &args->fractal_prefactor); + if(res != RES_OK) goto error; + } else if(!strcmp(key, "dimension")) { + res = cstr_to_double(val, &args->fractal_dimension); + if(res != RES_OK) goto error; + } else { + fprintf(stderr, "Invalid fractal parameter `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_combustion_args_init + (struct htrdr_combustion_args* args, + int argc, + char** argv) +{ + int opt; + res_T res = RES_OK; + ASSERT(args && argc && argv); + + *args = HTRDR_COMBUSTION_ARGS_DEFAULT; + + while((opt = getopt(argc, argv, "C:D:dF:fg:hi:l:m:NO:o:p:r:T:t:V:vw:")) != -1) { + switch(opt) { + case 'C': + res = htrdr_args_camera_parse(&args->camera, optarg); + break; + case 'D': + res = cstr_to_double(optarg, &args->laser_flux_density); + if(res == RES_OK && args->laser_flux_density <= 0) res = RES_BAD_ARG; + break; + case 'd': + args->dump_volumetric_acceleration_structure = 1; + break; + case 'F': + res = cstr_parse_list(optarg, ':', parse_fractal_parameters, args); + break; + case 'f': args->force_overwriting = 1; break; + case 'g': + res = htrdr_args_geometry_parse(&args->geom, optarg); + break; + case 'h': + print_help(argv[0]); + htrdr_combustion_args_release(args); + args->quit = 1; + goto exit; + case 'i': + res = htrdr_args_image_parse(&args->image, optarg); + break; + case 'l': + res = htrdr_args_rectangle_parse(&args->laser, optarg); + break; + case 'm': args->path_tetra = optarg; break; + case 'N': args->precompute_normals = 1; break; + case 'O': args->path_cache = optarg; break; + case 'o': args->path_output = optarg; break; + case 'p': args->path_therm_props = optarg; break; + case 'r': args->path_refract_ids = 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->grid, optarg); + break; + case 'v': args->verbose = 1; break; + case 'w': + res = cstr_to_double(optarg, &args->wavelength); + 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->path_tetra) { + fprintf(stderr, "Missing the volumetric mesh -- option '-m'\n"); + res = RES_BAD_ARG; + goto error; + } + if(!args->path_therm_props) { + fprintf(stderr, "Missing the thermodynamic properties -- option '-p'\n"); + res = RES_BAD_ARG; + goto error; + } + if(!args->path_refract_ids) { + fprintf(stderr, "Missing the refractive indices -- option '-r'\n"); + res = RES_BAD_ARG; + goto error; + } + +exit: + return res; +error: + htrdr_combustion_args_release(args); + goto exit; +} + +void +htrdr_combustion_args_release(struct htrdr_combustion_args* args) +{ + ASSERT(args); + htrdr_args_geometry_free(&args->geom); + *args = HTRDR_COMBUSTION_ARGS_DEFAULT; +} + diff --git a/src/combustion/htrdr_combustion_args.h b/src/combustion/htrdr_combustion_args.h @@ -0,0 +1,127 @@ +/* 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_COMBUSTION_ARGS_H +#define HTRDR_COMBUSTION_ARGS_H + +#include "core/htrdr_args.h" +#include <rsys/rsys.h> + +#include <limits.h> /* UINT_MAX support */ + +enum htrdr_combustion_args_grid_definition_type { + HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_FIXED, + HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_AUTO, + HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_TYPES_COUNT__ +}; + +struct htrdr_combustion_args_grid_definition { + union { + unsigned hint; /* Hint on the grid definition to eval */ + unsigned fixed[3]; /* Fixed grid definition along the 3 axis */ + } definition; + enum htrdr_combustion_args_grid_definition_type type; +}; +#define HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_DEFAULT__ { \ + {256}, \ + HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_AUTO \ +} +static const struct htrdr_combustion_args_grid_definition +HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_DEFAULT = + HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_DEFAULT__; + +struct htrdr_combustion_args { + struct htrdr_args_geometry geom; /* Combustion chamber geometry */ + + const char* path_tetra; /* Volumetric mesh of the medium */ + const char* path_therm_props; /* Termodynamic properties of the medium */ + const char* path_refract_ids; /* Refractive indices in the medium */ + + const char* path_cache; /* Path of the file to store/restore cached data */ + const char* path_output; /* Name of the output file */ + + struct htrdr_args_camera camera; /* Pinhole Camera */ + + struct htrdr_args_rectangle laser; /* Laser surface emission */ + double wavelength; /* Wavelength of the laser in nanometer */ + double laser_flux_density; /* In W/m^2 */ + + struct htrdr_args_image image; /* Output Image */ + + /* RDG-FA parameters */ + double fractal_prefactor; + double fractal_dimension; + + struct htrdr_combustion_args_grid_definition grid; + + double optical_thickness; /* Threshold used during octree building */ + + /* Miscellaneous parameters */ + unsigned nthreads; /* Hint on the number of threads to use */ + int precompute_normals; /* Pre-compute the tetrahedra normals */ + int force_overwriting; + int dump_volumetric_acceleration_structure; + int verbose; /* Verbosity level */ + int quit; /* Stop the command */ +}; + +#define HTRDR_COMBUSTION_ARGS_DEFAULT__ { \ + HTRDR_ARGS_GEOMETRY_NULL__, \ + \ + NULL, /* Tetra path */ \ + NULL, /* Therm props path */ \ + NULL, /* Refractive ids path */ \ + \ + NULL, /* Cache path */ \ + NULL, /* Output path */ \ + \ + HTRDR_ARGS_CAMERA_DEFAULT__, /* Pinhole camera */ \ + \ + HTRDR_ARGS_RECTANGLE_DEFAULT__, /* Laser surface emission */ \ + 532, /* Wavelength in nanometer */ \ + 1, /* Flux density */ \ + \ + HTRDR_ARGS_IMAGE_DEFAULT__, /* Image */ \ + \ + 1.30, /* Gyration radius prefactor */ \ + 1.80, /* Fractal dimension */ \ + \ + HTRDR_COMBUSTION_ARGS_GRID_DEFINITION_DEFAULT__, \ + \ + 1, /* Optical thickness */ \ + \ + UINT_MAX, /* #threads */ \ + 0, /* Precompute normals */ \ + 0, /* Force overwriting */ \ + 0, /* dump volumetric acceleration structure */ \ + 0, /* Verbose flag */ \ + 0 /* Stop the command */ \ +} +static const struct htrdr_combustion_args HTRDR_COMBUSTION_ARGS_DEFAULT = + HTRDR_COMBUSTION_ARGS_DEFAULT__; + +extern LOCAL_SYM res_T +htrdr_combustion_args_init + (struct htrdr_combustion_args* args, + int argc, + char** argv); + +extern LOCAL_SYM void +htrdr_combustion_args_release + (struct htrdr_combustion_args* args); + +#endif /* HTRDR_COMBUSTION_ARGS_H */ diff --git a/src/combustion/htrdr_combustion_c.h b/src/combustion/htrdr_combustion_c.h @@ -0,0 +1,93 @@ +/* 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_COMBUSTION_C_H +#define HTRDR_COMBUSTION_C_H + +#include "core/htrdr_accum.h" +#include "core/htrdr_args.h" +#include "core/htrdr_buffer.h" + +#include <rsys/ref_count.h> +#include <rsys/str.h> + +/* Forward declarations */ +struct atrstm; +struct htrdr; +struct htrdr_camera; +struct htrdr_combustion_laser; +struct htrdr_geometry; +struct htrdr_materials; +struct htrdr_rectangle; +struct ssf_phase; +struct ssp_rng; + +struct combustion_pixel { + struct htrdr_estimate radiance; /* In W/m^2/sr */ + struct htrdr_accum time; /* In microseconds */ +}; +#define COMBUSTION_PIXEL_NULL__ { \ + HTRDR_ESTIMATE_NULL__, /* Radiance */ \ + HTRDR_ACCUM_NULL__, /* Time */ \ +} +static const struct combustion_pixel COMBUSTION_PIXEL_NULL = + COMBUSTION_PIXEL_NULL__; + +struct htrdr_combustion { + struct htrdr_geometry* geom; /* Combustion chamber geometry */ + struct htrdr_materials* mats; /* Materials of the combustion chamber */ + struct atrstm* medium; /* Semi transparent medium */ + + struct htrdr_camera* camera; /* Pinhole camera */ + struct htrdr_combustion_laser* laser; /* Laser sheet */ + double wavelength; /* Wavelength of the laser in nanometer */ + + struct ssf_phase** rdgfa_phase_functions; /* Per thread RDG-FA phase func */ + struct ssf_phase** hg_phase_functions; /* Per thread Henyey-Greenstein func */ + + struct htrdr_buffer_layout buf_layout; + struct htrdr_buffer* buf; /* NULL on non master processes */ + size_t spp; /* #samples per pixel */ + + FILE* output; /* Output stream */ + struct str output_name; /* Name of the output stream */ + int dump_volumetric_acceleration_structure; + + + ref_T ref; + struct htrdr* htrdr; +}; + +extern LOCAL_SYM void +combustion_get_pixel_format + (const struct htrdr_combustion* cmd, + struct htrdr_pixel_format* fmt); + +extern LOCAL_SYM res_T +combustion_draw_map + (struct htrdr_combustion* cmd); + +/* Return the shortwave radiance in W/m^2/sr */ +extern LOCAL_SYM double +combustion_compute_radiance_sw + (struct htrdr_combustion* cmd, + const size_t ithread, + struct ssp_rng* rng, + const double pos_in[3], + const double dir_in[3]); + +#endif /* HTRDR_COMBUSTION_C_H */ diff --git a/src/combustion/htrdr_combustion_compute_radiance_sw.c b/src/combustion/htrdr_combustion_compute_radiance_sw.c @@ -0,0 +1,712 @@ +/* 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 "combustion/htrdr_combustion_c.h" +#include "combustion/htrdr_combustion_laser.h" + +#include "core/htrdr.h" + +#include <astoria/atrstm.h> + +#include <star/ssf.h> +#include <star/ssp.h> + +#include <rsys/double2.h> +#include <rsys/double3.h> +#include <rsys/double4.h> +#include <rsys/dynamic_array.h> + +/* Define a position along the ray into the semi-transparent medium */ +struct position { + double distance; /* Distance up to the position */ + struct suvm_primitive prim; /* Volumetric primitive of the position */ + double bcoords[4]; /* Local coordinate of the position into `prim' */ +}; +#define POSITION_NULL__ { \ + DBL_MAX, /* Distance */ \ + SUVM_PRIMITIVE_NULL__, /* Primitive */ \ + {0, 0, 0, 0} /* Barycentric coordinates */ \ +} +static const struct position POSITION_NULL = POSITION_NULL__; + +/* Syntactic sugar to check if the position is valid */ +#define POSITION_NONE(Pos) ((Pos)->distance >= DBL_MAX) + +/* Common position but preferentially sampled within a limited range. Its + * associated ksi variable defines the correction of the weight due to the + * normalization of the sampling pdf, and the recursivity associated with the + * null-collision technique. */ +struct position_limited { + struct position position; + double ksi; +}; +#define POSITION_LIMITED_NULL__ {POSITION_NULL__, 1} +static const struct position_limited POSITION_LIMITED_NULL = + POSITION_LIMITED_NULL__; + +struct sample_position_context { + /* Input data */ + struct ssp_rng* rng; /* Random Number Generator */ + struct atrstm* medium; /* Semi-transparent medium */ + double wavelength; /* Wavelength to handel in nanometer */ + enum atrstm_radcoef radcoef; /* Radiative coef used to sample a position */ + double Ts; /* Sampled optical thickness */ + + /* Output data */ + struct position position; +}; +#define SAMPLE_POSITION_CONTEXT_NULL__ { \ + NULL, /* RNG */ \ + NULL, /* Medium */ \ + 0, /* Wavelength */ \ + ATRSTM_RADCOEFS_COUNT__, /* Radiative coefficient */ \ + 0, /* Optical thickness */ \ + POSITION_NULL__ \ +} +static const struct sample_position_context SAMPLE_POSITION_CONTEXT_NULL = + SAMPLE_POSITION_CONTEXT_NULL__; + +struct sample_scattering_limited_context { + /* Input data */ + struct ssp_rng* rng; /* Random number generator to use */ + struct atrstm* medium; /* Semi transparent medium */ + double wavelength; /* Wavelength to handle in nanometer */ + + /* Local parameters update during ray traversal */ + double ks_2hat; /* Smallest scattering upper-field over the ray range */ + double Tmax; /* Scattering optical thickness computed using ks_2hat */ + double Ume; /* Normalization of the pdf for the sampled optical thickness */ + double sampled_vox_collision_dst; /* Scattering path length */ + + /* Output data */ + struct position_limited position_limited; +}; +#define SAMPLE_SCATTERING_LIMITED_CONTEXT_NULL__ { \ + NULL, /* RNG */ \ + NULL, /* Medium */ \ + -1, /* Wavelength */ \ + -1, /* ks_2hat */ \ + -1, /* Tau max */ \ + -1, /* Ume */ \ + -1, /* Sampled collision dst */ \ + POSITION_LIMITED_NULL__ \ +} +static const struct sample_scattering_limited_context +SAMPLE_SCATTERING_LIMITED_CONTEXT_NULL = + SAMPLE_SCATTERING_LIMITED_CONTEXT_NULL__; + +/******************************************************************************* + * Sample a position along a ray into the inhomogeneous medium for a given + * radiative coefficient + ******************************************************************************/ +static int +sample_position_hit_filter + (const struct svx_hit* hit, + const double org[3], + const double dir[3], + const double range[2], + void* context) +{ + atrstm_radcoefs_svx_T radcoefs_svx; + struct atrstm_fetch_radcoefs_args fetch_raw_args; + struct atrstm_fetch_radcoefs_svx_voxel_args fetch_svx_args; + struct sample_position_context* ctx = context; + double k_min = 0; + double k_max = 0; + double traversal_dst = 0; + int pursue_traversal = 1; + ASSERT(hit && org && dir && range && ctx); + (void)range; + + /* Fetch the K<min|max> of the current traversed voxel */ + fetch_svx_args.voxel = hit->voxel; + fetch_svx_args.radcoefs_mask = BIT(ctx->radcoef); + fetch_svx_args.components_mask = ATRSTM_CPNTS_MASK_ALL; + fetch_svx_args.operations_mask = ATRSTM_SVX_OP_FLAG_MIN | ATRSTM_SVX_OP_FLAG_MAX; + ATRSTM(fetch_radcoefs_svx_voxel(ctx->medium, &fetch_svx_args, radcoefs_svx)); + + k_min = radcoefs_svx[ctx->radcoef][ATRSTM_SVX_OP_MIN]; + k_max = radcoefs_svx[ctx->radcoef][ATRSTM_SVX_OP_MAX]; + + /* Setup the constants of the 'fetch' function for the current voxel */ + fetch_raw_args.wavelength = ctx->wavelength; + fetch_raw_args.radcoefs_mask = BIT(ctx->radcoef); + fetch_raw_args.components_mask = ATRSTM_CPNTS_MASK_ALL; + fetch_raw_args.k_min[ctx->radcoef] = k_min; + fetch_raw_args.k_max[ctx->radcoef] = k_max; + + /* Initialised the already traversed distance to the distance from which the + * current ray enters into the current voxel */ + traversal_dst = hit->distance[0]; + + for(;;) { + /* Compute the optical thickness of the current leaf */ + const double vox_dst = hit->distance[1] - traversal_dst; + const double T = vox_dst * k_max; + + /* A collision occurs behind the voxel */ + if(ctx->Ts > T) { + ctx->Ts -= T; + pursue_traversal = 1; + break; + + /* A collision occurs _in_ the voxel */ + } else { + const double vox_collision_dst = ctx->Ts / k_max; + atrstm_radcoefs_T radcoefs; + double x[3]; + double k; + double r; + + /* Compute the traversed distance up to the challenged collision */ + traversal_dst += vox_collision_dst; + + /* Compute the world space position where a collision may occur */ + x[0] = org[0] + traversal_dst * dir[0]; + x[1] = org[1] + traversal_dst * dir[1]; + x[2] = org[2] + traversal_dst * dir[2]; + + /* Fetch the radiative coefficient at the collision position */ + ATRSTM(at(ctx->medium, x, &fetch_raw_args.prim, fetch_raw_args.bcoords)); + if(SUVM_PRIMITIVE_NONE(&fetch_raw_args.prim)) { + k = 0; + } else { + ATRSTM(fetch_radcoefs(ctx->medium, &fetch_raw_args, radcoefs)); + k = radcoefs[ctx->radcoef]; + } + + r = ssp_rng_canonical(ctx->rng); + + if(r > k/k_max) { /* Null collision */ + ctx->Ts = ssp_ran_exp(ctx->rng, 1); + } else { /* Real collision */ + /* Setup output data */ + ctx->position.distance = traversal_dst; + ctx->position.prim = fetch_raw_args.prim; + d4_set(ctx->position.bcoords, fetch_raw_args.bcoords); + + /* Stop ray traversal */ + pursue_traversal = 0; + break; + } + } + } + return pursue_traversal; +} + +static void +sample_position + (struct htrdr_combustion* cmd, + struct ssp_rng* rng, + const enum atrstm_radcoef radcoef, + const double pos[3], + const double dir[3], + const double range[2], + struct position* position) +{ + struct atrstm_trace_ray_args args = ATRSTM_TRACE_RAY_ARGS_DEFAULT; + struct sample_position_context sample_pos_ctx = SAMPLE_POSITION_CONTEXT_NULL; + struct svx_hit svx_hit = SVX_HIT_NULL; + double wlen = 0; + ASSERT(cmd && rng && pos && dir && range); + + wlen = htrdr_combustion_laser_get_wavelength(cmd->laser); + + /* Sample an optical thickness */ + sample_pos_ctx.Ts = ssp_ran_exp(rng, 1); + + /* Setup the arguments of the function invoked per voxel (i.e. the filter + * function) */ + sample_pos_ctx.rng = rng; + sample_pos_ctx.medium = cmd->medium; + sample_pos_ctx.wavelength = wlen; + sample_pos_ctx.radcoef = radcoef; + + /* Setup ray tracing arguments */ + d3_set(args.ray_org, pos); + d3_set(args.ray_dir, dir); + d2_set(args.ray_range, range); + args.filter = sample_position_hit_filter; + args.context = &sample_pos_ctx; + + /* Trace the ray into the heterogeneous medium */ + ATRSTM(trace_ray(cmd->medium, &args, &svx_hit)); + + if(SVX_HIT_NONE(&svx_hit)) { + /* No collision with the medium was found */ + *position = POSITION_NULL; + } else { + /* A collision occurs into the medium */ + *position = sample_pos_ctx.position; + } +} + +/******************************************************************************* + * Preferentially sample a scattering position into an inhomogeneous medium + * according to a limited range + ******************************************************************************/ +/* Find the constant Ks max (named ks_2hat) along the traced ray */ +static int +sample_scattering_limited_find_ks_2hat + (const struct svx_hit* hit, + const double org[3], + const double dir[3], + const double range[2], + void* context) +{ + struct sample_scattering_limited_context* ctx = context; + struct atrstm_fetch_radcoefs_svx_voxel_args fetch_svx_args; + atrstm_radcoefs_svx_T radcoefs_svx; + ASSERT(hit && org && dir && range && context); + (void)org, (void)dir; + + /* In all situations, initialise ks_2hat with the ks_max of the root node */ + if(hit->voxel.depth == 0) { + fetch_svx_args.voxel = hit->voxel; + fetch_svx_args.radcoefs_mask = ATRSTM_RADCOEF_FLAG_Ks; + fetch_svx_args.components_mask = ATRSTM_CPNTS_MASK_ALL; + fetch_svx_args.operations_mask = ATRSTM_SVX_OP_FLAG_MAX; + ATRSTM(fetch_radcoefs_svx_voxel(ctx->medium, &fetch_svx_args, radcoefs_svx)); + ctx->ks_2hat = radcoefs_svx[ATRSTM_RADCOEF_Ks][ATRSTM_SVX_OP_MAX]; + + /* Update ks_2hat with the ks_max of the current descending node until the ray + * range was no more included into this node */ + } else if(hit->distance[0] <= range[0] && range[1] <= hit->distance[1]) { + fetch_svx_args.voxel = hit->voxel; + fetch_svx_args.radcoefs_mask = ATRSTM_RADCOEF_FLAG_Ks; + fetch_svx_args.components_mask = ATRSTM_CPNTS_MASK_ALL; + fetch_svx_args.operations_mask = ATRSTM_SVX_OP_FLAG_MAX; + ATRSTM(fetch_radcoefs_svx_voxel(ctx->medium, &fetch_svx_args, radcoefs_svx)); + ctx->ks_2hat = radcoefs_svx[ATRSTM_RADCOEF_Ks][ATRSTM_SVX_OP_MAX]; + } + + /* Do not stop here: keep diving up to the leaves */ + return 0; +} + +static int +sample_scattering_limited_hit_filter + (const struct svx_hit* hit, + const double org[3], + const double dir[3], + const double range[2], + void* context) +{ + atrstm_radcoefs_svx_T radcoefs_svx; + struct atrstm_fetch_radcoefs_args fetch_raw_args; + struct atrstm_fetch_radcoefs_svx_voxel_args fetch_svx_args; + struct sample_scattering_limited_context* ctx = context; + double ks_min = 0; + double ks_max = 0; + double traversal_dst = 0; + int pursue_traversal = 1; + ASSERT(hit && org && dir && range && ctx); + (void)range; + + /* Fetch the Ks<min|max> of the current traversed voxel */ + fetch_svx_args.voxel = hit->voxel; + fetch_svx_args.radcoefs_mask = ATRSTM_RADCOEF_FLAG_Ks; + fetch_svx_args.components_mask = ATRSTM_CPNTS_MASK_ALL; + fetch_svx_args.operations_mask = ATRSTM_SVX_OP_FLAG_MIN | ATRSTM_SVX_OP_FLAG_MAX; + ATRSTM(fetch_radcoefs_svx_voxel(ctx->medium, &fetch_svx_args, radcoefs_svx)); + + ks_min = radcoefs_svx[ATRSTM_RADCOEF_Ks][ATRSTM_SVX_OP_MIN]; + ks_max = radcoefs_svx[ATRSTM_RADCOEF_Ks][ATRSTM_SVX_OP_MAX]; + ASSERT(ks_max <= ctx->ks_2hat); + + /* Setup the constants of the 'fetch' function for the current voxel */ + fetch_raw_args.wavelength = ctx->wavelength; + fetch_raw_args.radcoefs_mask = ATRSTM_RADCOEF_FLAG_Ks; + fetch_raw_args.components_mask = ATRSTM_CPNTS_MASK_ALL; + fetch_raw_args.k_min[ATRSTM_RADCOEF_Ks] = ks_min; + fetch_raw_args.k_max[ATRSTM_RADCOEF_Ks] = ks_max; + + /* Initialised the already traversed distance to the distance from which the + * current ray enters into the current voxel */ + traversal_dst = hit->distance[0]; + + /* First traversed leaf */ + if(ctx->sampled_vox_collision_dst < 0) { + ctx->Tmax = (range[1] - range[0]) * ctx->ks_2hat; + ctx->Ume = 1 - exp(-ctx->Tmax); + } + + for(;;) { + atrstm_radcoefs_T radcoefs; + double vox_dst; + double tau; + double ks; + double r; + + /* Compute the remaining distance to traverse in the current voxel */ + vox_dst = hit->distance[1] - traversal_dst; + + /* A collision distance was not already sampled */ + if(ctx->sampled_vox_collision_dst < 0) { + r = ssp_rng_canonical(ctx->rng); + tau = -log(1.0-r*(1.0-exp(-ctx->Tmax))); + ctx->sampled_vox_collision_dst = tau / ctx->ks_2hat; + + /* Update the ksi output data */ + ctx->position_limited.ksi *= ctx->Ume; + } + + /* Compute the traversed distance up to the challenged collision */ + traversal_dst = traversal_dst + ctx->sampled_vox_collision_dst; + + /* The collision to challenge lies behind the current voxel */ + if(traversal_dst > hit->distance[1]) { + ctx->sampled_vox_collision_dst -= vox_dst; + pursue_traversal = 1; + break; + } + ASSERT(traversal_dst >= hit->distance[0]); + + r = ssp_rng_canonical(ctx->rng); + if(r >= ks_max / ctx->ks_2hat) { /* Null collision */ + ctx->sampled_vox_collision_dst = -1; + } else { /* Collision with ks_max */ + double x[3]; + + /* Compute the world space position where a collision may occur */ + x[0] = org[0] + traversal_dst * dir[0]; + x[1] = org[1] + traversal_dst * dir[1]; + x[2] = org[2] + traversal_dst * dir[2]; + + /* Fetch the radiative coefficient at the collision position */ + ATRSTM(at(ctx->medium, x, &fetch_raw_args.prim, fetch_raw_args.bcoords)); + if(SUVM_PRIMITIVE_NONE(&fetch_raw_args.prim)) { + ks = 0; + } else { + ATRSTM(fetch_radcoefs(ctx->medium, &fetch_raw_args, radcoefs)); + ks = radcoefs[ATRSTM_RADCOEF_Ks]; + } + + r = ssp_rng_canonical(ctx->rng); + + if(r >= ks/ks_max) { /* Null collision */ + ctx->sampled_vox_collision_dst = -1; + } else { /* Real collision */ + /* Setup output data */ + ctx->position_limited.position.distance = traversal_dst; + ctx->position_limited.position.prim = fetch_raw_args.prim; + d4_set(ctx->position_limited.position.bcoords, fetch_raw_args.bcoords); + + /* Stop ray traversal */ + pursue_traversal = 0; + break; + } + } + } + return pursue_traversal; +} + +static void +sample_scattering_limited + (struct htrdr_combustion* cmd, + struct ssp_rng* rng, + const double pos[3], + const double dir[3], + const double range[2], + struct position_limited* position) +{ + struct atrstm_trace_ray_args args = ATRSTM_TRACE_RAY_ARGS_DEFAULT; + struct sample_scattering_limited_context sample_scattering_limited_ctx = + SAMPLE_SCATTERING_LIMITED_CONTEXT_NULL; + struct svx_hit svx_hit = SVX_HIT_NULL; + double wlen = 0; + ASSERT(cmd && rng && pos && dir && position); + + wlen = htrdr_combustion_laser_get_wavelength(cmd->laser); + + /* Setup the trace ray context */ + sample_scattering_limited_ctx.rng = rng; + sample_scattering_limited_ctx.medium = cmd->medium; + sample_scattering_limited_ctx.wavelength = wlen; + sample_scattering_limited_ctx.ks_2hat = 0; + sample_scattering_limited_ctx.Tmax = -1; + sample_scattering_limited_ctx.Ume = -1; + sample_scattering_limited_ctx.sampled_vox_collision_dst = -1; + sample_scattering_limited_ctx.position_limited = POSITION_LIMITED_NULL; + + /* Setup the input arguments for the ray tracing into the medium */ + d3_set(args.ray_org, pos); + d3_set(args.ray_dir, dir); + d2_set(args.ray_range, range); + args.challenge = sample_scattering_limited_find_ks_2hat; + args.filter = sample_scattering_limited_hit_filter; + args.context = &sample_scattering_limited_ctx; + + /* Trace the ray into the medium to compute the transmissivity */ + ATRSTM(trace_ray(cmd->medium, &args, &svx_hit)); + + if(SVX_HIT_NONE(&svx_hit)) { + /* No scattering event was found */ + *position = POSITION_LIMITED_NULL; + } else { + /* A scattering event occurs into the medium */ + *position = sample_scattering_limited_ctx.position_limited; + } +} + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static double +transmissivity + (struct htrdr_combustion* cmd, + struct ssp_rng* rng, + const enum atrstm_radcoef radcoef, + const double pos[3], + const double dir[3], + const double range[2]) +{ + struct position position = POSITION_NULL; + + /* Degenerated range => no attenuation along dir */ + if(range[1] <= range[0]) return 1; + + sample_position(cmd, rng, radcoef, pos, dir, range, &position); + + if(POSITION_NONE(&position)) { + return 1; /* No collision with the medium */ + } else { + return 0; /* A collision occurs */ + } +} + +/* This function computes the contribution of the in-laser scattering for the + * current scattering position of the reverse optical path 'pos' and current + * direction 'dir'. One contribution has to be computed for each scattering + * position in order to compute the value of the Monte-Carlo weight for the + * optical path (weight of the statistical realization). */ +static double +laser_once_scattered + (struct htrdr_combustion* cmd, + const size_t ithread, + struct ssp_rng* rng, + const double pos[3], + const double dir[3]) +{ + /* RDG-FA phase function */ + struct atrstm_rdgfa rdgfa_param = ATRSTM_RDGFA_NULL; + struct atrstm_fetch_rdgfa_args fetch_rdgfa_args = + ATRSTM_FETCH_RDGFA_ARGS_DEFAULT; + struct ssf_phase_rdgfa_setup_args setup_rdgfa_args = + SSF_PHASE_RDGFA_SETUP_ARGS_DEFAULT; + struct ssf_phase* phase = NULL; + + /* Scattering position into the laser sheet */ + struct position_limited sc_sample = POSITION_LIMITED_NULL; + double xsc[3] = {0, 0, 0}; /* World space position */ + + /* The transmissivities to compute */ + double Tr_ext_pos_xin = 0; + double Tr_ext_xsc_lse = 0; + double Tr_abs_xin_xsc = 0; + + /* Laser data */ + double laser_hit_dst[2] = {0, 0}; + double laser_flux_density = 0; /* In W/m^2 */ + double wlen = 0; /* In nm */ + + /* Miscellaneous varbale */ + double wi[3] = {0, 0, 0}; + double wo[3] = {0, 0, 0}; + double range[2] = {0, DBL_MAX}; + double R = 0; + + /* Radiance to compute in W/m^2/sr */ + double L = 0; + + ASSERT(cmd && rng && pos && dir); + + /* Fetch the laser wavelength */ + wlen = htrdr_combustion_laser_get_wavelength(cmd->laser); + + /* Find the intersections along dir with the laser sheet */ + htrdr_combustion_laser_trace_ray(cmd->laser, pos, dir, range, laser_hit_dst); + + /* No intersection with the laser sheet => no laser contribution */ + if(HTRDR_COMBUSTION_LASER_HIT_NONE(laser_hit_dst)) return 0; + + /* Compute the transmissivity from 'pos' to 'xin' */ + range[0] = 0; + range[1] = laser_hit_dst[0]; + Tr_ext_pos_xin = transmissivity(cmd, rng, ATRSTM_RADCOEF_Kext, pos, dir, range); + if(Tr_ext_pos_xin == 0) return 0; /* no laser contribution */ + + /* Sample the scattering position into the laser sheet */ + range[0] = laser_hit_dst[0]; + range[1] = laser_hit_dst[1]; + sample_scattering_limited(cmd, rng, pos, dir, range, &sc_sample); + if(POSITION_NONE(&sc_sample.position)) return 0; /* No laser contribution */ + ASSERT(laser_hit_dst[0] <= sc_sample.position.distance); + ASSERT(laser_hit_dst[1] >= sc_sample.position.distance); + + /* Compute the transmissivity from 'xin' to 'xsc' */ + range[0] = laser_hit_dst[0]; /* <=> xin */ + range[1] = sc_sample.position.distance; /* <=> xsc */ + Tr_abs_xin_xsc = transmissivity(cmd, rng, ATRSTM_RADCOEF_Ka, pos, dir, range); + if(Tr_abs_xin_xsc == 0) return 0; /* No laser contribution */ + + /* Compute the scattering position */ + xsc[0] = pos[0] + dir[0] * sc_sample.position.distance; + xsc[1] = pos[1] + dir[1] * sc_sample.position.distance; + xsc[2] = pos[2] + dir[2] * sc_sample.position.distance; + + /* Retrieve the direction toward the laser surface emission */ + htrdr_combustion_laser_get_direction(cmd->laser, wi); + d3_minus(wi, wi); + + /* Compute the transmissivity from xsc to the laser surface emission */ + range[0] = 0; + range[1] = htrdr_combustion_laser_compute_surface_plane_distance + (cmd->laser, xsc); + ASSERT(range[1] >= 0); + Tr_ext_xsc_lse = transmissivity(cmd, rng, ATRSTM_RADCOEF_Kext, xsc, wi, range); + if(Tr_ext_xsc_lse == 0) return 0; /* No laser contribution */ + + /* Retrieve the RDG-FA phase function parameters from the semi transparent + * medium */ + fetch_rdgfa_args.wavelength = wlen; + fetch_rdgfa_args.prim = sc_sample.position.prim; + fetch_rdgfa_args.bcoords[0] = sc_sample.position.bcoords[0]; + fetch_rdgfa_args.bcoords[1] = sc_sample.position.bcoords[1]; + fetch_rdgfa_args.bcoords[2] = sc_sample.position.bcoords[2]; + fetch_rdgfa_args.bcoords[3] = sc_sample.position.bcoords[3]; + ATRSTM(fetch_rdgfa(cmd->medium, &fetch_rdgfa_args, &rdgfa_param)); + + /* Setup the RDG-FA phase function */ + phase = cmd->rdgfa_phase_functions[ithread]; + setup_rdgfa_args.wavelength = rdgfa_param.wavelength; + setup_rdgfa_args.fractal_dimension = rdgfa_param.fractal_dimension; + setup_rdgfa_args.gyration_radius = rdgfa_param.gyration_radius; + SSF(phase_rdgfa_setup(phase, &setup_rdgfa_args)); + + /* Evaluate the phase function at the scattering position */ + d3_minus(wo, dir); /* Ensure SSF convention */ + R = ssf_phase_eval(phase, wo, wi); /* In sr^-1 */ + + laser_flux_density = htrdr_combustion_laser_get_flux_density(cmd->laser); + + L = Tr_ext_pos_xin + * Tr_abs_xin_xsc + * sc_sample.ksi * R + * Tr_ext_xsc_lse + * laser_flux_density; + return L; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +extern LOCAL_SYM double +combustion_compute_radiance_sw + (struct htrdr_combustion* cmd, + const size_t ithread, + struct ssp_rng* rng, + const double pos_in[3], + const double dir_in[3]) +{ + /* RDG-FA phase function */ + struct atrstm_rdgfa rdgfa_param = ATRSTM_RDGFA_NULL; + struct atrstm_fetch_rdgfa_args fetch_rdgfa_args = + ATRSTM_FETCH_RDGFA_ARGS_DEFAULT; + struct ssf_phase_rdgfa_setup_args setup_rdgfa_args = + SSF_PHASE_RDGFA_SETUP_ARGS_DEFAULT; + struct ssf_phase* phase = NULL; + + /* Transmissivity between the probe position (i.e. 'pos_in') and the current + * scattering position over the reverse scattering path */ + double Tr_abs = 1; + + /* Monte carlo weight of the simulated optical path */ + double weight = 0; + + /* Miscellaneous variables */ + double pos[3] = {0, 0, 0}; + double dir[3] = {0, 0, 0}; + double range[2] = {0, DBL_MAX}; + double wlen = 0; + ASSERT(cmd && rng && pos_in && dir_in); + + d3_set(pos, pos_in); + d3_set(dir, dir_in); + d2(range, 0, FLT_MAX); + + wlen = htrdr_combustion_laser_get_wavelength(cmd->laser); + + Tr_abs = 1; + weight = 0; + + for(;;) { + struct position scattering = POSITION_NULL; + double laser_sheet_emissivity = 0; /* In W/m^2/sr */ + double Tr_abs_pos_xsc = 0; + double wo[3]; + double wi[3]; + + /* Handle the laser contribution */ + laser_sheet_emissivity = laser_once_scattered(cmd, ithread, rng, pos, dir); + weight += Tr_abs * laser_sheet_emissivity; + + /* Sample a scattering position */ + sample_position(cmd, rng, ATRSTM_RADCOEF_Ks, pos, dir, range, &scattering); + if(POSITION_NONE(&scattering)) break; + + /* Compute the absorption transmissivity */ + range[0] = 0; + range[1] = scattering.distance; + Tr_abs_pos_xsc = transmissivity(cmd, rng, ATRSTM_RADCOEF_Ka, pos, dir, range); + if(Tr_abs_pos_xsc == 0) break; + + /* Update the overall absorption transmissivity of the optical path */ + Tr_abs *= Tr_abs_pos_xsc; + + /* Update the position of the optical path */ + pos[0] = pos[0] + dir[0] * scattering.distance; + pos[1] = pos[1] + dir[1] * scattering.distance; + pos[2] = pos[2] + dir[2] * scattering.distance; + + /* Retrieve the RDG-FA phase function parameters */ + fetch_rdgfa_args.wavelength = wlen; + fetch_rdgfa_args.prim = scattering.prim; + fetch_rdgfa_args.bcoords[0] = scattering.bcoords[0]; + fetch_rdgfa_args.bcoords[1] = scattering.bcoords[1]; + fetch_rdgfa_args.bcoords[2] = scattering.bcoords[2]; + fetch_rdgfa_args.bcoords[3] = scattering.bcoords[3]; + ATRSTM(fetch_rdgfa(cmd->medium, &fetch_rdgfa_args, &rdgfa_param)); + + /* Setup the RDG-FA phase function corresponding to the scattering event */ + phase = cmd->rdgfa_phase_functions[ithread]; + setup_rdgfa_args.wavelength = rdgfa_param.wavelength; + setup_rdgfa_args.fractal_dimension = rdgfa_param.fractal_dimension; + setup_rdgfa_args.gyration_radius = rdgfa_param.gyration_radius; + SSF(phase_rdgfa_setup(phase, &setup_rdgfa_args)); + + /* Sample a new optical path direction from the phase function */ + d3_minus(wo, dir); /* Ensure SSF convention */ + ssf_phase_sample(phase, rng, wo, wi, NULL); + + /* Update the optical path direction */ + dir[0] = wi[0]; + dir[1] = wi[1]; + dir[2] = wi[2]; + } + return weight; +} diff --git a/src/combustion/htrdr_combustion_draw_map.c b/src/combustion/htrdr_combustion_draw_map.c @@ -0,0 +1,186 @@ +/* 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 "combustion/htrdr_combustion_c.h" + +#include "core/htrdr_accum.h" +#include "core/htrdr_camera.h" +#include "core/htrdr_draw_map.h" +#include "core/htrdr_log.h" + +#include <star/ssp.h> + +#include <rsys/clock_time.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +draw_pixel + (struct htrdr* htrdr, + const struct htrdr_draw_pixel_args* args, + void* data) +{ + struct htrdr_accum radiance = HTRDR_ACCUM_NULL; + struct htrdr_accum time = HTRDR_ACCUM_NULL; + struct htrdr_combustion* cmd = NULL; + struct combustion_pixel* pixel = NULL; + size_t isamp; + ASSERT(htrdr && htrdr_draw_pixel_args_check(args) && data); + (void)htrdr; + + cmd = args->context; + pixel = data; + + 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 usec; + + /* 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->camera, pix_samp, ray_org, ray_dir); + + /* Backward trace the path */ + weight = combustion_compute_radiance_sw(cmd, args->ithread, args->rng, + ray_org, ray_dir); + + /* 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 */ + 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 time */ + pixel->time = time; +} + +static void +dump_pixel + (const struct combustion_pixel* pix, + struct htrdr_accum* time_acc, /* May be NULL */ + FILE* stream) +{ + struct htrdr_estimate time = HTRDR_ESTIMATE_NULL; + ASSERT(pix && stream && pix->time.nweights); + + htrdr_accum_get_estimation(&pix->time, &time); + + fprintf(stream, "%g %g 0 0 0 0 %g %g\n", + pix->radiance.E, pix->radiance.SE, time.E,time.SE); + + if(time_acc) { + time_acc->sum_weights += pix->time.sum_weights; + time_acc->sum_weights_sqr += pix->time.sum_weights_sqr; + time_acc->nweights += pix->time.nweights; + } +} + +static res_T +dump_buffer + (struct htrdr_combustion* cmd, + struct htrdr_buffer* buf, + struct htrdr_accum* time_acc, /* May be NULL */ + FILE* stream) +{ + struct htrdr_pixel_format pixfmt; + struct htrdr_buffer_layout layout; + size_t x, y; + ASSERT(cmd && buf && stream); + + combustion_get_pixel_format(cmd, &pixfmt); + htrdr_buffer_get_layout(buf, &layout); + CHK(pixfmt.size == layout.elmt_size); + + fprintf(stream, "%lu %lu\n", layout.width, layout.height); + + if(time_acc) *time_acc = HTRDR_ACCUM_NULL; + + FOR_EACH(y, 0, layout.height) { + FOR_EACH(x, 0, layout.width) { + const struct combustion_pixel* pix = htrdr_buffer_at(buf, x, y); + ASSERT(IS_ALIGNED(pix, pixfmt.alignment)); + dump_pixel(pix, time_acc, stream); + } + fprintf(stream, "\n"); + } + return RES_OK; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +combustion_draw_map(struct htrdr_combustion* cmd) +{ + struct htrdr_draw_map_args args = HTRDR_DRAW_MAP_ARGS_NULL; + struct htrdr_accum path_time_acc = HTRDR_ACCUM_NULL; + struct htrdr_estimate path_time = HTRDR_ESTIMATE_NULL; + res_T res = RES_OK; + ASSERT(cmd); + + /* Setup the draw map input arguments */ + args.draw_pixel = draw_pixel; + args.buffer_layout = cmd->buf_layout; + args.spp = cmd->spp; + args.context = cmd; + + 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_get_mpi_rank(cmd->htrdr) != 0) goto exit; + + /* Write buffer to output */ + res = dump_buffer(cmd, cmd->buf, &path_time_acc, 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); + +exit: + return res; +error: + goto exit; +} + diff --git a/src/combustion/htrdr_combustion_laser.c b/src/combustion/htrdr_combustion_laser.c @@ -0,0 +1,266 @@ +/* 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 "combustion/htrdr_combustion_laser.h" + +#include "core/htrdr.h" +#include "core/htrdr_log.h" +#include "core/htrdr_rectangle.h" + +#include <rsys/cstr.h> +#include <rsys/double33.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> + +struct htrdr_combustion_laser { + struct htrdr_rectangle* surface; /* Surface emission */ + double world2local[12]; + double low_ls[3], upp_ls[3]; /* Local space AABB */ + double flux_density; /* In W/m^2 */ + double wavelength; /* In nm */ + ref_T ref; + struct htrdr* htrdr; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +laser_release(ref_T* ref) +{ + struct htrdr_combustion_laser* laser; + struct htrdr* htrdr; + ASSERT(ref); + laser = CONTAINER_OF(ref, struct htrdr_combustion_laser, ref); + if(laser->surface) htrdr_rectangle_ref_put(laser->surface); + htrdr = laser->htrdr; + MEM_RM(htrdr_get_allocator(htrdr), laser); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_combustion_laser_create + (struct htrdr* htrdr, + const struct htrdr_combustion_laser_create_args* args, + struct htrdr_combustion_laser** out_laser) +{ + struct htrdr_combustion_laser* laser = NULL; + res_T res = RES_OK; + ASSERT(htrdr && args && out_laser); + + if(args->flux_density <= 0) { + htrdr_log_err(htrdr, "Invalid flux density %g W.m^2\n", args->flux_density); + res = RES_BAD_ARG; + goto error; + } + + if(args->wavelength <= 0) { + htrdr_log_err(htrdr, "Invalid wavelength %g nm\n", args->wavelength); + res = RES_BAD_ARG; + goto error; + } + + laser = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*laser)); + if(!laser) { + htrdr_log_err(htrdr, "Could not allocate the laser data structure.\n"); + res = RES_MEM_ERR; + goto error; + } + ref_init(&laser->ref); + htrdr_ref_get(htrdr); + laser->htrdr = htrdr; + laser->flux_density = args->flux_density; + laser->wavelength = args->wavelength; + + res = htrdr_rectangle_create + (laser->htrdr, + args->surface.size, + args->surface.position, + args->surface.target, + args->surface.up, + &laser->surface); + if(res != RES_OK) { + htrdr_log_err(htrdr, "Could not create the laser surface emission -- %s.\n", + res_to_cstr(res)); + goto error; + } + + htrdr_rectangle_get_transform_inverse(laser->surface, laser->world2local); + + /* Define the laser sheet AABB in local space */ + laser->upp_ls[0] = args->surface.size[0] * 0.5; + laser->upp_ls[1] = args->surface.size[1] * 0.5; + laser->upp_ls[2] = 0; + laser->low_ls[0] = -laser->upp_ls[0]; + laser->low_ls[1] = -laser->upp_ls[1]; + laser->upp_ls[2] = INF; + +exit: + *out_laser = laser; + return res; +error: + if(laser) { htrdr_combustion_laser_ref_put(laser); laser = NULL; } + goto exit; +} + +void +htrdr_combustion_laser_ref_get(struct htrdr_combustion_laser* laser) +{ + ASSERT(laser); + ref_get(&laser->ref); +} + +void +htrdr_combustion_laser_ref_put(struct htrdr_combustion_laser* laser) +{ + ASSERT(laser); + ref_put(&laser->ref, laser_release); +} + +void +htrdr_combustion_laser_trace_ray + (struct htrdr_combustion_laser* laser, + const double pos[3], + const double dir[3], + const double range[2], + double distance[2]) +{ + double pos_ls[3]; /* Position in local space */ + double dir_ls[3]; /* Direction in local space */ + double t_min; + double t_max; + int i; + ASSERT(laser && pos && dir && range && distance); + + /* Transform the ray in local space */ + d33_muld3(pos_ls, laser->world2local, pos); + d33_muld3(dir_ls, laser->world2local, dir); + d3_add(pos_ls, pos_ls, laser->world2local+9); + + /* Reset the distance */ + distance[0] = INF; + distance[1] = INF; + + /* Initialise the range */ + t_min = range[0]; + t_max = range[1]; + + /* TODO one can optimise the Ray/AABB intersection test along the Z axis + * whose AABB interval is in [0, inf) */ + FOR_EACH(i, 0, 3) { + const double rcp_dir_ls = 1.0/dir_ls[i]; + double t0 = (laser->low_ls[i] - pos_ls[i]) * rcp_dir_ls; + double t1 = (laser->upp_ls[i] - pos_ls[i]) * rcp_dir_ls; + if(t0 > t1) SWAP(double, t0, t1); + t_min = MMAX(t_min, t0); + t_max = MMIN(t_max, t1); + if(t_min > t_max) return; /* No intersection */ + } + + /* Save the hit distance */ + distance[0] = t_min; + distance[1] = t_max; +} + +void +htrdr_combustion_laser_get_mesh + (const struct htrdr_combustion_laser* laser, + const double extend, + struct htrdr_combustion_laser_mesh* mesh) +{ + double transform[12]; + double low[3]; + double upp[3]; + ASSERT(laser && extend > 0 && mesh); + + htrdr_rectangle_get_transform(laser->surface, transform); + + /* Define the laser sheet vertices in local space */ + low[0] = laser->low_ls[0]; + low[1] = laser->low_ls[1]; + low[2] = laser->low_ls[2]; + upp[0] = laser->upp_ls[0]; + upp[1] = laser->upp_ls[1]; + upp[2] = laser->low_ls[2] + extend; + + /* Transform the laser sheet vertices in world space */ + d3(mesh->vertices + 0*3, low[0], low[1], low[2]); + d3(mesh->vertices + 1*3, upp[0], low[1], low[2]); + d3(mesh->vertices + 2*3, low[0], upp[1], low[2]); + d3(mesh->vertices + 3*3, upp[0], upp[1], low[2]); + d3(mesh->vertices + 4*3, low[0], low[1], upp[2]); + d3(mesh->vertices + 5*3, upp[0], low[1], upp[2]); + d3(mesh->vertices + 6*3, low[0], upp[1], upp[2]); + d3(mesh->vertices + 7*3, upp[0], upp[1], upp[2]); + mesh->nvertices = 8; + + /* Define the laser sheet triangles */ + mesh->triangles[0] = 0; mesh->triangles[1] = 2; mesh->triangles[2] = 1; + mesh->triangles[3] = 1; mesh->triangles[4] = 2; mesh->triangles[5] = 3; + mesh->triangles[6] = 0; mesh->triangles[7] = 4; mesh->triangles[8] = 2; + mesh->triangles[9] = 2; mesh->triangles[10] = 4; mesh->triangles[11] = 6; + mesh->triangles[12] = 1; mesh->triangles[13] = 3; mesh->triangles[14] = 5; + mesh->triangles[15] = 5; mesh->triangles[16] = 3; mesh->triangles[17] = 7; + mesh->triangles[18] = 1; mesh->triangles[19] = 5; mesh->triangles[20] = 0; + mesh->triangles[21] = 0; mesh->triangles[22] = 5; mesh->triangles[23] = 4; + mesh->triangles[24] = 3; mesh->triangles[25] = 2; mesh->triangles[26] = 7; + mesh->triangles[27] = 7; mesh->triangles[28] = 2; mesh->triangles[29] = 6; + mesh->ntriangles = 10; +} + +void +htrdr_combustion_laser_get_direction + (const struct htrdr_combustion_laser* laser, + double dir[3]) +{ + ASSERT(laser && dir); + htrdr_rectangle_get_normal(laser->surface, dir); +} + +double +htrdr_combustion_laser_get_flux_density + (const struct htrdr_combustion_laser* laser) +{ + ASSERT(laser); + return laser->flux_density; +} + +double +htrdr_combustion_laser_get_wavelength + (const struct htrdr_combustion_laser* laser) +{ + ASSERT(laser); + return laser->wavelength; +} + +double +htrdr_combustion_laser_compute_surface_plane_distance + (const struct htrdr_combustion_laser* laser, + const double pos[3]) +{ + double center[3]; + double normal[3]; + double vec[3]; + ASSERT(laser && pos); + htrdr_rectangle_get_normal(laser->surface, normal); + htrdr_rectangle_get_center(laser->surface, center); + d3_sub(vec, pos, center); + return d3_dot(normal, vec); +} diff --git a/src/combustion/htrdr_combustion_laser.h b/src/combustion/htrdr_combustion_laser.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_COMBUSTION_LASER_H +#define HTRDR_COMBUSTION_LASER_H + +#include "core/htrdr_args.h" + +#include <rsys/rsys.h> + +/* Monochromatic laser */ +struct htrdr_combustion_laser_create_args { + struct htrdr_args_rectangle surface; /* Surface emission */ + double wavelength; /* In nanometers */ + double flux_density; /* In W/m^2 */ +}; +#define HTRDR_COMBUSTION_LASER_CREATE_ARGS_DEFAULT__ { \ + HTRDR_ARGS_RECTANGLE_DEFAULT__, \ + -1, /* Wavelength */ \ + -1, /* Flux density */ \ +} +static const struct htrdr_combustion_laser_create_args +HTRDR_COMBUSTION_LASER_CREATE_ARGS_DEFAULT = + HTRDR_COMBUSTION_LASER_CREATE_ARGS_DEFAULT__; + +struct htrdr_combustion_laser_mesh { + double vertices[8/*#vertices*/*3/*#coords per vertex*/]; + /* Triangle are clock wise ordered from the inside of the laser sheet */ + unsigned triangles[10/*#triangles*/*3/*#vertices per triangle*/]; + unsigned nvertices; + unsigned ntriangles; +}; + +/* Syntactic sugar to check if a laser sheet hit is valid */ +#define HTRDR_COMBUSTION_LASER_HIT_NONE(Hit) ((Hit)[0] >= DBL_MAX) + +/* Forward declaration */ +struct htrdr; +struct htrdr_combustion_laser; + +BEGIN_DECLS + +HTRDR_API res_T +htrdr_combustion_laser_create + (struct htrdr* htrdr, + const struct htrdr_combustion_laser_create_args* args, + struct htrdr_combustion_laser** laser); + +HTRDR_API void +htrdr_combustion_laser_ref_get + (struct htrdr_combustion_laser* laser); + +HTRDR_API void +htrdr_combustion_laser_ref_put + (struct htrdr_combustion_laser* laser); + +HTRDR_API void +htrdr_combustion_laser_trace_ray + (struct htrdr_combustion_laser* laser, + const double pos[3], + const double dir[3], + const double range[2], + double distance[2]); + +HTRDR_API void +htrdr_combustion_laser_get_mesh + (const struct htrdr_combustion_laser* laser, + /* Max distance of the laser mesh along its infinite dimension */ + const double extend, + struct htrdr_combustion_laser_mesh* mesh); + +HTRDR_API void +htrdr_combustion_laser_get_direction + (const struct htrdr_combustion_laser* laser, + double dir[3]); /* Normalized */ + +HTRDR_API double /* In W.m^2 */ +htrdr_combustion_laser_get_flux_density + (const struct htrdr_combustion_laser* laser); + +HTRDR_API double /* In nm */ +htrdr_combustion_laser_get_wavelength + (const struct htrdr_combustion_laser* laser); + +HTRDR_API double +htrdr_combustion_laser_compute_surface_plane_distance + (const struct htrdr_combustion_laser* laser, + const double pos[3]); + +END_DECLS + +#endif /* HTRDR_COMBUSTION_LASER_H */ diff --git a/src/combustion/htrdr_combustion_main.c b/src/combustion/htrdr_combustion_main.c @@ -0,0 +1,87 @@ +/* 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 "combustion/htrdr_combustion.h" +#include "combustion/htrdr_combustion_args.h" + +#include "core/htrdr.h" +#include "core/htrdr_args.h" +#include "core/htrdr_log.h" + +#include <rsys/mem_allocator.h> + +int +htrdr_combustion_main(int argc, char** argv) +{ + char cmd_name[] = "htrdr-combustion"; + struct htrdr_args htrdr_args = HTRDR_ARGS_DEFAULT; + struct htrdr_combustion_args cmd_args = HTRDR_COMBUSTION_ARGS_DEFAULT; + struct htrdr* htrdr = NULL; + struct htrdr_combustion* cmd = NULL; + const size_t memsz_begin = MEM_ALLOCATED_SIZE(&mem_default_allocator); + size_t memsz_end; + int is_mpi_init = 0; + res_T res = RES_OK; + int err = 0; + + /* Overwrite command name */ + argv[0] = cmd_name; + + res = htrdr_mpi_init(argc, argv); + if(res != RES_OK) goto error; + is_mpi_init = 1; + + res = htrdr_combustion_args_init(&cmd_args, argc, argv); + if(res != RES_OK) goto error; + if(cmd_args.quit) goto exit; + + htrdr_args.nthreads = cmd_args.nthreads; + htrdr_args.verbose = cmd_args.verbose; + res = htrdr_create(&mem_default_allocator, &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_combustion_create(htrdr, &cmd_args, &cmd); + if(res != RES_OK) goto error; + + res = htrdr_combustion_run(cmd); + if(res != RES_OK) goto error; + +exit: + htrdr_combustion_args_release(&cmd_args); + if(is_mpi_init) htrdr_mpi_finalize(); + if(htrdr) htrdr_ref_put(htrdr); + if(cmd) htrdr_combustion_ref_put(cmd); + + /* Check memory leaks */ + memsz_end = MEM_ALLOCATED_SIZE(&mem_default_allocator); + if(memsz_begin != memsz_end) { + ASSERT(memsz_end >= memsz_begin); + fprintf(stderr, HTRDR_LOG_WARNING_PREFIX"Memory leaks: %lu Bytes\n", + (unsigned long)(memsz_end - memsz_begin)); + err = -1; + } + return err; +error: + err = -1; + goto exit; +} + diff --git a/src/combustion/test_htrdr_combustion_laser.c b/src/combustion/test_htrdr_combustion_laser.c @@ -0,0 +1,140 @@ +/* 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 "combustion/htrdr_combustion_laser.h" + +#include <rsys/double2.h> +#include <rsys/double3.h> + +static void +dump_obj(const struct htrdr_combustion_laser_mesh* mesh, FILE* stream) +{ + unsigned i; + ASSERT(mesh && stream); + + FOR_EACH(i, 0, mesh->nvertices) { + fprintf(stream, "v %g %g %g\n", + mesh->vertices[i*3+0], + mesh->vertices[i*3+1], + mesh->vertices[i*3+2]); + } + FOR_EACH(i, 0, mesh->ntriangles) { + fprintf(stream, "f %u %u %u\n", + mesh->triangles[i*3+0]+1, + mesh->triangles[i*3+1]+1, + mesh->triangles[i*3+2]+1); + } +} + +int +main(int argc, char** argv) +{ + struct htrdr_args args = HTRDR_ARGS_DEFAULT; + struct htrdr_combustion_laser_mesh mesh; + struct htrdr_combustion_laser_create_args laser_args = + HTRDR_COMBUSTION_LASER_CREATE_ARGS_DEFAULT; + struct htrdr* htrdr = NULL; + struct htrdr_combustion_laser* laser = NULL; + double org[3]; + double dir[3]; + double range[2]; + double hit_range[2]; + double t[2]; + double pt[3]; + double x[3], y[3], z[3]; + double plane0[4]; + double plane1[4]; + FILE* fp = NULL; + + args.verbose = 1; + htrdr_mpi_init(argc, argv); + CHK(htrdr_create(NULL, &args, &htrdr) == RES_OK); + + /* Setup the laser sheet */ + d3(laser_args.surface.position, 0, 0, 0); + d3(laser_args.surface.target, 10, 10, 0); + d3(laser_args.surface.up, 0, 1, 0); + d2(laser_args.surface.size, 100, 50); + laser_args.wavelength = 300; + laser_args.flux_density = 1; + CHK(htrdr_combustion_laser_create(htrdr, &laser_args, &laser) == RES_OK); + htrdr_combustion_laser_get_mesh(laser, 100/*arbitrary extend*/, &mesh); + + /* Write the laser geometry */ + CHK(fp = fopen("laser.obj", "w")); + dump_obj(&mesh, fp); + fclose(fp); + + /* Compute the frame of the surface emission */ + d3_sub(z, laser_args.surface.target, laser_args.surface.position); + d3_cross(x, z, laser_args.surface.up); + d3_cross(y, x, z); + CHK(d3_normalize(y, y) != 0); + + /* Compute the bottom plane equation of the laser sheet */ + pt[0] = laser_args.surface.position[0] - y[0]*laser_args.surface.size[1]*0.5; + pt[1] = laser_args.surface.position[1] - y[1]*laser_args.surface.size[1]*0.5; + pt[2] = laser_args.surface.position[2] - y[2]*laser_args.surface.size[1]*0.5; + plane0[0] = y[0]; + plane0[1] = y[1]; + plane0[2] = y[2]; + plane0[3] = -d3_dot(y, pt); + + /* Compute the top plane equation of the laser sheet */ + pt[0] = laser_args.surface.position[0] + y[0]*laser_args.surface.size[1]*0.5; + pt[1] = laser_args.surface.position[1] + y[1]*laser_args.surface.size[1]*0.5; + pt[2] = laser_args.surface.position[2] + y[2]*laser_args.surface.size[1]*0.5; + plane1[0] = y[0]; + plane1[1] = y[1]; + plane1[2] = y[2]; + plane1[3] = -d3_dot(y, pt); + + /* Trace a ray that misses the laser sheet */ + d3(org, 50, 0, 0); + d3(dir, 0, -1, 0); + d2(range, 0, INF); + htrdr_combustion_laser_trace_ray(laser, org, dir, range, hit_range); + CHK(hit_range[0] > DBL_MAX); + CHK(hit_range[1] > DBL_MAX); + + /* Trace a ray that intersects both bottom and top laser planes */ + d3(dir, 0, 1, 0); + htrdr_combustion_laser_trace_ray(laser, org, dir, range, hit_range); + CHK(hit_range[0] < hit_range[1]); + + /* Compute the intersection of the ray with the bottom/top laser planes */ + t[0] = (-d3_dot(plane0, org) - plane0[3]) / d3_dot(plane0, dir); + t[1] = (-d3_dot(plane1, org) - plane1[3]) / d3_dot(plane1, dir); + + /* Check the returned distances against the computed ones */ + CHK(eq_eps(hit_range[0], t[0], 1.e-6*hit_range[0])); + CHK(eq_eps(hit_range[1], t[1], 1.e-6*hit_range[1])); + + /* Trace a ray that starts into the laser sheet */ + range[0] = 0.5*(hit_range[0] + hit_range[1]); + htrdr_combustion_laser_trace_ray(laser, org, dir, range, hit_range); + CHK(hit_range[0] < hit_range[1]); + CHK(hit_range[0] == range[0]); + CHK(eq_eps(hit_range[1], t[1], 1.e-6*hit_range[1])); + + htrdr_ref_put(htrdr); + htrdr_combustion_laser_ref_put(laser); + htrdr_mpi_finalize(); + + return 0; +} + diff --git a/src/commands/htrdr_cmd.c b/src/commands/htrdr_cmd.c @@ -16,6 +16,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "atmosphere/htrdr_atmosphere.h" +#include "combustion/htrdr_combustion.h" #include "core/htrdr_version.h" #include <string.h> @@ -76,7 +77,8 @@ main(int argc, char** argv) /* Combustion mode */ } else if(!strcmp(argv[1], "combustion")) { - /* TODO */ + err = htrdr_combustion_main(argc-1, argv+1); + if(err) goto error; /* Version */ } else if(!strcmp(argv[1], "--version")) { diff --git a/src/commands/htrdr_combustion_cmd.c b/src/commands/htrdr_combustion_cmd.c @@ -0,0 +1,24 @@ +/* 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 "combustion/htrdr_combustion.h" + +int +main(int argc, char** argv) +{ + return htrdr_combustion_main(argc, argv); +} diff --git a/src/core/htrdr_args.c b/src/core/htrdr_args.c @@ -23,6 +23,7 @@ #include <rsys/cstr.h> #include <rsys/double3.h> +#include <rsys/str.h> #include <string.h> @@ -74,7 +75,7 @@ parse_fov(const char* str, double* out_fov) } static res_T -parse_image_parameter(void* args, const char* str) +parse_image_parameter(const char* str, void* args) { char buf[128]; struct htrdr_args_image* img = args; @@ -136,7 +137,7 @@ error: } static res_T -parse_camera_parameter(void* args, const char* str) +parse_camera_parameter(const char* str, void* args) { char buf[128]; struct htrdr_args_camera* cam = args; @@ -190,7 +191,7 @@ error: } static res_T -parse_rectangle_parameter(void* args, const char* str) +parse_rectangle_parameter(const char* str, void* args) { char buf[128]; struct htrdr_args_rectangle* rect = args; @@ -208,7 +209,6 @@ parse_rectangle_parameter(void* args, const char* str) } 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); @@ -269,7 +269,7 @@ error: } static res_T -parse_spectral_parameter(void* ptr, const char* str) +parse_spectral_parameter(const char* str, void* ptr) { char buf[128]; struct htrdr_args_spectral* args = ptr; @@ -329,34 +329,64 @@ error: } static res_T -parse_multiple_parameters - (void* args, - const char* str, - res_T (*parse_parameter)(void* args, const char* str)) +parse_geometry_parameter(const char* str, void* ptr) { - char buf[512]; - char* tk; + struct str buf; + struct htrdr_args_geometry* geom = ptr; + char* key; + char* val; char* ctx; res_T res = RES_OK; - ASSERT(args && str); + ASSERT(geom && str); - if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { - fprintf(stderr, "Could not duplicate the option string `%s'.\n", str); - res = RES_MEM_ERR; + str_init(NULL, &buf); + res = str_set(&buf, str); + if(res != RES_OK) { + fprintf(stderr, + "Could not duplicate the geometry option string `%s' -- %s.\n", + str, res_to_cstr(res)); 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); + key = strtok_r(str_get(&buf), "=", &ctx); + val = strtok_r(NULL, "", &ctx); + + if(!val) { + fprintf(stderr, "Missing value to the geometry parameter `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + + htrdr_args_geometry_free(geom); + + #define SET_VALUE(Key, Val, Str) { \ + const size_t len = strlen(Val) + 1; \ + Str = mem_alloc(len); \ + if(!Str) { \ + fprintf(stderr, \ + "Could not duplicate the value `%s' of the geometry parameter `%s'.\n",\ + (Val), (Key)); \ + res = RES_MEM_ERR; \ + goto error; \ + } \ + strncpy(Str, Val, len); \ + } (void)0 + if(!strcmp(key, "obj")) { + SET_VALUE(key, val, geom->path_obj); + } else if(!strcmp(key, "mats")) { + SET_VALUE(key, val, geom->path_mats); + } else { + fprintf(stderr, "Invalid geometry parameter `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + #undef SET_VALUE exit: + str_release(&buf); return res; error: + htrdr_args_geometry_free(geom); goto exit; } @@ -367,27 +397,68 @@ res_T htrdr_args_camera_parse(struct htrdr_args_camera* cam, const char* str) { if(!cam || !str) return RES_BAD_ARG; - return parse_multiple_parameters(cam, str, parse_camera_parameter); + return cstr_parse_list(str, ':', parse_camera_parameter, cam); } res_T htrdr_args_rectangle_parse(struct htrdr_args_rectangle* rect, const char* str) { if(!rect || !str) return RES_BAD_ARG; - return parse_multiple_parameters(rect, str, parse_rectangle_parameter); + return cstr_parse_list(str, ':', parse_rectangle_parameter, rect); } - + res_T htrdr_args_image_parse(struct htrdr_args_image* img, const char* str) { if(!img || !str) return RES_BAD_ARG; - return parse_multiple_parameters(img, str, parse_image_parameter); + return cstr_parse_list(str, ':', parse_image_parameter, img); } res_T htrdr_args_spectral_parse(struct htrdr_args_spectral* spectral, const char* str) { if(!spectral || !str) return RES_BAD_ARG; - return parse_multiple_parameters(spectral, str, parse_spectral_parameter); + return cstr_parse_list(str, ':', parse_spectral_parameter, spectral); +} + +void +htrdr_args_geometry_free(struct htrdr_args_geometry* geom) +{ + ASSERT(geom); + if(geom->path_obj) mem_rm(geom->path_obj); + if(geom->path_mats) mem_rm(geom->path_mats); + *geom = HTRDR_ARGS_GEOMETRY_NULL; +} + +res_T +htrdr_args_geometry_parse(struct htrdr_args_geometry* geom, const char* str) +{ + res_T res = RES_OK; + + if(!geom || !str) { + res = RES_BAD_ARG; + goto error; + } + + res = cstr_parse_list(str, ':', parse_geometry_parameter, geom); + if(res != RES_OK) goto error; + + if(!geom->path_obj) { + fprintf(stderr, "Missing the `obj' geometry parameter.\n"); + res = RES_BAD_ARG; + goto error; + } + + if(!geom->path_mats) { + fprintf(stderr, "Missing the `mats' geometry parameter.\n"); + res = RES_BAD_ARG; + goto error; + } + +exit: + return res; +error: + if(geom) htrdr_args_geometry_free(geom); + goto exit; } diff --git a/src/core/htrdr_args.h.in b/src/core/htrdr_args.h.in @@ -67,6 +67,15 @@ struct htrdr_args_image { static const struct htrdr_args_image HTRDR_ARGS_IMAGE_DEFAULT = HTRDR_ARGS_IMAGE_DEFAULT__; +/* Arguments of a geometry */ +struct htrdr_args_geometry { + char* path_obj; /* Path toward a htrdr-obj */ + char* path_mats; /* Path toward a htrdr-materials */ +}; +#define HTRDR_ARGS_GEOMETRY_NULL__ {NULL, NULL} +static const struct htrdr_args_geometry HTRDR_ARGS_GEOMETRY_NULL = + HTRDR_ARGS_GEOMETRY_NULL__; + /* Arguments of the spectral domain */ struct htrdr_args_spectral { double wlen_range[2]; /* Spectral range of integration in nm */ @@ -84,18 +93,18 @@ static const struct htrdr_args_spectral HTRDR_ARGS_SPECTRAL_DEFAULT = /******************************************************************************* * Exported functions ******************************************************************************/ -BEGIN_DECLS +BEGIN_DECLS HTRDR_CORE_API res_T htrdr_args_camera_parse - (struct htrdr_args_camera* cam, + (struct htrdr_args_camera* cam, const char* str); HTRDR_CORE_API res_T htrdr_args_rectangle_parse (struct htrdr_args_rectangle* rect, const char* str); - + HTRDR_CORE_API res_T htrdr_args_image_parse (struct htrdr_args_image* img, @@ -106,6 +115,15 @@ htrdr_args_spectral_parse (struct htrdr_args_spectral* spectral, const char* str); +HTRDR_CORE_API void +htrdr_args_geometry_free + (struct htrdr_args_geometry* geom); + +HTRDR_CORE_API res_T +htrdr_args_geometry_parse + (struct htrdr_args_geometry* geom, + const char* str); + END_DECLS #endif /* HTRDR_ARGS_H */ diff --git a/src/core/htrdr_buffer.h b/src/core/htrdr_buffer.h @@ -27,6 +27,14 @@ * Row major ordered 2D buffer */ +struct htrdr_pixel_format { + size_t size; /* In bytes */ + size_t alignment; /* Power of two, in Bytes */ +}; +#define HTRDR_PIXEL_FORMAT_NULL__ {0, 0} +static const struct htrdr_pixel_format HTRDR_PIXEL_FORMAT_NULL = + HTRDR_PIXEL_FORMAT_NULL__; + struct htrdr_buffer_layout { size_t width; /* #elements in X */ size_t height; /* #elements in Y */ diff --git a/src/core/htrdr_draw_map.c b/src/core/htrdr_draw_map.c @@ -635,7 +635,7 @@ draw_map * 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], + (htrdr_get_thread_allocator(htrdr, (size_t)ithread), &ssp_rng_threefry, RNG_SEQUENCE_SIZE * (size_t)mcode, /* Offset */ RNG_SEQUENCE_SIZE, /* Size */ diff --git a/src/core/htrdr_draw_map.h b/src/core/htrdr_draw_map.h @@ -52,7 +52,6 @@ typedef void struct htrdr_draw_map_args { htrdr_draw_pixel_T draw_pixel; struct htrdr_buffer_layout buffer_layout; - struct ssp_rng* rng; size_t spp; /* Samples per pixel */ void* context; /* User defined data */ }; @@ -60,7 +59,6 @@ struct htrdr_draw_map_args { #define HTRDR_DRAW_MAP_ARGS_NULL__ { \ NULL, /* Draw pixel functor */ \ HTRDR_BUFFER_LAYOUT_NULL__, /* Layout of the destination buffer */ \ - NULL, /* Random Number Generator */ \ 0, /* #Samples per pixel */ \ NULL /* User defined data */ \ } diff --git a/src/core/htrdr_ran_wlen.c b/src/core/htrdr_ran_wlen.c @@ -201,9 +201,10 @@ wlen_ran_sample_continue const double B_lambda = htrdr_planck(lambda_m, lambda_m, Tref); const double B_mean = htrdr_planck(range_m[0], range_m[1], Tref); *pdf = B_lambda / (B_mean * (range_m[1]-range_m[0])); + *pdf = 1.e-9; /* Transform from m^-1 to nm^-1 */ } - return lambda_m * 1.0e9; /* Convert in nanometers */ + return lambda_m * 1.e9; /* Convert in nanometers */ } static double @@ -249,7 +250,7 @@ wlen_ran_sample_discrete /* 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); + pdf_continue = 1.0 / (band_range[1] - band_range[0]); if(pdf) { *pdf = pdf_band * pdf_continue; diff --git a/src/core/htrdr_ran_wlen.h b/src/core/htrdr_ran_wlen.h @@ -53,7 +53,7 @@ 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 */ + double* pdf); /* In nm^-1. May be NULL */ END_DECLS diff --git a/src/core/htrdr_rectangle.c b/src/core/htrdr_rectangle.c @@ -20,6 +20,7 @@ #include "core/htrdr_rectangle.h" #include <rsys/double3.h> +#include <rsys/double33.h> #include <rsys/mem_allocator.h> #include <rsys/ref_count.h> @@ -27,8 +28,11 @@ struct htrdr_rectangle { /* Frame of the rectangle in world space */ double axis_x[3]; double axis_y[3]; - double normal[3]; + + double local2world[12]; /* Rectangle to world transformation matrix */ + double world2local[12]; /* World to rectangle transformation matrix */ + double position[3]; /* Center of the rectangle */ struct htrdr* htrdr; ref_T ref; @@ -50,7 +54,7 @@ rectangle_release(ref_T* ref) } /******************************************************************************* - * Local fuuction + * Exported functions ******************************************************************************/ res_T htrdr_rectangle_create @@ -63,12 +67,13 @@ htrdr_rectangle_create { struct htrdr_rectangle* rect = NULL; double x[3], y[3], z[3]; + double trans[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"); + htrdr_log_err(htrdr, "Could not allocate the rectangle data structure.\n"); res = RES_MEM_ERR; goto error; } @@ -78,7 +83,7 @@ htrdr_rectangle_create if(sz[0] <= 0 || sz[1] <= 0) { htrdr_log_err(htrdr, - "invalid rectangle size `%g %g'. It must be strictly positive.\n", + "Invalid rectangle size `%g %g'. It must be strictly positive.\n", SPLIT2(sz)); res = RES_BAD_ARG; goto error; @@ -87,7 +92,7 @@ htrdr_rectangle_create 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" + htrdr_log_err(htrdr, "Invalid rectangle frame:\n" "\tposition = %g %g %g\n" "\ttarget = %g %g %g\n" "\tup = %g %g %g\n", @@ -96,6 +101,21 @@ htrdr_rectangle_create goto error; } + /* Setup the local to world transformation matrix */ + d3_set(rect->local2world+0, x); + d3_set(rect->local2world+3, y); + d3_set(rect->local2world+6, z); + d3_set(rect->local2world+9, pos); + + /* Inverse the local to world transformation matrix. Note that since the + * represented frame is orthonormal one can transpose the original rotation + * matrix to cheaply compute its inverse */ + d33_transpose(rect->world2local, rect->local2world); + + /* Compute the affine inverse transform */ + d3_minus(trans, pos); + d33_muld3(rect->world2local+9, rect->world2local, trans); + 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); @@ -146,3 +166,35 @@ htrdr_rectangle_get_normal(const struct htrdr_rectangle* rect, double normal[3]) d3_set(normal, rect->normal); } +void +htrdr_rectangle_get_center(const struct htrdr_rectangle* rect, double pos[3]) +{ + ASSERT(rect && pos); + d3_set(pos, rect->position); +} + +double* +htrdr_rectangle_get_transform + (const struct htrdr_rectangle* rect, + double transform[12]) +{ + ASSERT(rect && transform); + d3_set(transform+0, rect->local2world+0); + d3_set(transform+3, rect->local2world+3); + d3_set(transform+6, rect->local2world+6); + d3_set(transform+9, rect->local2world+9); + return transform; +} + +double* +htrdr_rectangle_get_transform_inverse + (const struct htrdr_rectangle* rect, + double transform_inverse[12]) +{ + ASSERT(rect && transform_inverse); + d3_set(transform_inverse+0, rect->world2local+0); + d3_set(transform_inverse+3, rect->world2local+3); + d3_set(transform_inverse+6, rect->world2local+6); + d3_set(transform_inverse+9, rect->world2local+9); + return transform_inverse; +} diff --git a/src/core/htrdr_rectangle.h b/src/core/htrdr_rectangle.h @@ -32,7 +32,7 @@ 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 tgt[3], /* Targeted point */ const double up[3], /* vector orthogonal to the rectangle X axis */ struct htrdr_rectangle** rect); @@ -55,6 +55,21 @@ htrdr_rectangle_get_normal (const struct htrdr_rectangle* rect, double normal[3]); +HTRDR_CORE_API void +htrdr_rectangle_get_center + (const struct htrdr_rectangle* rect, + double center[3]); + +HTRDR_CORE_API double* +htrdr_rectangle_get_transform + (const struct htrdr_rectangle* rect, + double transform[12]); + +HTRDR_CORE_API double* +htrdr_rectangle_get_transform_inverse + (const struct htrdr_rectangle* rect, + double transform_inverse[12]); + END_DECLS #endif /* HTRDR_RECTANGLE_H */