htrdr

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

commit dfbf43fc51669a2ca706b3acd5ac0322dace7053
parent 7e8bae83c0da83fb307ec720a95ab881c5192ec6
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Wed, 24 Feb 2021 10:16:07 +0100

Merge branch 'feature_multiple_modes' into develop

Diffstat:
MREADME.md | 16+++++++++-------
Mcmake/CMakeLists.txt | 144++++++++-----------------------------------------------------------------------
Acmake/atmosphere/CMakeLists.txt | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmake/commands/CMakeLists.txt | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Acmake/core/CMakeLists.txt | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcmake/doc/CMakeLists.txt | 8++++----
Adoc/htrdr-atmosphere.1.txt.in | 404+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdoc/htrdr-image.5.txt | 52+++++++++++++++++++++++++---------------------------
Mdoc/htrdr-materials.5.txt | 20+++++++++++---------
Mdoc/htrdr-obj.5.txt | 20++++++++++++--------
Adoc/htrdr.1.txt | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ddoc/htrdr.1.txt.in | 398-------------------------------------------------------------------------------
Asrc/atmosphere/htrdr_atmosphere.c | 486+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_args.c | 298+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_args.h.in | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_c.h | 164+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_compute_radiance_lw.c | 274+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_compute_radiance_sw.c | 506+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_draw_map.c | 632+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_ground.c | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_ground.h | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_main.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_sun.c | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/atmosphere/htrdr_atmosphere_sun.h | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/commands/htrdr_atmosphere_cmd.c | 24++++++++++++++++++++++++
Asrc/commands/htrdr_cmd.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr.c | 639+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr.h | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_accum.h | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_args.c | 393+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_args.h.in | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_buffer.c | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_buffer.h | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_c.h | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_camera.c | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_camera.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_cie_xyz.c | 391+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_cie_xyz.h | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_draw_map.c | 815+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_draw_map.h | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_geometry.c | 686+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_geometry.h | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_interface.h | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_log.c | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_log.h | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_materials.c | 456+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_materials.h | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_ran_wlen.c | 365+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_ran_wlen.h | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_rectangle.c | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_rectangle.h | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_sensor.h | 39+++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_slab.c | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_slab.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_spectral.c | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/htrdr_spectral.h | 164+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/htrdr_version.h.in -> src/core/htrdr_version.h.in | 0
Dsrc/htrdr.c | 1034-------------------------------------------------------------------------------
Dsrc/htrdr.h | 163-------------------------------------------------------------------------------
Dsrc/htrdr_args.c | 644-------------------------------------------------------------------------------
Dsrc/htrdr_args.h.in | 127-------------------------------------------------------------------------------
Dsrc/htrdr_buffer.c | 167-------------------------------------------------------------------------------
Dsrc/htrdr_buffer.h | 74--------------------------------------------------------------------------
Dsrc/htrdr_c.h | 154-------------------------------------------------------------------------------
Dsrc/htrdr_camera.c | 151------------------------------------------------------------------------------
Dsrc/htrdr_camera.h | 52----------------------------------------------------
Dsrc/htrdr_cie_xyz.c | 396-------------------------------------------------------------------------------
Dsrc/htrdr_cie_xyz.h | 66------------------------------------------------------------------
Dsrc/htrdr_compute_radiance_lw.c | 270-------------------------------------------------------------------------------
Dsrc/htrdr_compute_radiance_sw.c | 497-------------------------------------------------------------------------------
Dsrc/htrdr_draw_map.c | 1207-------------------------------------------------------------------------------
Dsrc/htrdr_grid.c | 366-------------------------------------------------------------------------------
Dsrc/htrdr_grid.h | 71-----------------------------------------------------------------------
Dsrc/htrdr_ground.c | 753-------------------------------------------------------------------------------
Dsrc/htrdr_ground.h | 78------------------------------------------------------------------------------
Dsrc/htrdr_interface.h | 82-------------------------------------------------------------------------------
Dsrc/htrdr_main.c | 91-------------------------------------------------------------------------------
Dsrc/htrdr_materials.c | 449-------------------------------------------------------------------------------
Dsrc/htrdr_materials.h | 67-------------------------------------------------------------------
Dsrc/htrdr_ran_wlen.c | 364-------------------------------------------------------------------------------
Dsrc/htrdr_ran_wlen.h | 54------------------------------------------------------
Dsrc/htrdr_rectangle.c | 142-------------------------------------------------------------------------------
Dsrc/htrdr_rectangle.h | 54------------------------------------------------------
Dsrc/htrdr_sensor.c | 136-------------------------------------------------------------------------------
Dsrc/htrdr_sensor.h | 48------------------------------------------------
Dsrc/htrdr_slab.c | 134-------------------------------------------------------------------------------
Dsrc/htrdr_slab.h | 47-----------------------------------------------
Dsrc/htrdr_solve.h | 185-------------------------------------------------------------------------------
Dsrc/htrdr_spectral.c | 79-------------------------------------------------------------------------------
Dsrc/htrdr_spectral.h | 137-------------------------------------------------------------------------------
Dsrc/htrdr_sun.c | 144-------------------------------------------------------------------------------
Dsrc/htrdr_sun.h | 67-------------------------------------------------------------------
93 files changed, 10183 insertions(+), 9134 deletions(-)

diff --git a/README.md b/README.md @@ -191,13 +191,15 @@ regular image rendering), longwave or shortwave. - Fix a possible invalid memory access to cloud data leading to segmentation faults. +## Copyright notice + +Copyright (C) 2018, 2019, 2020, 2021 [|Meso|Star>](http://www.meso-star.com). +Copyright (C) 2018, 2019, 2021 CNRS. +Copyright (C) 2018, 2019 Université Paul Sabatier. + ## License -Copyright (C) 2018, 2019, 2020 [|Meso|Star>](http://www.meso-star.com) -<contact@meso-star.com>. Copyright (C) 2018, 2019 Centre National de la -Recherche Scientifique (CNRS), Université Paul Sabatier -<contact-edstar@laplace.univ-tlse.fr>. htrdr is free software released under -the GPL v3+ license: GNU GPL version 3 or later. You are welcome to -redistribute it under certain conditions; refer to the COPYING file for -details. +htrdr is free software released under the GPL v3+ license: GNU GPL version 3 or +later. You are welcome to redistribute it under certain conditions; refer to +the COPYING file for details. diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -1,5 +1,6 @@ -# Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) -# Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier +# 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 @@ -14,149 +15,30 @@ # 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 2.8) +cmake_minimum_required(VERSION 3.1) project(htrdr C) -enable_testing() set(HTRDR_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src) -option(NO_TEST "Do not build the tests" OFF) +set(HTRDR_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}) -################################################################################ -# Check dependencies -################################################################################ -find_package(AW 2.0 REQUIRED) -find_package(HTSky 0.2 REQUIRED) -find_package(MruMtl 0.0 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) -find_package(OpenMP 1.2 REQUIRED) -find_package(MPI 1 REQUIRED) - -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) -include(rcmake) -include(rcmake_runtime) - -set(CMAKE_C_COMPILER ${MPI_C_COMPILER}) - -include_directories( - ${AW_INCLUDE_DIR} - ${MruMtl_INCLUDE_DIR} - ${RSys_INCLUDE_DIR} - ${Star3D_INCLUDE_DIR} - ${StarSF_INCLUDE_DIR} - ${StarSP_INCLUDE_DIR} - ${StarVX_INCLUDE_DIR} - ${HTSky} - ${HTRDR_SOURCE_DIR} - ${MPI_INCLUDE_PATH} - ${CMAKE_CURRENT_BINARY_DIR}) - -################################################################################ -# Generate files -################################################################################ set(VERSION_MAJOR 0) -set(VERSION_MINOR 6) -set(VERSION_PATCH 1) +set(VERSION_MINOR 7) +set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) -set(HTRDR_ARGS_DEFAULT_CAMERA_POS "0,0,0") -set(HTRDR_ARGS_DEFAULT_CAMERA_TGT "0,1,0") -set(HTRDR_ARGS_DEFAULT_CAMERA_UP "0,0,1") -set(HTRDR_ARGS_DEFAULT_CAMERA_FOV "70") -set(HTRDR_ARGS_DEFAULT_RECTANGLE_POS "0,0,0") -set(HTRDR_ARGS_DEFAULT_RECTANGLE_TGT "0,0,1") -set(HTRDR_ARGS_DEFAULT_RECTANGLE_UP "0,1,0") -set(HTRDR_ARGS_DEFAULT_RECTANGLE_SZ "1,1") -set(HTRDR_ARGS_DEFAULT_IMG_WIDTH "320") -set(HTRDR_ARGS_DEFAULT_IMG_HEIGHT "240") -set(HTRDR_ARGS_DEFAULT_IMG_SPP "1") -set(HTRDR_ARGS_DEFAULT_OPTICAL_THICKNESS_THRESHOLD "1") -set(HTRDR_ARGS_DEFAULT_SKY_MTL_NAME "\"air\"") - -configure_file(${HTRDR_SOURCE_DIR}/htrdr_version.h.in - ${CMAKE_CURRENT_BINARY_DIR}/htrdr_version.h @ONLY) - -configure_file(${HTRDR_SOURCE_DIR}/htrdr_args.h.in - ${CMAKE_CURRENT_BINARY_DIR}/htrdr_args.h @ONLY) - ################################################################################ # Add sub projects ################################################################################ +add_subdirectory(core) +add_subdirectory(atmosphere) +add_subdirectory(commands) add_subdirectory(doc) ################################################################################ -# Configure and define targets -################################################################################ -set(HTRDR_FILES_SRC - htrdr.c - htrdr_args.c - htrdr_buffer.c - htrdr_camera.c - htrdr_cie_xyz.c - htrdr_compute_radiance_sw.c - htrdr_compute_radiance_lw.c - htrdr_draw_map.c - htrdr_grid.c - htrdr_ground.c - htrdr_main.c - htrdr_materials.c - htrdr_ran_wlen.c - htrdr_rectangle.c - htrdr_sensor.c - htrdr_slab.c - htrdr_spectral.c - htrdr_sun.c) -set(HTRDR_FILES_INC - htrdr.h - htrdr_c.h - htrdr_buffer.h - htrdr_camera.h - htrdr_cie_xyz.h - htrdr_grid.h - htrdr_ground.h - htrdr_interface.h - htrdr_materials.h - htrdr_ran_wlen.h - htrdr_rectangle.h - htrdr_sensor.h - htrdr_slab.h - htrdr_spectral.h - htrdr_sun.h - htrdr_solve.h) -set(HTRDR_FILES_INC2 # Generated files - htrdr_args.h - htrdr_version.h) -set(HTRDR_FILES_DOC COPYING README.md) - -# Prepend each file in the `HTRDR_FILES_<SRC|INC>' list by `HTRDR_SOURCE_DIR' -rcmake_prepend_path(HTRDR_FILES_SRC ${HTRDR_SOURCE_DIR}) -rcmake_prepend_path(HTRDR_FILES_INC ${HTRDR_SOURCE_DIR}) -rcmake_prepend_path(HTRDR_FILES_INC2 ${CMAKE_CURRENT_BINARY_DIR}) -rcmake_prepend_path(HTRDR_FILES_DOC ${PROJECT_SOURCE_DIR}/../) - -add_executable(htrdr ${HTRDR_FILES_SRC} ${HTRDR_FILES_INC} ${HTRDR_FILES_INC2}) -target_link_libraries(htrdr AW HTSky MruMtl RSys Star3D StarSF StarSP) - -if(CMAKE_COMPILER_IS_GNUCC) - target_link_libraries(htrdr m) - set_target_properties(htrdr PROPERTIES LINK_FLAGS "${OpenMP_C_FLAGS}") -endif() - -set_target_properties(htrdr PROPERTIES - COMPILE_FLAGS "${OpenMP_C_FLAGS}" - VERSION ${VERSION} - SOVERSION ${VERSION_MAJOR}) - -################################################################################ # Define output & install directories ################################################################################ -install(TARGETS htrdr - ARCHIVE DESTINATION bin - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin) +set(HTRDR_FILES_DOC + ${PROJECT_SOURCE_DIR}/../COPYING + ${PROJECT_SOURCE_DIR}/../README.md) install(FILES ${HTRDR_FILES_DOC} DESTINATION share/doc/htrdr) diff --git a/cmake/atmosphere/CMakeLists.txt b/cmake/atmosphere/CMakeLists.txt @@ -0,0 +1,105 @@ +# 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-atmosphere) + +################################################################################ +# Check dependencies +################################################################################ +find_package(HTSky 0.2 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( + ${HTSky_INCLUDE_DIR} + ${RSys_INCLUDE_DIR} + ${Star3D_INCLUDE_DIR} + ${StarSF_INCLUDE_DIR} + ${StarSP_INCLUDE_DIR} + ${StarVX_INCLUDE_DIR} + ${HTRDR_BUILD_DIR} + ${HTRDR_SOURCE_DIR}) + +################################################################################ +# Generate files +################################################################################ +set(HTRDR_ATMOSPHERE_ARGS_DEFAULT_OPTICAL_THICKNESS_THRESHOLD "1") +set(HTRDR_ATMOSPHERE_ARGS_DEFAULT_SKY_MTL_NAME "\"air\"") + +configure_file(${HTRDR_SOURCE_DIR}/atmosphere/htrdr_atmosphere_args.h.in + ${HTRDR_BUILD_DIR}/atmosphere/htrdr_atmosphere_args.h @ONLY) + +################################################################################ +# Configure and define targets +################################################################################ +set(HTRDR_ATMOSPHERE_FILES_SRC + htrdr_atmosphere_args.c + htrdr_atmosphere.c + htrdr_atmosphere_compute_radiance_lw.c + htrdr_atmosphere_compute_radiance_sw.c + htrdr_atmosphere_draw_map.c + htrdr_atmosphere_ground.c + htrdr_atmosphere_main.c + htrdr_atmosphere_sun.c) + +set(HTRDR_ATMOSPHERE_FILES_INC + htrdr_atmosphere_c.h + htrdr_atmosphere_ground.h + htrdr_atmosphere.h + htrdr_atmosphere_sun.h) + +set(HTRDR_ATMOSPHERE_FILES_INC2 # Generated files + htrdr_atmosphere_args.h) + +# Prepend each file in the `HTRDR_FILES_<SRC|INC>' list by `HTRDR_SOURCE_DIR' +rcmake_prepend_path(HTRDR_ATMOSPHERE_FILES_SRC ${HTRDR_SOURCE_DIR}/atmosphere) +rcmake_prepend_path(HTRDR_ATMOSPHERE_FILES_INC ${HTRDR_SOURCE_DIR}/atmosphere) +rcmake_prepend_path(HTRDR_ATMOSPHERE_FILES_INC2 ${HTRDR_BUILD_DIR}/atmosphere) + +# Atmosphere library +add_library(htrdr-atmosphere SHARED + ${HTRDR_ATMOSPHERE_FILES_SRC} + ${HTRDR_ATMOSPHERE_FILES_INC} + ${HTRDR_ATMOSPHERE_FILES_INC2}) +target_link_libraries(htrdr-atmosphere htrdr-core HTSky RSys Star3D StarSF StarSP) + +if(CMAKE_COMPILER_IS_GNUCC) + target_link_libraries(htrdr-atmosphere m) +endif() + +set_target_properties(htrdr-atmosphere PROPERTIES + DEFINE_SYMBOL HTRDR_SHARED_BUILD + VERSION ${VERSION} + SOVERSION ${VERSION_MAJOR}) + +################################################################################ +# Define output & install directories +################################################################################ +install(TARGETS htrdr-atmosphere + ARCHIVE DESTINATION bin + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + diff --git a/cmake/commands/CMakeLists.txt b/cmake/commands/CMakeLists.txt @@ -0,0 +1,50 @@ +# 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-commands C) + +include_directories(${HTRDR_BUILD_DIR} ${HTRDR_SOURCE_DIR}) + +################################################################################ +# Check dependencies +################################################################################ +find_package(RSys 0.11 REQUIRED) +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) +set_target_properties(htrdr_cmd PROPERTIES + OUTPUT_NAME htrdr) + +add_executable(htrdr_atmosphere_cmd + ${HTRDR_SOURCE_DIR}/commands/htrdr_atmosphere_cmd.c) +target_link_libraries(htrdr_atmosphere_cmd htrdr-atmosphere) +set_target_properties(htrdr_atmosphere_cmd PROPERTIES + OUTPUT_NAME htrdr-atmosphere) + +################################################################################ +# Define output & install directories +################################################################################ +install(TARGETS htrdr_cmd htrdr_atmosphere_cmd + ARCHIVE DESTINATION bin + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + diff --git a/cmake/core/CMakeLists.txt b/cmake/core/CMakeLists.txt @@ -0,0 +1,141 @@ +# 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-core C) + +################################################################################ +# Check dependencies +################################################################################ +find_package(AW 2.0 REQUIRED) +find_package(MruMtl 0.0 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(OpenMP 1.2 REQUIRED) +find_package(MPI 1 REQUIRED) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) +include(rcmake) +include(rcmake_runtime) + +set(CMAKE_C_COMPILER ${MPI_C_COMPILER}) + +include_directories( + ${AW_INCLUDE_DIR} + ${MruMtl_INCLUDE_DIR} + ${RSys_INCLUDE_DIR} + ${Star3D_INCLUDE_DIR} + ${StarSF_INCLUDE_DIR} + ${StarSP_INCLUDE_DIR} + ${StarVX_INCLUDE_DIR} + ${HTSky} + ${HTRDR_BUILD_DIR} + ${HTRDR_SOURCE_DIR} + ${MPI_INCLUDE_PATH}) + +################################################################################ +# Generate files +################################################################################ +set(HTRDR_ARGS_DEFAULT_CAMERA_POS "0,0,0") +set(HTRDR_ARGS_DEFAULT_CAMERA_TGT "0,1,0") +set(HTRDR_ARGS_DEFAULT_CAMERA_UP "0,0,1") +set(HTRDR_ARGS_DEFAULT_CAMERA_FOV "70") +set(HTRDR_ARGS_DEFAULT_RECTANGLE_POS "0,0,0") +set(HTRDR_ARGS_DEFAULT_RECTANGLE_TGT "0,0,1") +set(HTRDR_ARGS_DEFAULT_RECTANGLE_UP "0,1,0") +set(HTRDR_ARGS_DEFAULT_RECTANGLE_SZ "1,1") +set(HTRDR_ARGS_DEFAULT_IMG_WIDTH "320") +set(HTRDR_ARGS_DEFAULT_IMG_HEIGHT "240") +set(HTRDR_ARGS_DEFAULT_IMG_SPP "1") + +configure_file(${HTRDR_SOURCE_DIR}/core/htrdr_args.h.in + ${HTRDR_BUILD_DIR}/core/htrdr_args.h @ONLY) +configure_file(${HTRDR_SOURCE_DIR}/core/htrdr_version.h.in + ${HTRDR_BUILD_DIR}/core/htrdr_version.h @ONLY) + +################################################################################ +# Configure and define targets +################################################################################ +set(HTRDR_CORE_FILES_SRC + htrdr.c + htrdr_args.c + htrdr_buffer.c + htrdr_camera.c + htrdr_cie_xyz.c + htrdr_draw_map.c + htrdr_geometry.c + htrdr_log.c + htrdr_materials.c + htrdr_ran_wlen.c + htrdr_rectangle.c + htrdr_slab.c + htrdr_spectral.c) +set(HTRDR_CORE_FILES_INC + htrdr.h + htrdr_accum.h + htrdr_buffer.h + htrdr_camera.h + htrdr_c.h + htrdr_cie_xyz.h + htrdr_draw_map.h + htrdr_geometry.c + htrdr_interface.h + htrdr_log.h + htrdr_materials.h + htrdr_ran_wlen.h + htrdr_rectangle.h + htrdr_sensor.h + htrdr_slab.h + htrdr_spectral.h) +set(HTRDR_CORE_FILES_INC2 # Generated files + htrdr_args.h + htrdr_version.h) + +# Prepend each file in the `HTRDR_FILES_<SRC|INC>' list by `HTRDR_SOURCE_DIR' +rcmake_prepend_path(HTRDR_CORE_FILES_SRC ${HTRDR_SOURCE_DIR}/core) +rcmake_prepend_path(HTRDR_CORE_FILES_INC ${HTRDR_SOURCE_DIR}/core) +rcmake_prepend_path(HTRDR_CORE_FILES_INC2 ${HTRDR_BUILD_DIR}/core) + +# Core library +add_library(htrdr-core SHARED + ${HTRDR_CORE_FILES_SRC} + ${HTRDR_CORE_FILES_INC} + ${HTRDR_CORE_FILES_INC2}) +target_link_libraries(htrdr-core AW MruMtl RSys Star3D StarSF StarSP) + +if(CMAKE_COMPILER_IS_GNUCC) + target_link_libraries(htrdr-core m) + set_target_properties(htrdr-core PROPERTIES LINK_FLAGS "${OpenMP_C_FLAGS}") +endif() + +set_target_properties(htrdr-core PROPERTIES + COMPILE_FLAGS "${OpenMP_C_FLAGS}" + DEFINE_SYMBOL HTRDR_CORE_SHARED_BUILD + VERSION ${VERSION} + SOVERSION ${VERSION_MAJOR}) + +################################################################################ +# Define output & install directories +################################################################################ +install(TARGETS htrdr-core + ARCHIVE DESTINATION bin + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + diff --git a/cmake/doc/CMakeLists.txt b/cmake/doc/CMakeLists.txt @@ -29,7 +29,7 @@ endif() ################################################################################ # Copy doc files ################################################################################ -set(MAN_NAMES htrdr-image.5 htrdr-materials.5 htrdr-obj.5) +set(MAN_NAMES htrdr.1 htrdr-image.5 htrdr-materials.5 htrdr-obj.5) set(MAN_FILES) foreach(_name IN LISTS MAN_NAMES) @@ -45,9 +45,9 @@ foreach(_name IN LISTS MAN_NAMES) endforeach() add_custom_target(man-copy ALL DEPENDS ${MAN_FILES}) -configure_file(${HTRDR_SOURCE_DIR}/../doc/htrdr.1.txt.in - ${CMAKE_CURRENT_BINARY_DIR}/htrdr.1.txt @ONLY) -list(APPEND MAN_NAMES htrdr.1) +configure_file(${HTRDR_SOURCE_DIR}/../doc/htrdr-atmosphere.1.txt.in + ${CMAKE_CURRENT_BINARY_DIR}/htrdr-atmosphere.1.txt @ONLY) +list(APPEND MAN_NAMES htrdr-atmosphere.1) ################################################################################ # ROFF man pages diff --git a/doc/htrdr-atmosphere.1.txt.in b/doc/htrdr-atmosphere.1.txt.in @@ -0,0 +1,404 @@ +// 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 +// (contact-edstar@laplace.univ-tlse.fr) +// +// 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/>. +:toc: + +htrdr-atmosphere(1) +=================== + +NAME +---- +htrdr-atmosphere - simulate radiative transfert in cloudy atmospheres + +SYNOPSIS +-------- +[verse] +*htrdr-atmosphere* [_option_]... -a _atmosphere_ + +DESCRIPTION +----------- +*htrdr-atmosphere* simulate radiative transfert in scenes composed of an +atmospheric gas mixture, clouds, and a ground. It evaluates the intensity +incoming on each pixel of the sensor array. The underlying algorithm is based +on a Monte-Carlo method: it consists in simulating a given number of optical +paths originating from the sensor, directed into the atmosphere, taking into +account light absorption and scattering phenomena. + +Radiative transfert can be evaluated in the visible or the infrared part of the +spectrum. It uses spectral data that should be provided for the pressure and +temperature atmospheric vertical profile [1] (*-a* _atmosphere_), the liquid +water content in suspension within the clouds stored in a *htcp*(5) file (*-c* +_clouds_), and the optical properties of water droplets that have been obtained +from a Mie code and formatted according to the *htmie*(5) file format (*-m* +_mie_). The user also has to set the position of the sun (*-D* +_azimuth_,_elevation_), the sensor type (*-C* _camera_ or *-p* _rectangle_) and +its definition (*-i* _image_). It is also possible to provide an *htrdr-obj*(5) +file representing the ground geometry (*-g* _ground_) whose materials are +listed in the *htrdr-material*(5) file provided through the *-M* option. Both, +the clouds and the ground, can be infinitely repeated along the X and Y axis by +setting the *-r* and the *-R* options, respectively. + +Two types of sensor are supported by *htrdr-atmosphere*. The camera (*-C* +_camera_) is used to render an image of the scene from the given point of view +while the rectangle sensor (*-p* _rectangle_) is used to compute a flux map. + +Spectral dimension can be integrated in many ways (*-s* option). When rendering +an image (*-C* _camera_), the computation is by default performed for the +visible part of the spectrum in [380, 780] nanometers, for the three components +of the CIE 1931 XYZ colorimetric space that are subsequently recombined in +order to obtain the final color for each pixel, and finally the whole image of +the scene as seen from the set observation position. The two other ways consist +in explicitly defining the longwave or shortwave spectral range to handle and +continuously sampling a wavelength in this range. Actually longwave and +shortwave are keywords that mean that the source of radiation is whether +external or internal to the medium, respectively. In shortwave rendering, only +the pixel radiance is evaluated and stored in the output image. For longwave +rendering this estimated radiance is then converted to its brightness +temperature and both are saved in the image. When computing a flux map (*-p* +_rectangle_), the per pixel flux is saved into the output map whether spectral +domain is longwave or shortwave. + +In *htrdr-atmosphere* the spatial unit 1.0 corresponds to one meter and the +temperatures are expressed in Kelvin. The estimated radiances are given in +W/sr/m^2 excepted for monochromatic computations where the computed spectral +radiance is defined in W/sr/m^2/nm. The fluxes are saved in W/m^2. The results +are written to the output file if the *-o* option is defined and the standard +output otherwise. The output image is a list of raw ASCII data formatted with +respect to the *htrdr-image*(5) file format. Since *htrdr-atmosphere* relies +on the Monte-Carlo method, each estimation is given with its numerical +accuracy. + +During the simulation, *htrdr-atmosphere* dynamically loads/unloads cloud +properties to handle clouds whose data that do not feat in main memory. +*htrdr-atmosphere* also supports shared memory parallelism and relies on the +Message Passing Interface specification [3] to parallelise its computations in +a distributed memory environment; it can thus be run either directly or through +a MPI process launcher like *mpirun*(1). + +OPTIONS +------- +*-a* _atmosphere_:: + Path toward the file containing the gas optical properties of the atmosphere. + Data must be formatted according to the fileformat described in [1]. + +*-c* _clouds_:: + Submit a *htcp*(5) file describing the properties of the clouds. If not + defined, only the atmosphere properties submitted through the *-a* option + are taken into account. + +*-C* <__camera-parameter__:...>:: + Define the rendering point of view. Available camera parameters are: + + **fov**=**_angle_**;; + Vertical field of view of the camera in [30, 120] degrees. By default + _angle_ is set to @HTRDR_ARGS_DEFAULT_CAMERA_FOV@ degrees. + + **pos**=**_x_**,**_y_**,**_z_**;; + Position of the camera. By default it is set to + {@HTRDR_ARGS_DEFAULT_CAMERA_POS@}. + + **tgt**=**_x_**,**_y_**,**_z_**;; + Position targeted by the camera. By default it is set to + {@HTRDR_ARGS_DEFAULT_CAMERA_TGT@}. + + **up**=**_x_**,**_y_**,**_z_**;; + Up vector of the camera. By default it is set to + {@HTRDR_ARGS_DEFAULT_CAMERA_UP@}. + +*-D* <__azimuth__,__elevation__>:: + Direction toward the sun center. The direction is defined by two angles in + degrees: the __azimuth__ angle in [0, 360[ and the __elevation__ angle in [0, + 90]. Following the right-handed convention, the azimuthal rotation is + counter-clockwise, with 0 degree on the X axis. The elevation starts from 0 + degree for a direction in the XY plane, up to 90 degrees at zenith. Thus + -D0,0 -D90,0 -D180,0 and -D270,0 will produce solar vectors {+1,0,0} {0,+1,0} + {-1,0,0} and {0,-1,0} respectively, while -D__azimuth__,90 will produce + {0,0,+1} regardless of _azimuth_ value. + +*-d*:: + Write in _output_ the space partitioning data structures used to speed up + the radiative transfer computations in the clouds. The written data are + octrees saved in the VTK file format [2]. Each octree node stores the minimum + and the maximum of the extinction coefficients of the cloud cells overlapped + by the octree node. In the _output_ file, each octree is separated from the + previous one by a line with three minus characters, i.e. '---'. + +*-f*:: + Force overwrite of the _output_ file. + +*-g* _ground_:: + Path toward a *htrdr-obj*(5) representing the ground geometry. + +*-h*:: + List short help and exit. + +*-i* <__image-parameter__:...>:: + Define the sensor array. Available image parameters are: + + **def**=**_width_**x**_height_**;; + Definition of the image. By default the image definition is + @HTRDR_ARGS_DEFAULT_IMG_WIDTH@x@HTRDR_ARGS_DEFAULT_IMG_HEIGHT@. + + **spp**=**_samples-count_**;; + Number of samples per pixel estimation. In regular image rendering, a pixel + will use "3 * _samples-count_" Monte-Carlo realisations, one set of + _samples-count_ realisations for each X, Y and Z component of the CIE 1931 + XYZ color space. In shortwave/longwave rendering or flux computation, only + one set of _samples-count_ is used. By default, *spp* is set to + @HTRDR_ARGS_DEFAULT_IMG_SPP@. + +*-R*:: + Infinitely repeat the _ground_ along the X and Y axis. + +*-r*:: + Infinitely repeat the _clouds_ along the X and Y axis. + +*-M* _materials_:: + Path toward a *htrdr-materials*(5) file listing the ground materials. + +*-m* _mie_:: + Path toward a *htmie*(5) file defining the optical properties of water + droplets. + +*-n* _sky-mtl_:: + Name of the material representing the sky in the *htrdr-materials*(5) file. + By default, _sky-mtl_ is @HTRDR_ARGS_DEFAULT_SKY_MTL_NAME@. + +*-O* _cache_:: + File used to cache the sky data. If the _cache_ file does not exists, it is + created and filled with the sky data built from the _clouds_, the + _atmosphere_ and the _mie_ input files. This cached data can then be reused + in the next runs as long as the input files provided on the command are the + same as the ones used to setup the cache; leading to a significant speed-up + of the pre-processing step. If _cache_ contains data generated from input + files that are not the ones submitted on the command line, an error is + notified and the execution is stopped, avoiding the use of wrong cached data. + Note that when the cache is used, *htrdr-atmosphere* ignores the arguments + used to parametrise the structures partitioning the sky data, i.e. the *-T* + and *-V* options. + +*-o* _output_:: + File where *htrdr-atmosphere* writes its _output_ data. If not defined, write + results to standard output. + +*-p* <__rectangle-parameter__:...>:: + Switch in flux map computation. The flux is computed for the part of the + sensor that is outside any geometry. The rectangular sensor onto which the + flux is integrated is defined by the following parameters: + + **pos**=**_x_**,**_y_**,**_z_**;; + Position of the center of the rectangle. By default it is set to + {@HTRDR_ARGS_DEFAULT_RECTANGLE_POS@}. + + **tgt**=**_x_**,**_y_**,**_z_**;; + Position targeted by the rectangle, i.e. *tgt* - *pos* is the rectangle + normal. By default it is set to {@HTRDR_ARGS_DEFAULT_RECTANGLE_TGT@}. + + **up**=**_x_**,**_y_**,**_z_**;; + Up vector of the rectangle. By default it is set to + {@HTRDR_ARGS_DEFAULT_RECTANGLE_UP@}. + + **sz**=**_width_**,**_height_**;; + Size of the rectangle. By default it is set to + {@HTRDR_ARGS_DEFAULT_RECTANGLE_SZ@}. + +*-s* <__spectral-parameter__:...>:: + Define the type and the range of the spectral integration. Available spectral + parameters are: + + **cie_xyz**;; + the radiance is computed for the visible part of the spectrum in [380, 780] + nanometers with respect to the XYZ CIE 1931 tristimulus values. This is the + default comportment of *htrdr-atmosphere*. + + **lw**=*_wlen-min_*,*_wlen-max_*;; + perform the spectral sampling continuously in the [_wlen-min_, _wlen-max_] + wavelength range (wavelength must be provided in nanometers) according to + the Planck function for a reference temperature. If _wlen-min_ and + _wlen-max_ are equals the computation is monochromatic. *lw* means for + longwave but is here a code word that really means "computation of radiance + using the internal source of radiation": in other words, radiation is + emitted by the medium and its boundaries (ground and space). Because the + application is for the terrestrial atmosphere, internal radiation is + emitted in the thermal longwave part of the electromagnetic spectrum. + Therefore the default value of the reference temperature used in the + spectral sampling is fixed at 290 K. + + **sw**=*_wlen-min_*,*_wlen-max_*;; + perform the spectral sampling continuously in the [_wlen-min_, _wlen-max_] + wavelength range (wavelength must be provided in nanometers) according to + the Planck function for a reference temperature. If _wlen-min_ and + _wlen-max_ are equals the computation is monochromatic. In the present + case, *sw* means that the source of radiation is external to the medium: + because the application is for the terrestrial atmosphere, the value of the + reference temperature is by default fixed at 5778 K, i.e. the blackbody + temperature of the Sun. + + **Tref**=**_temperature_**;; + reference temperature of the Planck function used to continuously sample the + longwave/shortwave spectral range. In longwave, it is set to 290 K by + default while in shortwave its default value is the blackbody temperature + of the sun (i.e. 5778 K). + +*-T* _threshold_:: + Optical thickness used as threshold criteria to partition the properties of + the clouds. By default its value is @HTRDR_ARGS_DEFAULT_OPTICAL_THICKNESS_THRESHOLD@. + This option is ignored if a cache file is used (option *-O*). + +*-t* _threads-count_:: + Hint on the number of threads to use. By default use as many threads as CPU + cores. + +*-V* **_x_**,**_y_**,**_z_**:: + Define the maximum definition of the acceleration data structure that + partitions the cloud properties. By default the finest definition is the + definition of the submitted *htcp*(5) file. This option is ignored if a cache + file is used (option *-O*). + +*-v*:: + Make *htrdr-atmosphere* verbose. + +EXAMPLES +-------- + +Render a clear sky scene, i.e. a scene without any cloud, whose sun is at +zenith. The vertical atmospheric gaz mixture along the Z axis is described in +the *gas.txt* file. the ground geometry is a quad repeated to the infinity +whose materials are listed in the *material.mtl* file. The camera is positioned +at *400* meters height and looks toward the positive Y axis. The definition of +the rendered image is *800* by *600* pixels and the radiance of each pixel +component is estimated with *64* Monte-Carlo realisations. The resulting image +is written to *output* excepted if the file already exists; in this case an +error is notified, the program stops and the *output* file remains unchanged: + + $ htrdr-atmosphere -D0,90 -a gas.txt -m Mie.nc -g quad.obj -R \ + -M materials.mtl \ + -C pos=0,0,400:tgt=0,1,0:up=0,0,1 \ + -i def=800x600:spp=64 \ + -o output + +Add clouds to the previous scene and use a more complex geometry to represent +the ground. The Mie data are provided through the *Mie.nc* file. The ground +geometry was carefully designed to be cyclic and can be thus infinitely +repeated without visual glitches. Use the *-f* option to write the rendered +image to *output* even though the file already exists. Finally, use the +*htpp*(1) command to convert the *htrdr-image*(5) saved in output in a regular +PPM image [4]: + + $ htrdr-atmosphere -D0,90 -a gas.txt -m Mie.nc -g mountains.obj -R \ + -M materials.mtl \ + -c clouds.htcp \ + -C pos=0,0,400:tgt=0,1,0:up=0,0,1 \ + -i def=800x600:spp=64 \ + -f -o output + $ htpp -o image.ppm output + +Render the previous scene in infrared for the wavelengths in [*9200*, *10000*] +nanometers with a reference temperature of *300* Kelvin: + + $ htrdr-atmosphere -a gas.txt -m Mie.nc -g mountains.obj -R \ + -M materials.mtl \ + -c clouds.htcp \ + -C pos=0,0,400:tgt=0,1,0:up=0,0,1 \ + -i def=800x600:spp=64 \ + -s lw=9200,10000:Tref=300 + -f -o output + +Move the sun by setting its azimuthal and elevation angles to *120* and *40* +degrees respectively. Use the *-O* option to enable the cache mechanism on +sky data. Increase the image definition to *1280* by *720* and set the +number of samples per pixel component to *1024*. Write results on standard +output and convert the resulting image in PPM before visualising it through the +*feh*(1) image viewer: + + $ htrdr-atmosphere -D120,40 -a gas.txt -m Mie.nc -g mountains.obj -R \ + -M materials.mtl \ + -c clouds.htcp \ + -O my_cache \ + -C pos=0,0,400:tgt=0,1,0:up=0,0,1 \ + -i def=1280x720:spp=1024 | htpp | feh - + +Compute the downward flux for the shortwave interval [*350*, *4000*] nanometers +on a square of *100* meters side positionned at the origin at *1* meter height. +The resolution of the flux map is *500* by *500* pixels and *1000* realisations +is used to estimate the flux per pixel. It is saved in the *flux_map.txt* file +even though this file already exists: + + $ htrdr-atmosphere -D0,90 -a gas.txt -m Mie.nc -g plane.obj -R \ + -M materials.mtl \ + -c clouds.htcp \ + -O my_cache \ + -p pos=0,0,1:tgt=0,0,2:up=0,1,0:sz=100,100 \ + -i def=500x500:spp=1000 \ + -s sw=350,4000 \ + -f -o flux_map.txt + +Write into *output* the data structures used to partition the clouds +properties. Use the *csplit*(1) Unix command to extract from *output* the list +of the generated grids and save each of them in a specific VTK file whose name +is *cloud_grid_*<__NUM__>*.vtk with __NUM__ in [0, N-1] where N is the number +of grids written into *output*: + + $ htrdr-atmosphere -a gas.txt -m Mie.nc -c clouds.htcp -d -f -o output + $ csplit -f cloud_grid_ -b %02d.vtk -z --suppress-matched output /^---$/ {*} + +Use *mpirun*(1) to launch *htrdr-atmosphere* on several hosts defined in the +*my_hosts* file. Make the clouds infinite along the X and Y axis: + + $ mpirun --hostfile my_hosts htrdr-atmosphere \ + -D120,40 -a gas.txt -m Mie.nc -g mountains.obj -R \ + -M materials.mtl \ + -c clouds.htcp -r \ + -C pos=0,0,400:tgt=0,1,0:up=0,0,1 \ + -i def=1280x720:spp=1024 \ + -f -o output + +NOTES +----- +1. High-Tune: Gas Optical Properties file format - + <https://www.meso-star.com/projects/high-tune/downloads/gas_opt_prop_en.pdf> +2. VTK file format - + <http://www.vtk.org/wp-content/uploads/2015/04/file-formats.pdf> +3. MPI specifications - <https://www.mpi-forum.org/docs/> +4. Portable PixMap - <http://netpbm.sourceforge.net/doc/ppm.html> + +COPYRIGHT +--------- +Copyright $copy; 2018, 2019, 2020, 2021 |Meso|Star> <contact@meso-star.com>. +Copyright &copy; 2018, 2019, 2021 CNRS +Copyright &copy; 2018, 2019 Université Paul Sabatier +<contact-edstar@laplace.univ-tlse.fr>. + +LICENSE +------- + +*htrdr-atmosphere* is free software released under the GPLv3+ license: GNU GPL +version 3 or later <https://gnu.org/licenses/gpl.html>. You are free to change +and redistribute it. There is NO WARRANTY, to the extent permitted by law. + +SEE ALSO +-------- +*csplit*(1), +*feh*(1), +*mpirun*(1), +*htcp*(5), +*htmie*(5), +*htpp*(1), +*htrdr*(1), +*htrdr-image*(5), +*htrdr-materials*(5) +*htrdr-obj*(5) diff --git a/doc/htrdr-image.5.txt b/doc/htrdr-image.5.txt @@ -1,5 +1,7 @@ -// Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) -// Copyright (C) 2018-2019 CNRS, Université Paul Sabatier +// 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 +// (contact-edstar@laplace.univ-tlse.fr) // // 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 @@ -30,37 +32,33 @@ thus ignored as well as empty lines. The first valid line stores 2 unsigned integers that represent the image definition, i.e. the number of pixels per line and per column. Then each line stores 8 pixel components. -If the image is a regular rendering in the visible part of the spectrum (*-C* -_camera_ and *-s* _cie_xyz_ options in *htrdr*(1)), the pixel components are -actually 4 pairs of floating points data representing the pixel color encoded -in the CIE 1931 XYZ color space and the per radiative path computation time. -The first, second and third pairs encode the estimated integrated radiance in -W/sr/m^2 of the X, Y and Z pixel components, respectively. The first value of -each pair is the expected value of the estimated radiance while the second one -is its associated standard deviation. The fourth pair saves the estimate in +If the image is a regular rendering in the visible part of the spectrum, the +pixel components are actually 4 pairs of floating points data representing the +pixel color encoded in the CIE 1931 XYZ color space and the per radiative path +computation time. The first, second and third pairs encode the estimated +integrated radiance in W/sr/m^2 of the X, Y and Z pixel components, +respectively. The first value of each pair is the expected value of the +estimated radiance while the second one is its associated standard deviation. +The fourth pair saves the estimate in microseconds of the per radiative path +computation time and its standard error. + +If the image is an infrared rendering the first and second pixel components +store the expected value and the standard error, respectively, of the +estimated brightness temperature in Kelvin. The third and fourth components +save the expected value and standard deviation of the pixel radiance that is +either an integrated radiance in W/sr/m^2 or a spectral radiance in +W/sr/m^2/nm whether this radiance is computed for a spectral range or for one +wavelength. The fifth and sixth pixel components are unused. Finally the last +2 pixel components save, as for a regular rendering, the estimate in microseconds of the per radiative path computation time and its standard error. -If the image is an infrared rendering (*-C* _camera_ and *-s* -*lw*=_wlen-min_,_wlen_max_ options in *htrdr*(1)), the first and second pixel -components store the expected value and the standard error, respectively, of -the estimated brightness temperature in Kelvin. The third and fourth -components save the expected value and standard deviation of the pixel -radiance that is either an integrated radiance in W/sr/m^2 or a spectral -radiance in W/sr/m^2/nm whether this radiance is computed for a spectral range -or for one wavelength. The fifth and sixth pixel components are unused. -Finally the last 2 pixel components save, as for a regular rendering, the -estimate in microseconds of the per radiative path computation time and its -standard error. - -If it was generating from a shortwave rendering (*-C* _camera_ and *-s* -*sw*=_wlen-min_,wlen-max_ options in *htrdr*(1)) the image is formatted as in +If it was generating from a shortwave rendering the image is formatted as in longwave rendering mode exepted that the first and second pixel components are unused since no brightness temperature was evaluated in shortwave. -For flux computations (*-p* _rectangle_ option in *htrdr*(1)), the first and -second pixel component stores the expected value and the standard error of the -pixel flux in W/m^2 for the part of the pixel that is outside any geometry. As +For flux computations, the first and second pixel component stores the +expected value and the standard error of the pixel flux in W/m^2. As previously, the seventh and eighth pixel components store the estimate of the radiative path computation time in microseconds and its standard error. The remaining components, i.e. the components 3 to 6, are unused. diff --git a/doc/htrdr-materials.5.txt b/doc/htrdr-materials.5.txt @@ -1,5 +1,7 @@ -// Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) -// Copyright (C) 2018-2019 CNRS, Université Paul Sabatier +// 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 +// (contact-edstar@laplace.univ-tlse.fr) // // 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 @@ -20,16 +22,16 @@ htrdr-materials(5) NAME ---- -htrdr-materials - list of materials used by the ground geometry in htrdr(1) +htrdr-materials - list of materials used by the geometries in htrdr(1) DESCRIPTION ----------- -A *htrdr-materials* file lists the materials that can be used by the ground -geometry provided through a *htrdr-obj*(5) file to the *htrdr*(1) program. -Each line of the file gives the name of the material. For opaque materials the -material name is followed by the path toward the *mrumtl*(5) file storing the -spectral properties of its associated Bidirectional Reflectance Distribution -Function. Furthermore, the temperature of the material must be defined too. +A *htrdr-materials* file lists the materials that can be used by geometries +provided through a *htrdr-obj*(5) file to the *htrdr*(1) program. Each line +of the file gives the name of the material. For opaque materials the material +name is followed by the path toward the *mrumtl*(5) file storing the spectral +properties of its associated Bidirectional Reflectance Distribution Function. +Furthermore, the temperature of the material must be defined too. The material name can be composed of any characters excepted for spaces and tabulations. The path toward the *mrumtl*(5) file must be a valid path diff --git a/doc/htrdr-obj.5.txt b/doc/htrdr-obj.5.txt @@ -1,5 +1,7 @@ -// Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) -// Copyright (C) 2018-2019 CNRS, Université Paul Sabatier +// 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 +// (contact-edstar@laplace.univ-tlse.fr) // // 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 @@ -27,12 +29,14 @@ DESCRIPTION A *htrdr-obj* file is a regular OBJ [1] composed only of triangular meshes. Each triangle must be included in a material group as defined by the 'usemtl' directive. The name of the material group must be of the form -"<__front-mtl-name__>:<__back-mtl-name__>", where <__front-mtl-name__> and -<__back-mtl-name__> are strings separated by a colon (:) defining the name of -the front and back facing materials, respectively. These names can be composed -of any characters expected for spaces and tabulations. Note that regarding the -*htrdr*(1) convention, a triangle side is said "front facing" when its -vertices are clock-wise ordered. +"<__front-mtl-name__>:[<__interface-mtl-name__>:]<__back-mtl-name__>", where +<__front-mtl-name__>, <__interface-mtl-name__> and <__back-mtl-name__> are +strings separated by a colon (:) defining the name of the front, interface, +and back facing materials, respectively. The interface material name is +optionnal: it is used for thin geometries, i.e. geometries with no thickness. +Material names can be composed of any characters expected for spaces and +tabulations. Note that regarding the *htrdr*(1) convention, a triangle side is +said "front facing" when its vertices are clock-wise ordered. Note that to be a valid *htrdr-obj*(5) file for *htrdr*(1), the front and the back facing names must reference a material listed in *htrdr-materials*(5) diff --git a/doc/htrdr.1.txt b/doc/htrdr.1.txt @@ -0,0 +1,77 @@ +// 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 +// (contact-edstar@laplace.univ-tlse.fr) +// +// 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/>. +:toc: + +htrdr(1) +======== + +NAME +---- +htrdr - the Monte-Carlo radiative transfert simulator + +SYNOPSIS +-------- +[verse] +*htrdr* [--version] [--help] <__mode__> [<__args__>] + +DESCRIPTION +----------- +*htrdr* computes images of scenes composed of semi transparent materials and +geometries with spectral varying reflectivities. The way the scenes are +described and the solved Monte-Carlo algorithms are controlled by the _mode_ +argument that defines the computation context. + +OPTIONS +------- +*--help*:: + Print short help and exit. + +*--version*:: + Display version information and exit. + +MODES +----- + +Each *htrdr* _mode_ is actually a mapping to a seperate command line named +*htrdr-<__mode__>* with its own set of arguments. Refer to their corresponding +man page for their complete description. + +The available *htrdr-<__mode__>* commands are: + +*htrdr-atmosphere*(1):: + Radiative transfer computations in a cloudy atmosphere. + +*htrdr-combustion*(1):: + Radiative transfer computations in a combustion medium. + +COPYRIGHT +--------- +Copyright &copy; 2018, 2019, 2020, 2021 |Meso|Star> <contact@meso-star.com>. +Copyright &copy; 2018, 2019, 2021 CNRS. +Copyright &copy; 2018, 2019 Université Paul Sabatier <contact-edstar@laplace.univ-tlse.fr>. + +LICENSE +------- +*htrdr* is free software released under the GPLv3+ license: GNU GPL version 3 +or later <https://gnu.org/licenses/gpl.html>. You are free to change and +redistribute it. There is NO WARRANTY, to the extent permitted by law. + +SEE ALSO +-------- +*htrdr-atmosphere*(1), +*htrdr-combustion*(1) diff --git a/doc/htrdr.1.txt.in b/doc/htrdr.1.txt.in @@ -1,398 +0,0 @@ -// Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) -// Copyright (C) 2018-2019 CNRS, Université Paul Sabatier -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. -:toc: - -htrdr(1) -======== - -NAME ----- -htrdr - simulate radiative transfert in cloudy atmospheres - -SYNOPSIS --------- -[verse] -*htrdr* [_option_]... -a _atmosphere_ - -DESCRIPTION ------------ -*htrdr* simulate radiative transfert in scenes composed of an atmospheric gas -mixture, clouds, and a ground. It evaluates the intensity incoming on each -pixel of the sensor array. The underlying algorithm is based on a Monte-Carlo -method: it consists in simulating a given number of optical paths originating -from the sensor, directed into the atmosphere, taking into account light -absorption and scattering phenomena. - -Radiative transfert can be evaluated in the visible or the infrared part of the -spectrum. It uses spectral data that should be provided for the pressure and -temperature atmospheric vertical profile [1] (*-a* _atmosphere_), the liquid -water content in suspension within the clouds stored in a *htcp*(5) file (*-c* -_clouds_), and the optical properties of water droplets that have been obtained -from a Mie code and formatted according to the *htmie*(5) file format (*-m* -_mie_). The user also has to set the position of the sun (*-D* -_azimuth_,_elevation_), the sensor type (*-C* _camera_ or *-p* _rectangle_) and -its definition (*-i* _image_). It is also possible to provide an *htrdr-obj*(5) -file representing the ground geometry (*-g* _ground_) whose materials are -listed in the *htrdr-material*(5) file provided through the *-M* option. Both, -the clouds and the ground, can be infinitely repeated along the X and Y axis by -setting the *-r* and the *-R* options, respectively. - -Two types of sensor are supported by *htrdr*. The camera (*-C* _camera_) is -used to render an image of the scene from the given point of view while the -rectangle sensor (*-p* _rectangle_) is used to compute a flux map. - -Spectral dimension can be integrated in many ways (*-s* option). When rendering -an image (*-C* _camera_), the computation is by default performed for the -visible part of the spectrum in [380, 780] nanometers, for the three components -of the CIE 1931 XYZ colorimetric space that are subsequently recombined in -order to obtain the final color for each pixel, and finally the whole image of -the scene as seen from the set observation position. The two other ways consist -in explicitly defining the longwave or shortwave spectral range to handle and -continuously sampling a wavelength in this range. Actually longwave and -shortwave are keywords that mean that the source of radiation is whether -external or internal to the medium, respectively. In shortwave rendering, only -the pixel radiance is evaluated and stored in the output image. For longwave -rendering this estimated radiance is then converted to its brightness -temperature and both are saved in the image. When computing a flux map (*-p* -_rectangle_), the per pixel flux is saved into the output map whether spectral -domain is longwave or shortwave. - -In *htrdr* the spatial unit 1.0 corresponds to one meter and the temperatures -are expressed in Kelvin. The estimated radiances are given in W/sr/m^2 excepted -for monochromatic computations where the computed spectral radiance is defined -in W/sr/m^2/nm. The fluxes are saved in W/m^2. The results are written to the -output file if the *-o* option is defined and the standard output otherwise. -The output image is a list of raw ASCII data formatted with respect to the -*htrdr-image*(5) file format. Since *htrdr* relies on the Monte-Carlo method, -each estimation is given with its numerical accuracy. - -During the simulation, *htrdr* dynamically loads/unloads cloud properties to -handle clouds whose data that do not feat in main memory. *htrdr* also supports -shared memory parallelism and relies on the Message Passing Interface -specification [3] to parallelise its computations in a distributed memory -environment; it can thus be run either directly or through a MPI process -launcher like *mpirun*(1). - -OPTIONS -------- -*-a* _atmosphere_:: - Path toward the file containing the gas optical properties of the atmosphere. - Data must be formatted according to the fileformat described in [1]. - -*-c* _clouds_:: - Submit a *htcp*(5) file describing the properties of the clouds. If not - defined, only the atmosphere properties submitted through the *-a* option - are taken into account. - -*-C* <__camera-parameter__:...>:: - Define the rendering point of view. Available camera parameters are: - - **fov**=**_angle_**;; - Vertical field of view of the camera in [30, 120] degrees. By default - _angle_ is set to @HTRDR_ARGS_DEFAULT_CAMERA_FOV@ degrees. - - **pos**=**_x_**,**_y_**,**_z_**;; - Position of the camera. By default it is set to - {@HTRDR_ARGS_DEFAULT_CAMERA_POS@}. - - **tgt**=**_x_**,**_y_**,**_z_**;; - Position targeted by the camera. By default it is set to - {@HTRDR_ARGS_DEFAULT_CAMERA_TGT@}. - - **up**=**_x_**,**_y_**,**_z_**;; - Up vector of the camera. By default it is set to - {@HTRDR_ARGS_DEFAULT_CAMERA_UP@}. - -*-D* <__azimuth__,__elevation__>:: - Direction toward the sun center. The direction is defined by two angles in - degrees: the __azimuth__ angle in [0, 360[ and the __elevation__ angle in [0, - 90]. Following the right-handed convention, the azimuthal rotation is - counter-clockwise, with 0 degree on the X axis. The elevation starts from 0 - degree for a direction in the XY plane, up to 90 degrees at zenith. Thus - -D0,0 -D90,0 -D180,0 and -D270,0 will produce solar vectors {+1,0,0} {0,+1,0} - {-1,0,0} and {0,-1,0} respectively, while -D__azimuth__,90 will produce - {0,0,+1} regardless of _azimuth_ value. - -*-d*:: - Write in _output_ the space partitioning data structures used to speed up - the radiative transfer computations in the clouds. The written data are - octrees saved in the VTK file format [2]. Each octree node stores the minimum - and the maximum of the extinction coefficients of the cloud cells overlapped - by the octree node. In the _output_ file, each octree is separated from the - previous one by a line with three minus characters, i.e. '---'. - -*-f*:: - Force overwrite of the _output_ file. - -*-g* _ground_:: - Path toward a *htrdr-obj*(5) representing the ground geometry. - -*-h*:: - List short help and exit. - -*-i* <__image-parameter__:...>:: - Define the sensor array. Available image parameters are: - - **def**=**_width_**x**_height_**;; - Definition of the image. By default the image definition is - @HTRDR_ARGS_DEFAULT_IMG_WIDTH@x@HTRDR_ARGS_DEFAULT_IMG_HEIGHT@. - - **spp**=**_samples-count_**;; - Number of samples per pixel estimation. In regular image rendering, a pixel - will use "3 * _samples-count_" Monte-Carlo realisations, one set of - _samples-count_ realisations for each X, Y and Z component of the CIE 1931 - XYZ color space. In shortwave/longwave rendering or flux computation, only - one set of _samples-count_ is used. By default, *spp* is set to - @HTRDR_ARGS_DEFAULT_IMG_SPP@. - -*-R*:: - Infinitely repeat the _ground_ along the X and Y axis. - -*-r*:: - Infinitely repeat the _clouds_ along the X and Y axis. - -*-M* _materials_:: - Path toward a *htrdr-materials*(5) file listing the ground materials. - -*-m* _mie_:: - Path toward a *htmie*(5) file defining the optical properties of water - droplets. - -*-n* _sky-mtl_:: - Name of the material representing the sky in the *htrdr-materials*(5) file. - By default, _sky-mtl_ is @HTRDR_ARGS_DEFAULT_SKY_MTL_NAME@. - -*-O* _cache_:: - File used to cache the sky data. If the _cache_ file does not exists, it is - created and filled with the sky data built from the _clouds_, the - _atmosphere_ and the _mie_ input files. This cached data can then be reused - in the next runs as long as the input files provided on the command are the - same as the ones used to setup the cache; leading to a significant speed-up - of the pre-processing step. If _cache_ contains data generated from input - files that are not the ones submitted on the command line, an error is - notified and the execution is stopped, avoiding the use of wrong cached data. - Note that when the cache is used, *htrdr* ignores the arguments used to - parametrise the structures partitioning the sky data, i.e. the *-T* and *-V* - options. - -*-o* _output_:: - File where *htrdr* writes its _output_ data. If not defined, write results to - standard output. - -*-p* <__rectangle-parameter__:...>:: - Switch in flux map computation. The flux is computed for the part of the - sensor that is outside any geometry. The rectangular sensor onto which the - flux is integrated is defined by the following parameters: - - **pos**=**_x_**,**_y_**,**_z_**;; - Position of the center of the rectangle. By default it is set to - {@HTRDR_ARGS_DEFAULT_RECTANGLE_POS@}. - - **tgt**=**_x_**,**_y_**,**_z_**;; - Position targeted by the rectangle, i.e. *tgt* - *pos* is the rectangle - normal. By default it is set to {@HTRDR_ARGS_DEFAULT_RECTANGLE_TGT@}. - - **up**=**_x_**,**_y_**,**_z_**;; - Up vector of the rectangle. By default it is set to - {@HTRDR_ARGS_DEFAULT_RECTANGLE_UP@}. - - **sz**=**_width_**,**_height_**;; - Size of the rectangle. By default it is set to - {@HTRDR_ARGS_DEFAULT_RECTANGLE_SZ@}. - -*-s* <__spectral-parameter__:...>:: - Define the type and the range of the spectral integration. Available spectral - parameters are: - - **cie_xyz**;; - the radiance is computed for the visible part of the spectrum in [380, 780] - nanometers with respect to the XYZ CIE 1931 tristimulus values. This is the - default comportment of *htrdr*. - - **lw**=*_wlen-min_*,*_wlen-max_*;; - perform the spectral sampling continuously in the [_wlen-min_, _wlen-max_] - wavelength range (wavelength must be provided in nanometers) according to - the Planck function for a reference temperature. If _wlen-min_ and - _wlen-max_ are equals the computation is monochromatic. *lw* means for - longwave but is here a code word that really means "computation of radiance - using the internal source of radiation": in other words, radiation is - emitted by the medium and its boundaries (ground and space). Because the - application is for the terrestrial atmosphere, internal radiation is - emitted in the thermal longwave part of the electromagnetic spectrum. - Therefore the default value of the reference temperature used in the - spectral sampling is fixed at 290 K. - - **sw**=*_wlen-min_*,*_wlen-max_*;; - perform the spectral sampling continuously in the [_wlen-min_, _wlen-max_] - wavelength range (wavelength must be provided in nanometers) according to - the Planck function for a reference temperature. If _wlen-min_ and - _wlen-max_ are equals the computation is monochromatic. In the present - case, *sw* means that the source of radiation is external to the medium: - because the application is for the terrestrial atmosphere, the value of the - reference temperature is by default fixed at 5778 K, i.e. the blackbody - temperature of the Sun. - - **Tref**=**_temperature_**;; - reference temperature of the Planck function used to continuously sample the - longwave/shortwave spectral range. In longwave, it is set to 290 K by - default while in shortwave its default value is the blackbody temperature - of the sun (i.e. 5778 K). - -*-T* _threshold_:: - Optical thickness used as threshold criteria to partition the properties of - the clouds. By default its value is @HTRDR_ARGS_DEFAULT_OPTICAL_THICKNESS_THRESHOLD@. - This option is ignored if a cache file is used (option *-O*). - -*-t* _threads-count_:: - Hint on the number of threads to use. By default use as many threads as CPU - cores. - -*-V* **_x_**,**_y_**,**_z_**:: - Define the maximum definition of the acceleration data structure that - partitions the cloud properties. By default the finest definition is the - definition of the submitted *htcp*(5) file. This option is ignored if a cache - file is used (option *-O*). - -*-v*:: - Make *htrdr* verbose. - -*--version*:: - Display version information and exit. - -EXAMPLES --------- - -Render a clear sky scene, i.e. a scene without any cloud, whose sun is at -zenith. The vertical atmospheric gaz mixture along the Z axis is described in -the *gas.txt* file. the ground geometry is a quad repeated to the infinity -whose materials are listed in the *material.mtl* file. The camera is positioned -at *400* meters height and looks toward the positive Y axis. The definition of -the rendered image is *800* by *600* pixels and the radiance of each pixel -component is estimated with *64* Monte-Carlo realisations. The resulting image -is written to *output* excepted if the file already exists; in this case an -error is notified, the program stops and the *output* file remains unchanged: - - $ htrdr -D0,90 -a gas.txt -m Mie.nc -g quad.obj -R \ - -M materials.mtl \ - -C pos=0,0,400:tgt=0,1,0:up=0,0,1 \ - -i def=800x600:spp=64 \ - -o output - -Add clouds to the previous scene and use a more complex geometry to represent -the ground. The Mie data are provided through the *Mie.nc* file. The ground -geometry was carefully designed to be cyclic and can be thus infinitely -repeated without visual glitches. Use the *-f* option to write the rendered -image to *output* even though the file already exists. Finally, use the -*htpp*(1) command to convert the *htrdr-image*(5) saved in output in a regular -PPM image [4]: - - $ htrdr -D0,90 -a gas.txt -m Mie.nc -g mountains.obj -R \ - -M materials.mtl \ - -c clouds.htcp \ - -C pos=0,0,400:tgt=0,1,0:up=0,0,1 \ - -i def=800x600:spp=64 \ - -f -o output - $ htpp -o image.ppm output - -Render the previous scene in infrared for the wavelengths in [*9200*, *10000*] -nanometers with a reference temperature of *300* Kelvin: - - $ htrdr -a gas.txt -m Mie.nc -g mountains.obj -R \ - -M materials.mtl \ - -c clouds.htcp \ - -C pos=0,0,400:tgt=0,1,0:up=0,0,1 \ - -i def=800x600:spp=64 \ - -s lw=9200,10000:Tref=300 - -f -o output - -Move the sun by setting its azimuthal and elevation angles to *120* and *40* -degrees respectively. Use the *-O* option to enable the cache mechanism on -sky data. Increase the image definition to *1280* by *720* and set the -number of samples per pixel component to *1024*. Write results on standard -output and convert the resulting image in PPM before visualising it through the -*feh*(1) image viewer: - - $ htrdr -D120,40 -a gas.txt -m Mie.nc -g mountains.obj -R \ - -M materials.mtl \ - -c clouds.htcp \ - -O my_cache \ - -C pos=0,0,400:tgt=0,1,0:up=0,0,1 \ - -i def=1280x720:spp=1024 | htpp | feh - - -Compute the downward flux for the shortwave interval [*350*, *4000*] nanometers -on a square of *100* meters side positionned at the origin at *1* meter height. -The resolution of the flux map is *500* by *500* pixels and *1000* realisations -is used to estimate the flux per pixel. It is saved in the *flux_map.txt* file -even though this file already exists: - - $ htrdr -D0,90 -a gas.txt -m Mie.nc -g plane.obj -R \ - -M materials.mtl \ - -c clouds.htcp \ - -O my_cache \ - -p pos=0,0,1:tgt=0,0,2:up=0,1,0:sz=100,100 \ - -i def=500x500:spp=1000 \ - -s sw=350,4000 \ - -f -o flux_map.txt - -Write into *output* the data structures used to partition the clouds -properties. Use the *csplit*(1) Unix command to extract from *output* the list -of the generated grids and save each of them in a specific VTK file whose name -is *cloud_grid_*<__NUM__>*.vtk with __NUM__ in [0, N-1] where N is the number -of grids written into *output*: - - $ htrdr -a gas.txt -m Mie.nc -c clouds.htcp -d -f -o output - $ csplit -f cloud_grid_ -b %02d.vtk -z --suppress-matched output /^---$/ {*} - -Use *mpirun*(1) to launch *htrdr* on several hosts defined in the *my_hosts* -file. Make the clouds infinite along the X and Y axis: - - $ mpirun --hostfile my_hosts htrdr \ - -D120,40 -a gas.txt -m Mie.nc -g mountains.obj -R \ - -M materials.mtl \ - -c clouds.htcp -r \ - -C pos=0,0,400:tgt=0,1,0:up=0,0,1 \ - -i def=1280x720:spp=1024 \ - -f -o output - -NOTES ------ -1. High-Tune: Gas Optical Properties file format - - <https://www.meso-star.com/projects/high-tune/downloads/gas_opt_prop_en.pdf> -2. VTK file format - - <http://www.vtk.org/wp-content/uploads/2015/04/file-formats.pdf> -3. MPI specifications - <https://www.mpi-forum.org/docs/> -4. Portable PixMap - <http://netpbm.sourceforge.net/doc/ppm.html> - -COPYRIGHT ---------- -Copyright &copy; 2018, 2019, 2020 |Meso|Star> <contact@meso-star.com>. -Copyright &copy; 2018, 2019 CNRS, Université Paul Sabatier -<contact-edstar@laplace.univ-tlse.fr>. *htrdr* is free software released under -the GPLv3+ license: GNU GPL version 3 or later -<https://gnu.org/licenses/gpl.html>. You are free to change and redistribute -it. There is NO WARRANTY, to the extent permitted by law. - -SEE ALSO --------- -*csplit*(1), -*feh*(1), -*mpirun*(1), -*htcp*(5), -*htmie*(5), -*htpp*(1), -*htrdr-image*(5), -*htrdr-materials*(5) -*htrdr-obj*(5) diff --git a/src/atmosphere/htrdr_atmosphere.c b/src/atmosphere/htrdr_atmosphere.c @@ -0,0 +1,486 @@ +/* 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 + +#include "atmosphere/htrdr_atmosphere.h" +#include "atmosphere/htrdr_atmosphere_c.h" +#include "atmosphere/htrdr_atmosphere_args.h" +#include "atmosphere/htrdr_atmosphere_ground.h" +#include "atmosphere/htrdr_atmosphere_sun.h" + +#include "core/htrdr_buffer.h" +#include "core/htrdr_camera.h" +#include "core/htrdr_cie_xyz.h" +#include "core/htrdr_log.h" +#include "core/htrdr_materials.h" +#include "core/htrdr_ran_wlen.h" +#include "core/htrdr_rectangle.h" + +#include <high_tune/htsky.h> + +#include <rsys/cstr.h> + +#include <math.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static double +compute_sky_min_band_len(struct htsky* sky, const double range[2]) +{ + double min_band_len = DBL_MAX; + size_t nbands; + ASSERT(sky && range && range[0] <= range[1]); + + nbands = htsky_get_spectral_bands_count(sky); + + if(eq_eps(range[0], range[1], 1.e-6)) { + ASSERT(nbands == 1); + min_band_len = 0; + } else { + size_t i = 0; + + /* Compute the length of the current band clamped to the submitted range */ + FOR_EACH(i, 0, nbands) { + const size_t iband = htsky_get_spectral_band_id(sky, i); + double wlens[2]; + HTSKY(get_spectral_band_bounds(sky, iband, wlens)); + + /* Adjust band boundaries to the submitted range */ + wlens[0] = MMAX(wlens[0], range[0]); + wlens[1] = MMIN(wlens[1], range[1]); + + min_band_len = MMIN(wlens[1] - wlens[0], min_band_len); + } + } + return min_band_len; +} + +/* Compute the number of fixed size bands used to discretized the spectral + * range */ +static size_t +compute_spectral_bands_count(const struct htrdr_atmosphere* cmd) +{ + double wlen_range[2]; + double wlen_range_size; + size_t nbands; + double band_len; + double band_len_max; + ASSERT(cmd); + + /* Compute size of the spectral range in nanometers */ + wlen_range[0] = cmd->wlen_range_m[0]*1.e9; + wlen_range[1] = cmd->wlen_range_m[1]*1.e9; + wlen_range_size = wlen_range[1] - wlen_range[0]; + + /* Define as many intervals as wavelengths count in the spectral range */ + nbands = (size_t)rint(wlen_range_size); + + /* Compute the size in nanometers of an interval */ + band_len = wlen_range_size / (double)nbands; + + /* Compute the minimum band length of the sky spectral data and define it + * as the maximum length that the bands can have */ + band_len_max = compute_sky_min_band_len(cmd->sky, wlen_range); + + /* Adjust the bands count to ensure that each sky spectral interval is + * overlapped by at least one band */ + if(band_len > band_len_max) { + nbands = (size_t)ceil(wlen_range_size / band_len_max); + } + return nbands; +} + +static enum htsky_spectral_type +htrdr_to_sky_spectral_type(const enum htrdr_spectral_type type) +{ + enum htsky_spectral_type spectype; + switch(type) { + case HTRDR_SPECTRAL_LW: + spectype = HTSKY_SPECTRAL_LW; + break; + case HTRDR_SPECTRAL_SW: + case HTRDR_SPECTRAL_SW_CIE_XYZ: + spectype = HTSKY_SPECTRAL_SW; + break; + default: FATAL("Unreachable code.\n"); break; + } + return spectype; +} + +static INLINE void +spherical_to_cartesian_dir + (const double azimuth, /* In radians */ + const double elevation, /* In radians */ + double dir[3]) +{ + double cos_azimuth; + double sin_azimuth; + double cos_elevation; + double sin_elevation; + ASSERT(azimuth >= 0 && azimuth < 2*PI); + ASSERT(elevation >= 0 && elevation <= PI/2.0); + ASSERT(dir); + + cos_azimuth = cos(azimuth); + sin_azimuth = sin(azimuth); + cos_elevation = cos(elevation); + sin_elevation = sin(elevation); + + dir[0] = cos_elevation * cos_azimuth; + dir[1] = cos_elevation * sin_azimuth; + dir[2] = sin_elevation; +} + +static res_T +setup_sensor + (struct htrdr_atmosphere* cmd, + const struct htrdr_atmosphere_args* args) +{ + double proj_ratio; + res_T res = RES_OK; + ASSERT(cmd && args); + + cmd->sensor.type = args->sensor_type; + + if(args->spectral.spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ + && args->sensor_type != HTRDR_SENSOR_CAMERA) { + htrdr_log_err(cmd->htrdr, "the CIE 1931 XYZ spectral integration can be used " + "only with a camera sensor.\n"); + res = RES_BAD_ARG; + goto error; + } + + switch(args->sensor_type) { + case HTRDR_SENSOR_CAMERA: + proj_ratio = + (double)args->image.definition[0] + / (double)args->image.definition[1]; + res = htrdr_camera_create + (cmd->htrdr, + args->sensor.camera.position, + args->sensor.camera.target, + args->sensor.camera.up, + proj_ratio, + MDEG2RAD(args->sensor.camera.fov_y), + &cmd->sensor.camera); + break; + case HTRDR_SENSOR_RECTANGLE: + res = htrdr_rectangle_create + (cmd->htrdr, + args->sensor.rectangle.size, + args->sensor.rectangle.position, + args->sensor.rectangle.target, + args->sensor.rectangle.up, + &cmd->sensor.rectangle); + break; + default: FATAL("Unreachable code.\n"); break; + } + if(res != RES_OK) goto error; + +exit: + return res; +error: + goto exit; +} + +static res_T +dump_volumetric_acceleration_structure(struct htrdr_atmosphere* cmd) +{ + size_t nbands; + size_t i; + res_T res = RES_OK; + ASSERT(cmd); + + nbands = htsky_get_spectral_bands_count(cmd->sky); + + /* Nothing to do */ + if(htrdr_get_mpi_rank(cmd->htrdr) != 0) goto exit; + + FOR_EACH(i, 0, nbands) { + size_t iquad; + const size_t iband = htsky_get_spectral_band_id(cmd->sky, i); + const size_t nquads = htsky_get_spectral_band_quadrature_length + (cmd->sky, iband); + + FOR_EACH(iquad, 0, nquads) { + res = htsky_dump_cloud_vtk(cmd->sky, iband, iquad, cmd->output); + if(res != RES_OK) goto error; + fprintf(cmd->output, "---\n"); + } + } + +exit: + return res; +error: + goto exit; +} + +static void +atmosphere_release(ref_T* ref) +{ + struct htrdr_atmosphere* cmd = CONTAINER_OF(ref, struct htrdr_atmosphere, ref); + struct htrdr* htrdr = NULL; + ASSERT(ref); + + if(cmd->ground) htrdr_atmosphere_ground_ref_put(cmd->ground); + if(cmd->mats) htrdr_materials_ref_put(cmd->mats); + if(cmd->sun) htrdr_atmosphere_sun_ref_put(cmd->sun); + if(cmd->cie) htrdr_cie_xyz_ref_put(cmd->cie); + if(cmd->ran_wlen) htrdr_ran_wlen_ref_put(cmd->ran_wlen); + if(cmd->sensor.camera) htrdr_camera_ref_put(cmd->sensor.camera); + if(cmd->sensor.rectangle) htrdr_rectangle_ref_put(cmd->sensor.rectangle); + if(cmd->buf) htrdr_buffer_ref_put(cmd->buf); + if(cmd->sky) HTSKY(ref_put(cmd->sky)); + if(cmd->output && cmd->output != stdout) fclose(cmd->output); + str_release(&cmd->output_name); + + htrdr = cmd->htrdr; + MEM_RM(htrdr_get_allocator(htrdr), cmd); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +htrdr_atmosphere_create + (struct htrdr* htrdr, + const struct htrdr_atmosphere_args* args, + struct htrdr_atmosphere** out_cmd) +{ + struct htrdr_atmosphere* cmd = NULL; + struct htsky_args htsky_args = HTSKY_ARGS_DEFAULT; + double sun_dir[3]; + double spectral_range[2]; + const char* output_name = NULL; + size_t nintervals; /* #bands used to discretized the spectral curve */ + 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, + "%s: could not allocate the htrdr_atmosphere data.\n", FUNC_NAME); + res = RES_MEM_ERR; + goto error; + } + ref_init(&cmd->ref); + str_init(htrdr_get_allocator(htrdr), &cmd->output_name); + cmd->dump_volumetric_acceleration_structure = + args->dump_volumetric_acceleration_structure; + cmd->verbose = args->verbose; + cmd->spp = args->image.spp; + cmd->width = args->image.definition[0]; + cmd->height = args->image.definition[1]; + cmd->grid_max_definition[0] = args->grid_max_definition[0]; + cmd->grid_max_definition[1] = args->grid_max_definition[1]; + cmd->grid_max_definition[2] = args->grid_max_definition[2]; + cmd->spectral_type = args->spectral.spectral_type; + cmd->ref_temperature = args->spectral.ref_temperature; + cmd->sky_mtl_name = args->sky_mtl_name; + + /* Get ownership on the htrdr structure */ + htrdr_ref_get(htrdr); + cmd->htrdr = htrdr; + + if(!args->filename_output) { + cmd->output = stdout; + output_name = "<stdout>"; + } else if(htrdr_get_mpi_rank(htrdr) != 0) { + cmd->output = NULL; + output_name = "<null>"; + } else { + res = htrdr_open_output_stream(htrdr, args->filename_output, 0/*read*/, + args->force_overwriting, &cmd->output); + if(res != RES_OK) goto error; + output_name = args->filename_output; + } + res = str_set(&cmd->output_name, output_name); + if(res != RES_OK) { + htrdr_log_err(htrdr, + "%s: could not store the name of the output stream `%s' -- %s.\n", + FUNC_NAME, output_name, res_to_cstr(res)); + goto error; + } + + /* Materials are necessary only if a ground geometry is defined */ + if(args->filename_obj) { + res = htrdr_materials_create(htrdr, args->filename_mtl, &cmd->mats); + if(res != RES_OK) goto error; + } + + res = htrdr_atmosphere_ground_create(htrdr, args->filename_obj, cmd->mats, + args->repeat_ground, &cmd->ground); + if(res != RES_OK) goto error; + + res = setup_sensor(cmd, args); + if(res != RES_OK) goto error; + + res = htrdr_atmosphere_sun_create(cmd->htrdr, &cmd->sun); + if(res != RES_OK) goto error; + spherical_to_cartesian_dir + (MDEG2RAD(args->sun_azimuth), MDEG2RAD(args->sun_elevation), sun_dir); + htrdr_atmosphere_sun_set_direction(cmd->sun, sun_dir); + + htsky_args.htcp_filename = args->filename_les; + htsky_args.htgop_filename = args->filename_gas; + htsky_args.htmie_filename = args->filename_mie; + htsky_args.cache_filename = args->filename_cache; + htsky_args.grid_max_definition[0] = args->grid_max_definition[0]; + htsky_args.grid_max_definition[1] = args->grid_max_definition[1]; + htsky_args.grid_max_definition[2] = args->grid_max_definition[2]; + htsky_args.optical_thickness = args->optical_thickness; + htsky_args.nthreads = (unsigned)htrdr_get_threads_count(htrdr); + htsky_args.repeat_clouds = args->repeat_clouds; + htsky_args.verbose = htrdr_get_mpi_rank(htrdr) == 0 ? args->verbose : 0; + htsky_args.spectral_type = htrdr_to_sky_spectral_type(args->spectral.spectral_type); + htsky_args.wlen_range[0] = args->spectral.wlen_range[0]; + htsky_args.wlen_range[1] = args->spectral.wlen_range[1]; + res = htsky_create(htrdr_get_logger(htrdr), htrdr_get_allocator(htrdr), + &htsky_args, &cmd->sky); + if(res != RES_OK) goto error; + + HTSKY(get_raw_spectral_bounds(cmd->sky, spectral_range)); + + spectral_range[0] = MMAX(args->spectral.wlen_range[0], spectral_range[0]); + spectral_range[1] = MMIN(args->spectral.wlen_range[1], spectral_range[1]); + if(spectral_range[0] != args->spectral.wlen_range[0] + || spectral_range[1] != args->spectral.wlen_range[1]) { + htrdr_log_warn(htrdr, + "%s: the submitted spectral range overflowed the spectral data.\n", + FUNC_NAME); + } + + cmd->wlen_range_m[0] = spectral_range[0]*1e-9; /* Convert in meters */ + cmd->wlen_range_m[1] = spectral_range[1]*1e-9; /* Convert in meters */ + + /* Compute the number of fixed sized bands used to descrised to the spectral + * data */ + nintervals = compute_spectral_bands_count(cmd); + + if(cmd->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ) { + res = htrdr_cie_xyz_create(htrdr, spectral_range, nintervals, &cmd->cie); + if(res != RES_OK) goto error; + } else { + if(cmd->ref_temperature <= 0) { + htrdr_log_err(htrdr, "%s: invalid reference temperature %g K.\n", + FUNC_NAME, cmd->ref_temperature); + res = RES_BAD_ARG; + goto error; + } + res = htrdr_ran_wlen_create + (htrdr, spectral_range, nintervals, cmd->ref_temperature, &cmd->ran_wlen); + if(res != RES_OK) goto error; + } + + + if(!cmd->dump_volumetric_acceleration_structure) { + struct atmosphere_pixel_format pixfmt = ATMOSPHERE_PIXEL_FORMAT_NULL; + atmosphere_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(htrdr) == 0) { + res = htrdr_buffer_create(htrdr, &cmd->buf_layout, &cmd->buf); + if(res != RES_OK) goto error; + } + } + +exit: + *out_cmd = cmd; + return res; +error: + if(cmd) { + htrdr_atmosphere_ref_put(cmd); + cmd = NULL; + } + goto exit; +} + +void +htrdr_atmosphere_ref_get(struct htrdr_atmosphere* cmd) +{ + ASSERT(cmd); + ref_get(&cmd->ref); +} + +void +htrdr_atmosphere_ref_put(struct htrdr_atmosphere* cmd) +{ + ASSERT(cmd); + ref_put(&cmd->ref, atmosphere_release); +} + +res_T +htrdr_atmosphere_run(struct htrdr_atmosphere* cmd) +{ + res_T res = RES_OK; + + if(cmd->dump_volumetric_acceleration_structure) { + res = dump_volumetric_acceleration_structure(cmd); + if(res != RES_OK) goto error; + } else { + res = atmosphere_draw_map(cmd); + if(res != RES_OK) goto error; + } + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +void +atmosphere_get_pixel_format + (const struct htrdr_atmosphere* cmd, + struct atmosphere_pixel_format* fmt) +{ + switch(cmd->sensor.type) { + case HTRDR_SENSOR_RECTANGLE: + fmt->size = sizeof(struct atmosphere_pixel_flux); + fmt->alignment = ALIGNOF(struct atmosphere_pixel_flux); + break; + case HTRDR_SENSOR_CAMERA: + switch(cmd->spectral_type) { + case HTRDR_SPECTRAL_LW: + case HTRDR_SPECTRAL_SW: + fmt->size = sizeof(struct atmosphere_pixel_xwave); + fmt->alignment = ALIGNOF(struct atmosphere_pixel_xwave); + break; + case HTRDR_SPECTRAL_SW_CIE_XYZ: + fmt->size = sizeof(struct atmosphere_pixel_image); + fmt->alignment = ALIGNOF(struct atmosphere_pixel_image); + break; + default: FATAL("Unreachable code.\n"); break; + } + break; + default: FATAL("Unreachable code.\n"); break; + } +} + diff --git a/src/atmosphere/htrdr_atmosphere.h b/src/atmosphere/htrdr_atmosphere.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019, Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef HTRDR_ATMOSPHERE_H +#define HTRDR_ATMOSPHERE_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +/* Forward declarations */ +struct htrdr; +struct htrdr_atmosphere; +struct htrdr_atmosphere_args; + +BEGIN_DECLS + +HTRDR_API res_T +htrdr_atmosphere_create + (struct htrdr* htrdr, + const struct htrdr_atmosphere_args* args, + struct htrdr_atmosphere** cmd); + +HTRDR_API void +htrdr_atmosphere_ref_get + (struct htrdr_atmosphere* cmd); + +HTRDR_API void +htrdr_atmosphere_ref_put + (struct htrdr_atmosphere* cmd); + +HTRDR_API res_T +htrdr_atmosphere_run + (struct htrdr_atmosphere* cmd); + +HTRDR_API int +htrdr_atmosphere_main + (int argc, + char** argv); + +END_DECLS + +#endif /* HTRDR_ATMOSPHERE_H */ + diff --git a/src/atmosphere/htrdr_atmosphere_args.c b/src/atmosphere/htrdr_atmosphere_args.c @@ -0,0 +1,298 @@ +/* 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 "atmosphere/htrdr_atmosphere_args.h" + +#include <rsys/cstr.h> + +#include <getopt.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +print_help(const char* cmd) +{ + ASSERT(cmd); + printf("Usage: %s [<opions>] -a GAS\n", cmd); + printf( +"Render an image or compute a flux map in both the longwave and\n" +"shortwave domains, for scenes with a cloudy atmosphere and a ground\n" +"geometry.\n\n"); + + printf( +" -a GAS filename of the gas optical properties.\n"); + + printf( +" -C <camera> define the rendering point of view. Refer to the\n" +" %s man page for the list of camera options.\n", cmd); + printf( +" -c CLOUDS filename of the clouds properties.\n"); + printf( +" -D AZIMUTH,ELEVATION\n" +" direction in degrees toward the sun center. By default\n" +" AZIMUTH is %g and ELEVATION is %g.\n", + HTRDR_ATMOSPHERE_ARGS_DEFAULT.sun_azimuth, + HTRDR_ATMOSPHERE_ARGS_DEFAULT.sun_elevation); + printf( +" -d dump volumetric acceleration structures to OUTPUT\n" +" and exit.\n"); + printf( +" -f overwrite the OUTPUT file if it already exists.\n"); + printf( +" -g GROUND filename of the ground geometry.\n"); + printf( +" -h display this help and exit.\n"); + printf( +" -i <image> define the image to compute. Refer to the %s man\n" +" page for the list of image options\n", cmd); + printf( +" -M MATERIALS filename of the ground materials.\n"); + printf( +" -m MIE filename of the Mie's data.\n"); + printf( +" -n SKY-NAME name used to identify the sky in the MATERIALS file.\n" +" Its default value is `%s'.\n", + HTRDR_ATMOSPHERE_ARGS_DEFAULT.sky_mtl_name); + printf( +" -O CACHE filenaname of the cache file used to store/restore the\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 <rectangle> switch in flux computation by defining the rectangular\n" +" sensor onto wich the flux is computed. Refer to the\n" +" %s man page for the list of rectangle options.\n", cmd); + printf( +" -R infinitely repeat the ground along the X and Y axis.\n"); + printf( +" -r infinitely repeat the clouds along the X and Y axis.\n"); + printf( +" -s <spectral> define the type and range of the spectral\n" +" integration. Refer to the %s man page for the list\n" +" of spectral options\n", cmd); + printf( +" -T THRESHOLD optical thickness used as threshold during the\n" +" building of the volumetric acceleration structure.\n" +" By default its value is `%g'.\n", + HTRDR_ATMOSPHERE_ARGS_DEFAULT.optical_thickness); + printf( +" -t THREADS hint on the number of threads to use. By default use\n" +" as many threads as CPU cores.\n"); + printf( +" -V X,Y,Z maximum definition along the 3 axis of the 3D\n" +" volumetric majorant field to partition. By default use\n" +" the definition of the clouds data.\n"); + printf( +" -v make the command verbose.\n"); + printf("\n"); + + htrdr_fprint_copyright(cmd, stdout); + htrdr_fprint_license(cmd, stdout); +} + +static res_T +parse_grid_definition(struct htrdr_atmosphere_args* args, const char* str) +{ + unsigned def[3]; + size_t len; + res_T res = RES_OK; + ASSERT(args && str); + + res = cstr_to_list_uint(str, ',', def, &len, 3); + if(res == RES_OK && len != 3) res = RES_BAD_ARG; + if(res != RES_OK) { + fprintf(stderr, "Invalid grid definition `%s'.\n", str); + goto error; + } + + if(!def[0] || !def[1] || !def[2]) { + fprintf(stderr, + "Invalid null grid definition {%u, %u, %u}.\n", SPLIT3(def)); + res = RES_BAD_ARG; + goto error; + } + + args->grid_max_definition[0] = def[0]; + args->grid_max_definition[1] = def[1]; + args->grid_max_definition[2] = def[2]; + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_sun_dir(struct htrdr_atmosphere_args* args, const char* str) +{ + double angles[2]; + size_t len; + res_T res = RES_OK; + ASSERT(args && str); + + res = cstr_to_list_double(str, ',', angles, &len, 2); + if(res == RES_OK && len != 2) res = RES_BAD_ARG; + if(res != RES_OK) { + fprintf(stderr, "Invalid direction `%s'.\n", str); + goto error; + } + + if(angles[0] < 0 || angles[0] >= 360) { + fprintf(stderr, + "Invalid azimuth angle `%g'. Azimuth must be in [0, 360[ degrees.\n", + angles[0]); + res = RES_BAD_ARG; + goto error; + } + + if(angles[1] < 0 || angles[1] > 90) { + fprintf(stderr, + "Invalid elevation angle `%g'. Elevation must be in [0, 90] degrees.\n", + angles[1]); + res = RES_BAD_ARG; + goto error; + } + + args->sun_azimuth = angles[0]; + args->sun_elevation = angles[1]; + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_atmosphere_args_init + (struct htrdr_atmosphere_args* args, + int argc, + char** argv) +{ + int opt; + res_T res = RES_OK; + ASSERT(args && argc && argv); + + *args = HTRDR_ATMOSPHERE_ARGS_DEFAULT; + + while((opt = getopt(argc, argv, "a:C:c:D:dfg:hi:M:m:n:O:o:p:Rrs:T:t:V:v")) != -1) { + switch(opt) { + case 'a': args->filename_gas = optarg; break; + case 'C': + args->sensor_type = HTRDR_SENSOR_CAMERA; + res = htrdr_args_camera_parse(&args->sensor.camera, optarg); + break; + case 'c': args->filename_les = optarg; break; + case 'D': res = parse_sun_dir(args, optarg); break; + case 'd': args->dump_volumetric_acceleration_structure = 1; break; + case 'f': args->force_overwriting = 1; break; + case 'g': args->filename_obj = optarg; break; + case 'h': + print_help(argv[0]); + htrdr_atmosphere_args_release(args); + args->quit = 1; + goto exit; + case 'i': + res = htrdr_args_image_parse(&args->image, optarg); + break; + case 'M': args->filename_mtl = optarg; break; + case 'm': args->filename_mie = optarg; break; + case 'n': args->sky_mtl_name = optarg; break; + case 'O': args->filename_cache = optarg; break; + case 'o': args->filename_output = optarg; break; + case 'p': + args->sensor_type = HTRDR_SENSOR_RECTANGLE; + res = htrdr_args_rectangle_parse(&args->sensor.rectangle, optarg); + break; + case 'r': args->repeat_clouds = 1; break; + case 'R': args->repeat_ground = 1; break; + case 's': + res = htrdr_args_spectral_parse(&args->spectral, optarg); + break; + case 'T': + res = cstr_to_double(optarg, &args->optical_thickness); + if(res == RES_OK && args->optical_thickness < 0) res = RES_BAD_ARG; + break; + case 't': /* Submit an hint on the number of threads to use */ + res = cstr_to_uint(optarg, &args->nthreads); + if(res == RES_OK && !args->nthreads) res = RES_BAD_ARG; + break; + case 'V': res = parse_grid_definition(args, optarg); break; + case 'v': args->verbose = 1; break; + default: res = RES_BAD_ARG; break; + } + if(res != RES_OK) { + if(optarg) { + fprintf(stderr, "%s: invalid option argument '%s' -- '%c'\n", + argv[0], optarg, opt); + } + goto error; + } + } + if(!args->filename_gas) { + fprintf(stderr, + "Missing the path of the gas optical properties file -- option '-a'\n"); + res = RES_BAD_ARG; + goto error; + } + if(args->filename_obj && !args->filename_mtl) { + fprintf(stderr, + "Missing the path of the file listing the ground materials -- option '-M'\n"); + res = RES_BAD_ARG; + goto error; + } + if(args->filename_les && !args->filename_mie) { + fprintf(stderr, + "Missing the path toward the file of the Mie's data -- option '-m'\n"); + res = RES_BAD_ARG; + goto error; + } + + /* Setup default ref temperature if necessary */ + if(args->spectral.ref_temperature <= 0) { + switch(args->spectral.spectral_type) { + case HTRDR_SPECTRAL_LW: + args->spectral.ref_temperature = HTRDR_DEFAULT_LW_REF_TEMPERATURE; + break; + case HTRDR_SPECTRAL_SW: + args->spectral.ref_temperature = HTRDR_SUN_TEMPERATURE; + break; + case HTRDR_SPECTRAL_SW_CIE_XYZ: + args->spectral.ref_temperature = -1; /* Unused */ + break; + default: FATAL("Unreachable code.\n"); break; + } + } + +exit: + return res; +error: + htrdr_atmosphere_args_release(args); + goto exit; +} + +void +htrdr_atmosphere_args_release(struct htrdr_atmosphere_args* args) +{ + ASSERT(args); + *args = HTRDR_ATMOSPHERE_ARGS_DEFAULT; +} + diff --git a/src/atmosphere/htrdr_atmosphere_args.h.in b/src/atmosphere/htrdr_atmosphere_args.h.in @@ -0,0 +1,113 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019, Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef HTRDR_ATMOSPHERE_ARGS_H +#define HTRDR_ATMOSPHERE_ARGS_H + +#include "core/htrdr_args.h" +#include "core/htrdr_sensor.h" + +#include <rsys/rsys.h> + +#include <limits.h> /* UINT_MAX support */ + +struct htrdr_atmosphere_args { + const char* filename_gas; /* Path of the gas file */ + const char* filename_les; /* Path of the HTCP file */ + const char* filename_mie; /* Path of the Mie properties */ + const char* filename_obj; /* Path of the ground geometry */ + const char* filename_mtl; /* Path of the ground materials */ + + const char* filename_cache; /* Path of the file to store/restore cached data */ + const char* filename_output; /* Name of the output file */ + + const char* sky_mtl_name; /* Name of the sky material */ + + union { + struct htrdr_args_camera camera; /* Pinhole Camera */ + struct htrdr_args_rectangle rectangle; /* Flux map */ + } sensor; + enum htrdr_sensor_type sensor_type; + + struct htrdr_args_image image; /* Output Image */ + struct htrdr_args_spectral spectral; /* Spectral domain */ + + double sun_azimuth; /* In degrees */ + double sun_elevation; /* In degrees */ + + /* Parameters of the volumetric acceleration data structures */ + double optical_thickness; /* Threshold used during octree building */ + unsigned grid_max_definition[3]; /* Maximum definition of the grid */ + + int repeat_clouds; /* Make the clouds infinite in X and Y */ + int repeat_ground; /* Make the ground infinite in X and Y */ + + /* Miscellaneous parameters */ + unsigned nthreads; /* Hint on the number of threads to use */ + int force_overwriting; + int dump_volumetric_acceleration_structure; + int verbose; /* Verbosity level */ + int quit; /* Stop the command */ +}; + +#define HTRDR_ATMOSPHERE_ARGS_DEFAULT__ { \ + NULL, /* Gas filename */ \ + NULL, /* LES filename */ \ + NULL, /* Mie filename */ \ + NULL, /* Obj filename */ \ + NULL, /* Mtl filename */ \ + \ + NULL, /* Cache filename */ \ + NULL, /* Output filename */ \ + \ + @HTRDR_ATMOSPHERE_ARGS_DEFAULT_SKY_MTL_NAME@, /* Sky mtl name */ \ + \ + {HTRDR_ARGS_CAMERA_DEFAULT__}, /* Sensor */ \ + HTRDR_SENSOR_CAMERA, /* Sensor type */ \ + \ + HTRDR_ARGS_IMAGE_DEFAULT__, /* Image */ \ + HTRDR_ARGS_SPECTRAL_DEFAULT__, /* Spectral */ \ + \ + 0, /* Sun azimuth */ \ + 90, /* Sun elevation */ \ + \ + @HTRDR_ATMOSPHERE_ARGS_DEFAULT_OPTICAL_THICKNESS_THRESHOLD@, \ + {UINT_MAX, UINT_MAX, UINT_MAX}, /* Maximum definition of the grid */ \ + \ + 0, /* Repeat clouds */ \ + 0, /* Repeat ground */ \ + \ + UINT_MAX, /* #threads */ \ + 0, /* Force overwriting */ \ + 0, /* dump volumetric acceleration structure */ \ + 0, /* Verbose flag */ \ + 0 /* Stop the command */ \ +} +static const struct htrdr_atmosphere_args HTRDR_ATMOSPHERE_ARGS_DEFAULT = + HTRDR_ATMOSPHERE_ARGS_DEFAULT__; + +extern LOCAL_SYM res_T +htrdr_atmosphere_args_init + (struct htrdr_atmosphere_args* args, + int argc, + char** argv); + +extern LOCAL_SYM void +htrdr_atmosphere_args_release + (struct htrdr_atmosphere_args* args); + +#endif /* HTRDR_ATMOSPHERE_ARGS_H */ diff --git a/src/atmosphere/htrdr_atmosphere_c.h b/src/atmosphere/htrdr_atmosphere_c.h @@ -0,0 +1,164 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019, Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef HTRDR_ATMOSPHERE_C_H +#define HTRDR_ATMOSPHERE_C_H + +#include "core/htrdr_accum.h" +#include "core/htrdr_buffer.h" +#include "core/htrdr_sensor.h" +#include "core/htrdr_spectral.h" + +#include <rsys/ref_count.h> +#include <rsys/rsys.h> +#include <rsys/str.h> + +/* Define the radiance component */ +enum atmosphere_radiance_cpnt_flag { + ATMOSPHERE_RADIANCE_DIRECT = BIT(0), + ATMOSPHERE_RADIANCE_DIFFUSE = BIT(1), + ATMOSPHERE_RADIANCE_ALL = + ATMOSPHERE_RADIANCE_DIRECT + | ATMOSPHERE_RADIANCE_DIFFUSE +}; + +struct atmosphere_pixel_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 */ + struct htrdr_accum time; /* In microseconds */ +}; +#define ATMOSPHERE_PIXEL_XWAVE_NULL__ { \ + HTRDR_ESTIMATE_NULL__, /* Radiance */ \ + HTRDR_ESTIMATE_NULL__, /* Radiance temperature */ \ + HTRDR_ACCUM_NULL /* Time */ \ +} +static const struct atmosphere_pixel_xwave ATMOSPHERE_PIXEL_XWAVE_NULL = + ATMOSPHERE_PIXEL_XWAVE_NULL__; + +struct atmosphere_pixel_flux { + struct htrdr_accum flux; + struct htrdr_accum time; +}; +#define ATMOSPHERE_PIXEL_FLUX_NULL__ { \ + HTRDR_ACCUM_NULL, \ + HTRDR_ACCUM_NULL \ +} +static const struct atmosphere_pixel_flux ATMOSPHERE_PIXEL_FLUX_NULL = + ATMOSPHERE_PIXEL_FLUX_NULL__; + +struct atmosphere_pixel_image { + struct htrdr_estimate X; /* In W/m^2/sr */ + struct htrdr_estimate Y; /* In W/m^2/sr */ + struct htrdr_estimate Z; /* In W/m^2/sr */ + struct htrdr_accum time; /* In microseconds */ +}; +#define ATMOSPHERE_PIXEL_IMAGE_NULL__ { \ + HTRDR_ESTIMATE_NULL__, /* X */ \ + HTRDR_ESTIMATE_NULL__, /* Y */ \ + HTRDR_ESTIMATE_NULL__, /* Z */ \ + HTRDR_ACCUM_NULL /* Time */ \ +} +static const struct atmosphere_pixel_image ATMOSPHERE_PIXEL_IMAGE_NULL = + ATMOSPHERE_PIXEL_IMAGE_NULL__; + +/* Forward declarations */ +struct htsky; +struct htrdr; +struct htrdr_atmosphere_args; +struct htrdr_buffer; +struct htrdr_cie_xyz; +struct htrdr_materials; +struct htrdr_ran_wlen; + +struct htrdr_atmosphere { + struct htrdr_atmosphere_ground* ground; + struct htrdr_atmosphere_sun* sun; + struct htrdr_materials* mats; + struct htrdr_cie_xyz* cie; + struct htrdr_ran_wlen* ran_wlen; + + struct htrdr_sensor sensor; + + struct htrdr_buffer_layout buf_layout; + struct htrdr_buffer* buf; /* NULL on non master processes */ + + struct htsky* sky; + const char* sky_mtl_name; + enum htrdr_spectral_type spectral_type; + double wlen_range_m[2]; /* Integration range in *meters* */ + double ref_temperature; /* Reference temperature in Kelvin */ + + size_t spp; /* #samples per pixel */ + size_t width; /* Image width */ + size_t height; /* Image height */ + + FILE* output; + struct str output_name; + + unsigned grid_max_definition[3]; /* Max definition of the acceleration grids */ + unsigned nthreads; /* #threads of the process */ + int dump_volumetric_acceleration_structure; /* Dump octrees */ + int verbose; /* Verbosity level */ + + ref_T ref; + struct htrdr* htrdr; +}; + +extern LOCAL_SYM void +atmosphere_get_pixel_format + (const struct htrdr_atmosphere* cmd, + struct atmosphere_pixel_format* fmt); + +extern LOCAL_SYM res_T +atmosphere_draw_map + (struct htrdr_atmosphere* cmd); + +/* Return the shortwave radiance in W/m^2/sr/m */ +extern LOCAL_SYM double +atmosphere_compute_radiance_sw + (struct htrdr_atmosphere* cmd, + const size_t ithread, + struct ssp_rng* rng, + const int cpnt_mask, /* Combination of enum atmosphere_radiance_cpnt_flag */ + const double pos_in[3], + const double dir_in[3], + const double wlen, /* In nanometer */ + const size_t iband, + const size_t iquad); + +/* Return the longwave radiance in W/m^2/sr/m */ +extern LOCAL_SYM double +atmosphere_compute_radiance_lw + (struct htrdr_atmosphere* cmd, + const size_t ithread, + struct ssp_rng* rng, + const double pos_in[3], + const double dir_in[3], + const double wlen, /* In nanometer */ + const size_t iband, + const size_t iquad); + +#endif /* HTRDR_ATMOSPHERE_C_H */ + diff --git a/src/atmosphere/htrdr_atmosphere_compute_radiance_lw.c b/src/atmosphere/htrdr_atmosphere_compute_radiance_lw.c @@ -0,0 +1,274 @@ +/*Spectralt (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 "atmosphere/htrdr_atmosphere_c.h" +#include "atmosphere/htrdr_atmosphere_ground.h" + +#include "core/htrdr.h" +#include "core/htrdr_interface.h" + +#include <high_tune/htsky.h> + +#include <star/s3d.h> +#include <star/ssf.h> +#include <star/ssp.h> +#include <star/svx.h> + +#include <rsys/double2.h> +#include <rsys/double3.h> + +enum event { + EVENT_ABSORPTION, + EVENT_SCATTERING, + EVENT_NONE +}; + +struct filter_context { + struct ssp_rng* rng; + const struct htsky* htsky; + size_t iband; /* Index of the spectral band */ + size_t iquad; /* Index of the quadrature point into the band */ + + double Ts; /* Sampled optical thickness */ + double traversal_dst; /* Distance traversed along the ray */ + + enum event event_type; +}; +static const struct filter_context FILTER_CONTEXT_NULL = { + NULL, NULL, 0, 0, 0.0, 0.0, EVENT_NONE +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static int +hit_filter + (const struct svx_hit* hit, + const double org[3], + const double dir[3], + const double range[2], + void* context) +{ + struct filter_context* ctx = context; + double kext_max; + int pursue_traversal = 1; + ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range); + (void)range; + + kext_max = htsky_fetch_svx_voxel_property(ctx->htsky, HTSKY_Kext, + HTSKY_SVX_MAX, HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, &hit->voxel); + + ctx->traversal_dst = hit->distance[0]; + + for(;;) { + double vox_dst = hit->distance[1] - ctx->traversal_dst; + const double T = vox_dst * kext_max; + + /* A collision occurs behind `vox_dst' */ + if(ctx->Ts > T) { + ctx->Ts -= T; + ctx->traversal_dst = hit->distance[1]; + pursue_traversal = 1; + break; + + /* A real/null collision occurs before `vox_dst' */ + } else { + const double collision_dst = ctx->Ts / kext_max; + double pos[3]; + double ks, ka; + double r; + double proba_abs; + double proba_sca; + + /* Compute the traversed distance up to the challenged collision */ + ctx->traversal_dst += collision_dst; + ASSERT(ctx->traversal_dst >= hit->distance[0]); + ASSERT(ctx->traversal_dst <= hit->distance[1]); + + /* Compute the world space position where a collision may occur */ + pos[0] = org[0] + ctx->traversal_dst * dir[0]; + pos[1] = org[1] + ctx->traversal_dst * dir[1]; + pos[2] = org[2] + ctx->traversal_dst * dir[2]; + + ka = htsky_fetch_raw_property(ctx->htsky, HTSKY_Ka, HTSKY_CPNT_MASK_ALL, + ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX); + ks = htsky_fetch_raw_property(ctx->htsky, HTSKY_Ks, HTSKY_CPNT_MASK_ALL, + ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX); + + r = ssp_rng_canonical(ctx->rng); + proba_abs = ka / kext_max; + proba_sca = ks / kext_max; + if(r < proba_abs) { /* Absorption event */ + pursue_traversal = 0; + ctx->event_type = EVENT_ABSORPTION; + break; + } else if(r < proba_abs + proba_sca) { /* Scattering event */ + pursue_traversal = 0; + ctx->event_type = EVENT_SCATTERING; + break; + } else { /* Null collision */ + ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */ + } + } + } + return pursue_traversal; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +double +atmosphere_compute_radiance_lw + (struct htrdr_atmosphere* cmd, + const size_t ithread, + struct ssp_rng* rng, + const double pos_in[3], + const double dir_in[3], + const double wlen, /* In nanometer */ + const size_t iband, + const size_t iquad) +{ + struct s3d_hit s3d_hit = S3D_HIT_NULL; + struct s3d_hit s3d_hit_prev = S3D_HIT_NULL; + struct svx_hit svx_hit = SVX_HIT_NULL; + struct ssf_phase* phase_hg = NULL; + + double wo[3]; + double pos[3]; + double dir[3]; + double range[2]; + double pos_next[3]; + double dir_next[3]; + double temperature; + double wlen_m = wlen * 1.e-9; + double g; + double w = 0; /* Weight */ + + ASSERT(cmd && rng && pos_in && dir_in); + ASSERT(ithread < htrdr_get_threads_count(cmd->htrdr)); + + /* Setup the phase function for this spectral band & quadrature point */ + CHK(RES_OK == ssf_phase_create + (htrdr_get_thread_allocator(cmd->htrdr, ithread), + &ssf_phase_hg, + &phase_hg)); + g = htsky_fetch_per_wavelength_particle_phase_function_asymmetry_parameter + (cmd->sky, wlen); + SSF(phase_hg_setup(phase_hg, g)); + + /* Initialise the random walk */ + d3_set(pos, pos_in); + d3_set(dir, dir_in); + d2(range, 0, INF); + + for(;;) { + struct filter_context ctx = FILTER_CONTEXT_NULL; + + /* Sample an optical thickness */ + ctx.Ts = ssp_ran_exp(rng, 1); + + /* Setup the remaining fields of the hit filter context */ + ctx.rng = rng; + ctx.htsky = cmd->sky; + ctx.iband = iband; + ctx.iquad = iquad; + + /* Found the first intersection with the surface geometry */ + HTRDR(atmosphere_ground_trace_ray + (cmd->ground, pos, dir, range, &s3d_hit_prev, &s3d_hit)); + + /* Fit the ray range to the surface distance along the ray */ + range[0] = 0; + range[1] = s3d_hit.distance; + + /* Trace a ray into the participating media */ + HTSKY(trace_ray(cmd->sky, pos, dir, range, NULL, + hit_filter, &ctx, iband, iquad, &svx_hit)); + + /* No scattering and no surface reflection. + * Congratulation !! You are in space. */ + if(S3D_HIT_NONE(&s3d_hit) && SVX_HIT_NONE(&svx_hit)) { + w = 0; + break; + } + + /* Compute the next position */ + pos_next[0] = pos[0] + dir[0]*ctx.traversal_dst; + pos_next[1] = pos[1] + dir[1]*ctx.traversal_dst; + pos_next[2] = pos[2] + dir[2]*ctx.traversal_dst; + + /* Absorption event. Stop the realisation */ + if(ctx.event_type == EVENT_ABSORPTION) { + ASSERT(!SVX_HIT_NONE(&svx_hit)); + temperature = htsky_fetch_temperature(cmd->sky, pos_next); + /* weight is planck integrated over the spectral sub-interval */ + w = htrdr_planck_monochromatic(wlen_m, temperature); + break; + } + + /* Negate the incoming dir to match the convention of the SSF library */ + d3_minus(wo, dir); + + /* Scattering in the volume */ + if(ctx.event_type == EVENT_SCATTERING) { + ASSERT(!SVX_HIT_NONE(&svx_hit)); + ssf_phase_sample(phase_hg, rng, wo, dir_next, NULL); + s3d_hit_prev = S3D_HIT_NULL; + + /* Scattering at a surface */ + } else { + struct htrdr_interface interf = HTRDR_INTERFACE_NULL; + const struct htrdr_mtl* mtl = NULL; + struct ssf_bsdf* bsdf = NULL; + double bounce_reflectivity = 0; + double N[3]; + int type; + ASSERT(ctx.event_type == EVENT_NONE); + ASSERT(!S3D_HIT_NONE(&s3d_hit)); + + /* Fetch the hit interface materal and build its BSDF */ + htrdr_atmosphere_ground_get_interface(cmd->ground, &s3d_hit, &interf); + mtl = htrdr_interface_fetch_hit_mtl(&interf, dir, &s3d_hit); + HTRDR(mtl_create_bsdf(cmd->htrdr, mtl, ithread, wlen, rng, &bsdf)); + + d3_normalize(N, d3_set_f3(N, s3d_hit.normal)); + if(d3_dot(N, wo) < 0) d3_minus(N, N); + + bounce_reflectivity = ssf_bsdf_sample + (bsdf, rng, wo, N, dir_next, &type, NULL); + if(!(type & SSF_REFLECTION)) { /* Handle only reflections */ + bounce_reflectivity = 0; + } + + /* Release the BSDF */ + SSF(bsdf_ref_put(bsdf)); + + if(ssp_rng_canonical(rng) >= bounce_reflectivity) { /* Absorbed at boundary */ + temperature = mtl->temperature; /* Fetch mtl temperature */ + /* Weight is planck integrated over the spectral sub-interval */ + w = temperature>0 ? htrdr_planck_monochromatic(wlen_m, temperature) : 0; + break; + } + s3d_hit_prev = s3d_hit; + } + d3_set(pos, pos_next); + d3_set(dir, dir_next); + } + SSF(phase_ref_put(phase_hg)); + return w; +} + diff --git a/src/atmosphere/htrdr_atmosphere_compute_radiance_sw.c b/src/atmosphere/htrdr_atmosphere_compute_radiance_sw.c @@ -0,0 +1,506 @@ +/* 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 "atmosphere/htrdr_atmosphere_c.h" +#include "atmosphere/htrdr_atmosphere_ground.h" +#include "atmosphere/htrdr_atmosphere_sun.h" + +#include "core/htrdr_interface.h" + +#include <high_tune/htsky.h> + +#include <star/s3d.h> +#include <star/ssf.h> +#include <star/ssp.h> +#include <star/svx.h> + +#include <rsys/double2.h> +#include <rsys/float2.h> +#include <rsys/float3.h> + +struct scattering_context { + struct ssp_rng* rng; + const struct htsky* sky; + size_t iband; /* Index of the spectral band */ + size_t iquad; /* Index of the quadrature point into the band */ + + double Ts; /* Sampled optical thickness */ + double traversal_dst; /* Distance traversed along the ray */ +}; +static const struct scattering_context SCATTERING_CONTEXT_NULL = { + NULL, NULL, 0, 0, 0, 0 +}; + +struct transmissivity_context { + struct ssp_rng* rng; + const struct htsky* sky; + size_t iband; /* Index of the spectral */ + size_t iquad; /* Index of the quadrature point into the band */ + + double Ts; /* Sampled optical thickness */ + double Tmin; /* Minimal optical thickness */ + double traversal_dst; /* Distance traversed along the ray */ + + enum htsky_property prop; +}; +static const struct transmissivity_context TRANSMISSION_CONTEXT_NULL = { + NULL, NULL, 0, 0, 0, 0, 0, 0 +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static int +scattering_hit_filter + (const struct svx_hit* hit, + const double org[3], + const double dir[3], + const double range[2], + void* context) +{ + struct scattering_context* ctx = context; + double ks_max; + int pursue_traversal = 1; + ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range); + (void)range; + + ks_max = htsky_fetch_svx_voxel_property(ctx->sky, HTSKY_Ks, + HTSKY_SVX_MAX, HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, &hit->voxel); + + ctx->traversal_dst = hit->distance[0]; + + /* Iterate until a collision occurs into the voxel or until the ray + * does not collide the voxel */ + for(;;) { + /* Compute tau for the current leaf */ + const double vox_dst = hit->distance[1] - ctx->traversal_dst; + const double T = vox_dst * ks_max; + + /* A collision occurs behind `vox_dst' */ + if(ctx->Ts > T) { + ctx->Ts -= T; + ctx->traversal_dst = hit->distance[1]; + pursue_traversal = 1; + break; + + /* A real/null collision occurs before `vox_dst' */ + } else { + double pos[3]; + double proba; + double ks; + const double collision_dst = ctx->Ts / ks_max; + + /* Compute the traversed distance up to the challenged collision */ + ctx->traversal_dst += collision_dst; + ASSERT(ctx->traversal_dst >= hit->distance[0]); + ASSERT(ctx->traversal_dst <= hit->distance[1]); + + /* Stop the ray whenever the traversal distance without any scattering + * event is too high. It means the maximum scattering coefficient has a + * very small value, and the returned radiance is null. This can only + * happen when the voxel has a [quasi] infinite length in the propagation + * direction. */ + if(ctx->traversal_dst > 1e9) break; + + /* Compute the world space position where a collision may occur */ + pos[0] = org[0] + ctx->traversal_dst * dir[0]; + pos[1] = org[1] + ctx->traversal_dst * dir[1]; + pos[2] = org[2] + ctx->traversal_dst * dir[2]; + + ks = htsky_fetch_raw_property(ctx->sky, HTSKY_Ks, + HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX); + + /* Handle the case that ks_max is not *really* the max */ + proba = ks / ks_max; + + if(ssp_rng_canonical(ctx->rng) < proba) {/* Collide <=> real scattering */ + pursue_traversal = 0; + break; + } else { /* Null collision */ + ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */ + } + } + } + return pursue_traversal; +} + +static int +transmissivity_hit_filter + (const struct svx_hit* hit, + const double org[3], + const double dir[3], + const double range[2], + void* context) +{ + struct transmissivity_context* ctx = context; + int comp_mask = HTSKY_CPNT_MASK_ALL; + double k_max; + double k_min; + int pursue_traversal = 1; + ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range); + (void)range; + + k_min = htsky_fetch_svx_voxel_property(ctx->sky, ctx->prop, + HTSKY_SVX_MIN, comp_mask, ctx->iband, ctx->iquad, &hit->voxel); + k_max = htsky_fetch_svx_voxel_property(ctx->sky, ctx->prop, + HTSKY_SVX_MAX, comp_mask, ctx->iband, ctx->iquad, &hit->voxel); + ASSERT(k_min <= k_max); + + ctx->Tmin += (hit->distance[1] - hit->distance[0]) * k_min; + ctx->traversal_dst = hit->distance[0]; + + /* Iterate until a collision occurs into the voxel or until the ray + * does not collide the voxel */ + for(;;) { + const double vox_dst = hit->distance[1] - ctx->traversal_dst; + const double Tdif = vox_dst * (k_max-k_min); + + /* A collision occurs behind `vox_dst' */ + if(ctx->Ts > Tdif) { + ctx->Ts -= Tdif; + ctx->traversal_dst = hit->distance[1]; + pursue_traversal = 1; + break; + + /* A real/null collision occurs before `vox_dst' */ + } else { + double x[3]; + double k; + double proba; + double collision_dst = ctx->Ts / (k_max - k_min); + + /* Compute the traversed distance up to the challenged collision */ + ctx->traversal_dst += collision_dst; + ASSERT(ctx->traversal_dst >= hit->distance[0]); + ASSERT(ctx->traversal_dst <= hit->distance[1]); + + /* Compute the world space position where a collision may occur */ + x[0] = org[0] + ctx->traversal_dst * dir[0]; + x[1] = org[1] + ctx->traversal_dst * dir[1]; + x[2] = org[2] + ctx->traversal_dst * dir[2]; + + k = htsky_fetch_raw_property(ctx->sky, ctx->prop, + comp_mask, ctx->iband, ctx->iquad, x, k_min, k_max); + ASSERT(k >= k_min && k <= k_max); + + proba = (k - k_min) / (k_max - k_min); + + if(ssp_rng_canonical(ctx->rng) < proba) { /* Collide */ + pursue_traversal = 0; + break; + } else { /* Null collision */ + ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */ + } + } + } + return pursue_traversal; +} + +static double +transmissivity + (struct htrdr_atmosphere* cmd, + struct ssp_rng* rng, + const enum htsky_property prop, + const size_t iband, + const size_t iquad, + const double pos[3], + const double dir[3], + const double range[2]) +{ + struct svx_hit svx_hit; + struct transmissivity_context transmissivity_ctx = TRANSMISSION_CONTEXT_NULL; + + ASSERT(cmd && rng && pos && dir && range); + + transmissivity_ctx.rng = rng; + transmissivity_ctx.sky = cmd->sky; + transmissivity_ctx.iband = iband; + transmissivity_ctx.iquad = iquad; + transmissivity_ctx.Ts = ssp_ran_exp(rng, 1); /* Sample an optical thickness */ + transmissivity_ctx.prop = prop; + + /* Compute the transmissivity */ + HTSKY(trace_ray(cmd->sky, pos, dir, range, NULL, + transmissivity_hit_filter, &transmissivity_ctx, iband, iquad, &svx_hit)); + + if(SVX_HIT_NONE(&svx_hit)) { + return transmissivity_ctx.Tmin ? exp(-transmissivity_ctx.Tmin) : 1.0; + } else { + return 0; + } +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +double +atmosphere_compute_radiance_sw + (struct htrdr_atmosphere* cmd, + const size_t ithread, + struct ssp_rng* rng, + const int cpnt_mask, /* Combination of enum htrdr_radiance_cpnt_flag */ + const double pos_in[3], + const double dir_in[3], + const double wlen, /* In nanometer */ + const size_t iband, + const size_t iquad) +{ + struct s3d_hit s3d_hit = S3D_HIT_NULL; + struct s3d_hit s3d_hit_tmp = S3D_HIT_NULL; + struct s3d_hit s3d_hit_prev = S3D_HIT_NULL; + struct svx_hit svx_hit = SVX_HIT_NULL; + struct ssf_phase* phase_hg = NULL; + struct ssf_phase* phase_rayleigh = NULL; + + double pos[3]; + double dir[3]; + double range[2]; + double pos_next[3]; + double dir_next[3]; + double band_bounds[2]; /* In nanometers */ + + double R; + double r; /* Random number */ + double wo[3]; /* -dir */ + double pdf; + double Tr; /* Overall transmissivity */ + double Tr_abs; /* Absorption transmissivity */ + double L_sun; /* Sun radiance in W.m^-2.sr^-1 */ + double sun_dir[3]; + double ksi = 1; /* Throughput */ + double w = 0; /* MC weight */ + double g = 0; /* Asymmetry parameter of the HG phase function */ + + ASSERT(cmd && rng && pos_in && dir_in); + ASSERT(cmd->spectral_type == HTRDR_SPECTRAL_SW + || cmd->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ); + + /* Create the Henyey-Greenstein phase function */ + CHK(RES_OK == ssf_phase_create + (htrdr_get_thread_allocator(cmd->htrdr, ithread), + &ssf_phase_hg, + &phase_hg)); + + /* Create the Rayleigh phase function */ + CHK(RES_OK == ssf_phase_create + (htrdr_get_thread_allocator(cmd->htrdr, ithread), + &ssf_phase_rayleigh, + &phase_rayleigh)); + + /* Setup the phase function for this wavelength */ + g = htsky_fetch_per_wavelength_particle_phase_function_asymmetry_parameter + (cmd->sky, wlen); + SSF(phase_hg_setup(phase_hg, g)); + + /* Fetch sun properties. Note that the sun spectral data are defined by bands + * that, actually are the same of the SW spectral bands defined in the + * default "ecrad_opt_prot.txt" file provided by the HTGOP project. */ + htsky_get_spectral_band_bounds(cmd->sky, iband, band_bounds); + ASSERT(band_bounds[0] <= wlen && wlen <= band_bounds[1]); + L_sun = htrdr_atmosphere_sun_get_radiance(cmd->sun, wlen); + d3_set(pos, pos_in); + d3_set(dir, dir_in); + + if((cpnt_mask & ATMOSPHERE_RADIANCE_DIRECT) /* Handle direct contribution */ + && htrdr_atmosphere_sun_is_dir_in_solar_cone(cmd->sun, dir)) { + /* Check that the ray is not occluded along the submitted range */ + d2(range, 0, FLT_MAX); + HTRDR(atmosphere_ground_trace_ray + (cmd->ground, pos, dir, range, NULL, &s3d_hit_tmp)); + if(!S3D_HIT_NONE(&s3d_hit_tmp)) { + Tr = 0; + } else { + Tr = transmissivity + (cmd, rng, HTSKY_Kext, iband, iquad , pos, dir, range); + w = L_sun * Tr; + } + } + + if((cpnt_mask & ATMOSPHERE_RADIANCE_DIFFUSE) == 0) + goto exit; /* Discard diffuse contribution */ + + /* Radiative random walk */ + for(;;) { + struct scattering_context scattering_ctx = SCATTERING_CONTEXT_NULL; + struct ssf_bsdf* bsdf = NULL; + struct ssf_phase* phase; + double N[3]; + double bounce_reflectivity = 1; + double sun_dir_pdf; + int surface_scattering = 0; /* Define if hit a surface */ + int bsdf_type = 0; + + /* Find the first intersection with a surface */ + d2(range, 0, DBL_MAX); + HTRDR(atmosphere_ground_trace_ray + (cmd->ground, pos, dir, range, &s3d_hit_prev, &s3d_hit)); + + /* Sample an optical thickness */ + scattering_ctx.Ts = ssp_ran_exp(rng, 1); + + /* Setup the remaining scattering context fields */ + scattering_ctx.rng = rng; + scattering_ctx.sky = cmd->sky; + scattering_ctx.iband = iband; + scattering_ctx.iquad = iquad; + + /* Define if a scattering event occurs */ + d2(range, 0, s3d_hit.distance); + HTSKY(trace_ray(cmd->sky, pos, dir, range, NULL, + scattering_hit_filter, &scattering_ctx, iband, iquad, &svx_hit)); + + /* No scattering and no surface reflection. Stop the radiative random walk */ + if(S3D_HIT_NONE(&s3d_hit) && SVX_HIT_NONE(&svx_hit)) { + break; + } + ASSERT(SVX_HIT_NONE(&svx_hit) + || ( svx_hit.distance[0] <= scattering_ctx.traversal_dst + && svx_hit.distance[1] >= scattering_ctx.traversal_dst)); + + /* Negate the incoming dir to match the convention of the SSF library */ + d3_minus(wo, dir); + + /* Define if the scattering occurs at a surface */ + surface_scattering = SVX_HIT_NONE(&svx_hit); + + /* Compute the new position */ + pos_next[0] = pos[0] + dir[0]*scattering_ctx.traversal_dst; + pos_next[1] = pos[1] + dir[1]*scattering_ctx.traversal_dst; + pos_next[2] = pos[2] + dir[2]*scattering_ctx.traversal_dst; + + /* Define the previous hit surface used to avoid self hit */ + s3d_hit_prev = surface_scattering ? s3d_hit : S3D_HIT_NULL; + + /* Define the absorption transmissivity from the current position to the + * next position */ + d2(range, 0, scattering_ctx.traversal_dst); + Tr_abs = transmissivity + (cmd, rng, HTSKY_Ka, iband, iquad, pos, dir, range); + if(Tr_abs <= 0) break; + + /* Sample the scattering direction */ + if(surface_scattering) { /* Scattering at a surface */ + struct htrdr_interface interf = HTRDR_INTERFACE_NULL; + const struct htrdr_mtl* mtl = NULL; + + /* Fetch the hit interface materal and build its BSDF */ + htrdr_atmosphere_ground_get_interface(cmd->ground, &s3d_hit, &interf); + mtl = htrdr_interface_fetch_hit_mtl(&interf, dir, &s3d_hit); + HTRDR(mtl_create_bsdf(cmd->htrdr, mtl, ithread, wlen, rng, &bsdf)); + + /* Revert the normal if necessary to match the SSF convention */ + d3_normalize(N, d3_set_f3(N, s3d_hit.normal)); + if(d3_dot(N, wo) < 0) d3_minus(N, N); + + /* Sample scattering direction */ + bounce_reflectivity = ssf_bsdf_sample + (bsdf, rng, wo, N, dir_next, &bsdf_type, &pdf); + if(!(bsdf_type & SSF_REFLECTION)) { /* Handle only reflections */ + bounce_reflectivity = 0; + } + + } else { /* Scattering in a volume */ + double ks_particle; /* Scattering coefficient of the particles */ + double ks_gas; /* Scattering coefficient of the gaz */ + double ks; /* Overall scattering coefficient */ + + ks_gas = htsky_fetch_raw_property(cmd->sky, HTSKY_Ks, + HTSKY_CPNT_FLAG_GAS, iband, iquad, pos_next, -DBL_MAX, DBL_MAX); + ks_particle = htsky_fetch_raw_property(cmd->sky, HTSKY_Ks, + HTSKY_CPNT_FLAG_PARTICLES, iband, iquad, pos_next, -DBL_MAX, DBL_MAX); + ks = ks_particle + ks_gas; + + r = ssp_rng_canonical(rng); + if(r < ks_gas / ks) { /* Gas scattering */ + phase = phase_rayleigh; + } else { /* Cloud scattering */ + phase = phase_hg; + } + + /* Sample scattering direction */ + ssf_phase_sample(phase, rng, wo, dir_next, NULL); + ssf_phase_ref_get(phase); + } + + /* Sample the direction of the direct contribution */ + if(surface_scattering && (bsdf_type & SSF_SPECULAR)) { + if(!htrdr_atmosphere_sun_is_dir_in_solar_cone(cmd->sun, dir_next)) { + R = 0; /* No direct lightning */ + } else { + sun_dir[0] = dir_next[0]; + sun_dir[1] = dir_next[1]; + sun_dir[2] = dir_next[2]; + R = d3_dot(N, sun_dir)<0/* Below the ground*/ ? 0 : bounce_reflectivity; + } + sun_dir_pdf = 1.0; + } else { + /* Sample a sun direction */ + sun_dir_pdf = htrdr_atmosphere_sun_sample_direction + (cmd->sun, rng, sun_dir); + if(surface_scattering) { + R = d3_dot(N, sun_dir) < 0/* Below the ground */ + ? 0 : ssf_bsdf_eval(bsdf, wo, N, sun_dir) * d3_dot(N, sun_dir); + } else { + R = ssf_phase_eval(phase, wo, sun_dir); + } + } + + /* The direct contribution to the scattering point is not null so we need + * to compute the transmissivity from sun to scatt pt */ + if(R <= 0) { + Tr = 0; + } else { + /* Check that the sun is visible from the new position */ + d2(range, 0, FLT_MAX); + HTRDR(atmosphere_ground_trace_ray + (cmd->ground, pos_next, sun_dir, range, &s3d_hit_prev, &s3d_hit_tmp)); + + /* Compute the sun transmissivity */ + if(!S3D_HIT_NONE(&s3d_hit_tmp)) { + Tr = 0; + } else { + Tr = transmissivity + (cmd, rng, HTSKY_Kext, iband, iquad, pos_next, sun_dir, range); + } + } + + /* Release the scattering function */ + if(surface_scattering) { + SSF(bsdf_ref_put(bsdf)); + } else { + SSF(phase_ref_put(phase)); + } + + /* Update the MC weight */ + ksi *= Tr_abs; + w += ksi * L_sun * Tr * R / sun_dir_pdf; + + /* Russian roulette wrt surface scattering */ + if(surface_scattering && ssp_rng_canonical(rng) >= bounce_reflectivity) + break; + + /* Setup the next random walk state */ + d3_set(pos, pos_next); + d3_set(dir, dir_next); + } + +exit: + SSF(phase_ref_put(phase_hg)); + SSF(phase_ref_put(phase_rayleigh)); + return w; +} + diff --git a/src/atmosphere/htrdr_atmosphere_draw_map.c b/src/atmosphere/htrdr_atmosphere_draw_map.c @@ -0,0 +1,632 @@ +/* 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 "atmosphere/htrdr_atmosphere_c.h" +#include "atmosphere/htrdr_atmosphere_ground.h" +#include "atmosphere/htrdr_atmosphere_sun.h" + +#include "core/htrdr.h" +#include "core/htrdr_buffer.h" +#include "core/htrdr_camera.h" +#include "core/htrdr_cie_xyz.h" +#include "core/htrdr_draw_map.h" +#include "core/htrdr_interface.h" +#include "core/htrdr_log.h" +#include "core/htrdr_ran_wlen.h" +#include "core/htrdr_rectangle.h" + +#include <high_tune/htsky.h> + +#include <star/s3d.h> +#include <star/ssp.h> + +#include <rsys/clock_time.h> +#include <rsys/str.h> + +#include <string.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static res_T +sample_rectangle_ray + (struct htrdr_atmosphere* cmd, + struct htrdr_rectangle* rect, + const size_t ipix[2], + const double pix_sz[2], + struct ssp_rng* rng, + double ray_org[3], + double ray_dir[3]) +{ + struct s3d_hit hit = S3D_HIT_NULL; + double pix_samp[2]; + const double up_dir[3] = {0,0,1}; + const double range[2] = {0, DBL_MAX}; + double normal[3]; + ASSERT(cmd && rect && ipix && pix_sz && rng && ray_org && ray_dir); + + /* Sample a position into the pixel, in the normalized image plane */ + pix_samp[0] = ((double)ipix[0] + ssp_rng_canonical(rng)) * pix_sz[0]; + pix_samp[1] = ((double)ipix[1] + ssp_rng_canonical(rng)) * pix_sz[1]; + + /* Retrieve the world space position of pix_samp */ + htrdr_rectangle_sample_pos(rect, pix_samp, ray_org); + + /* Check that `ray_org' is not included into a geometry */ + HTRDR(atmosphere_ground_trace_ray + (cmd->ground, ray_org, up_dir, range, NULL, &hit)); + + /* Up direction is occluded. Check if the sample must be rejected, i.e. does it + * lies inside a geometry? */ + if(!S3D_HIT_NONE(&hit)) { + struct htrdr_interface interf = HTRDR_INTERFACE_NULL; + const struct htrdr_mtl* mtl = NULL; + float N[3]; /* Normalized normal of the hit */ + float wi[3]; + float cos_wi_N; + + /* Compute the cosine between the up direction and the hit normal */ + f3_set_d3(wi, up_dir); + f3_normalize(N, hit.normal); + cos_wi_N = f3_dot(wi, N); + + /* Fetch the hit interface and retrieve the material into which the ray was + * traced */ + htrdr_atmosphere_ground_get_interface(cmd->ground, &hit, &interf); + mtl = cos_wi_N < 0 ? &interf.mtl_front : &interf.mtl_back; + + /* Reject the sample if the incident direction do not travel into the sky */ + if(strcmp(mtl->name, cmd->sky_mtl_name) != 0) return RES_BAD_OP; + } + + /* Sample a ray direction */ + htrdr_rectangle_get_normal(rect, normal); + ssp_ran_hemisphere_cos(rng, normal, ray_dir, NULL); + + return RES_OK; +} + +static void +draw_pixel_image + (struct htrdr* htrdr, + const struct htrdr_draw_pixel_args* args, + void* data) +{ + struct htrdr_accum XYZ[3]; /* X, Y, and Z */ + struct htrdr_accum time; + struct htrdr_atmosphere* cmd; + struct atmosphere_pixel_image* pixel = data; + size_t ichannel; + ASSERT(htrdr && htrdr_draw_pixel_args_check(args) && data); + (void)htrdr; + + cmd = args->context; + ASSERT(cmd); + ASSERT(cmd->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ); + ASSERT(cmd->sensor.type == HTRDR_SENSOR_CAMERA); + + /* Reset accumulators */ + XYZ[0] = HTRDR_ACCUM_NULL; + XYZ[1] = HTRDR_ACCUM_NULL; + XYZ[2] = HTRDR_ACCUM_NULL; + time = HTRDR_ACCUM_NULL; + + FOR_EACH(ichannel, 0, 3) { + size_t isamp; + + FOR_EACH(isamp, 0, args->spp) { + struct time t0, t1; + double pix_samp[2]; + double ray_org[3]; + double ray_dir[3]; + double weight; + double r0, r1, r2; + double wlen; /* Sampled wavelength into the spectral band */ + double pdf; + size_t iband; /* Sampled spectral band */ + size_t iquad; /* Sampled quadrature point into the spectral band */ + double usec; + + /* Begin the registration of the time spent to in the realisation */ + time_current(&t0); + + /* Sample a position into the pixel, in the normalized image plane */ + pix_samp[0] = (double)args->pixel_coord[0] + ssp_rng_canonical(args->rng); + pix_samp[1] = (double)args->pixel_coord[1] + ssp_rng_canonical(args->rng); + pix_samp[0] *= args->pixel_normalized_size[0]; + pix_samp[1] *= args->pixel_normalized_size[1]; + + /* Generate a ray starting from the pinhole camera and passing through the + * pixel sample */ + htrdr_camera_ray(cmd->sensor.camera, pix_samp, ray_org, ray_dir); + + r0 = ssp_rng_canonical(args->rng); + r1 = ssp_rng_canonical(args->rng); + r2 = ssp_rng_canonical(args->rng); + + /* Sample a spectral band and a quadrature point */ + switch(ichannel) { + case 0: wlen = htrdr_cie_xyz_sample_X(cmd->cie, r0, r1, &pdf); break; + case 1: wlen = htrdr_cie_xyz_sample_Y(cmd->cie, r0, r1, &pdf); break; + case 2: wlen = htrdr_cie_xyz_sample_Z(cmd->cie, r0, r1, &pdf); break; + default: FATAL("Unreachable code.\n"); break; + } + + iband = htsky_find_spectral_band(cmd->sky, wlen); + iquad = htsky_spectral_band_sample_quadrature(cmd->sky, r2, iband); + + /* Compute the radiance in W/m^2/sr/m */ + weight = atmosphere_compute_radiance_sw(cmd, args->ithread, args->rng, + 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 */ + time_sub(&t0, time_current(&t1), &t0); + usec = (double)time_val(&t0, TIME_NSEC) * 0.001; + + /* Update the pixel accumulator of the current channel */ + XYZ[ichannel].sum_weights += weight; + XYZ[ichannel].sum_weights_sqr += weight*weight; + XYZ[ichannel].nweights += 1; + + /* Update the pixel accumulator of per realisation time */ + time.sum_weights += usec; + time.sum_weights_sqr += usec*usec; + time.nweights += 1; + } + } + + /* Flush pixel data */ + htrdr_accum_get_estimation(XYZ+0, &pixel->X); + htrdr_accum_get_estimation(XYZ+1, &pixel->Y); + htrdr_accum_get_estimation(XYZ+2, &pixel->Z); + pixel->time = time; +} + +static void +draw_pixel_flux + (struct htrdr* htrdr, + const struct htrdr_draw_pixel_args* args, + void* data) +{ + struct htrdr_accum flux; + struct htrdr_accum time; + struct htrdr_atmosphere* cmd; + struct atmosphere_pixel_flux* pixel = data; + size_t isamp; + ASSERT(htrdr && htrdr_draw_pixel_args_check(args) && data); + (void)htrdr; + + cmd = args->context; + ASSERT(cmd); + ASSERT(cmd->sensor.type == HTRDR_SENSOR_RECTANGLE); + ASSERT(cmd->spectral_type == HTRDR_SPECTRAL_LW + || cmd->spectral_type == HTRDR_SPECTRAL_SW); + + /* Reset the pixel accumulators */ + flux = HTRDR_ACCUM_NULL; + time = HTRDR_ACCUM_NULL; + + FOR_EACH(isamp, 0, args->spp) { + struct time t0, t1; + double ray_org[3]; + double ray_dir[3]; + double weight; + double r0, r1, r2; + double wlen; + size_t iband; + size_t iquad; + double usec; + double band_pdf; + res_T res = RES_OK; + + /* Begin the registration of the time spent in the realisation */ + time_current(&t0); + + res = sample_rectangle_ray(cmd, cmd->sensor.rectangle, args->pixel_coord, + args->pixel_normalized_size, args->rng, ray_org, ray_dir); + if(res != RES_OK) continue; /* Reject the current sample */ + + r0 = ssp_rng_canonical(args->rng); + r1 = ssp_rng_canonical(args->rng); + r2 = ssp_rng_canonical(args->rng); + + /* Sample a wavelength */ + wlen = htrdr_ran_wlen_sample(cmd->ran_wlen, r0, r1, &band_pdf); + + /* Select the associated band and sample a quadrature point */ + iband = htsky_find_spectral_band(cmd->sky, wlen); + iquad = htsky_spectral_band_sample_quadrature(cmd->sky, r2, iband); + + if(cmd->spectral_type == HTRDR_SPECTRAL_LW) { + weight = atmosphere_compute_radiance_lw(cmd, args->ithread, args->rng, + ray_org, ray_dir, wlen, iband, iquad); + weight *= PI / band_pdf; /* Transform weight from W/m^2/sr/m to W/m^2 */ + } else { + double sun_dir[3]; + double N[3]; + double L_direct; + double L_diffuse; + double cos_N_sun_dir; + double sun_solid_angle; + ASSERT(cmd->spectral_type == HTRDR_SPECTRAL_SW); + + /* Compute direct contribution if necessary */ + htrdr_atmosphere_sun_sample_direction(cmd->sun, args->rng, sun_dir); + htrdr_rectangle_get_normal(cmd->sensor.rectangle, N); + cos_N_sun_dir = d3_dot(N, sun_dir); + + if(cos_N_sun_dir <= 0) { + L_direct = 0; + } else { + L_direct = atmosphere_compute_radiance_sw(cmd, args->ithread, + args->rng, ATMOSPHERE_RADIANCE_DIRECT, ray_org, sun_dir, wlen, iband, + iquad); + } + + /* Compute diffuse contribution */ + L_diffuse = atmosphere_compute_radiance_sw(cmd, args->ithread, args->rng, + ATMOSPHERE_RADIANCE_DIFFUSE, ray_org, ray_dir, wlen, iband, iquad); + + sun_solid_angle = htrdr_atmosphere_sun_get_solid_angle(cmd->sun); + + /* Compute the weight in W/m^2/m */ + weight = cos_N_sun_dir * sun_solid_angle * L_direct + PI * L_diffuse; + + /* Importance sampling: correct weight with pdf */ + weight /= band_pdf; /* In W/m^2 */ + } + + /* End the registration of the per realisation time */ + time_sub(&t0, time_current(&t1), &t0); + usec = (double)time_val(&t0, TIME_NSEC) * 0.001; + + /* Update the pixel accumulator of the flux */ + flux.sum_weights += weight; + flux.sum_weights_sqr += weight*weight; + flux.nweights += 1; + + /* Update the pixel accumulator of per realisation time */ + time.sum_weights += usec; + time.sum_weights_sqr += usec*usec; + time.nweights += 1; + } + + /* Save the per realisation integration time */ + pixel->flux = flux; + pixel->time = time; +} + +static void +draw_pixel_xwave + (struct htrdr* htrdr, + const struct htrdr_draw_pixel_args* args, + void* data) +{ + struct htrdr_accum radiance; + struct htrdr_accum time; + struct htrdr_atmosphere* cmd; + struct atmosphere_pixel_xwave* pixel = data; + size_t isamp; + double temp_min, temp_max; + ASSERT(htrdr && htrdr_draw_pixel_args_check(args) && data); + (void)htrdr; + + cmd = args->context; + ASSERT(cmd->sensor.type == HTRDR_SENSOR_CAMERA); + ASSERT(cmd->spectral_type == HTRDR_SPECTRAL_LW + || cmd->spectral_type == HTRDR_SPECTRAL_SW); + + /* Reset the pixel accumulators */ + radiance = HTRDR_ACCUM_NULL; + time = HTRDR_ACCUM_NULL; + + FOR_EACH(isamp, 0, args->spp) { + struct time t0, t1; + double pix_samp[2]; + double ray_org[3]; + double ray_dir[3]; + double weight; + double r0, r1, r2; + double wlen; + size_t iband; + size_t iquad; + double usec; + double band_pdf; + + /* Begin the registration of the time spent in the realisation */ + time_current(&t0); + + /* Sample a position into the pixel, in the normalized image plane */ + pix_samp[0] = (double)args->pixel_coord[0] + ssp_rng_canonical(args->rng); + pix_samp[1] = (double)args->pixel_coord[1] + ssp_rng_canonical(args->rng); + pix_samp[0] *= args->pixel_normalized_size[0]; + pix_samp[1] *= args->pixel_normalized_size[1]; + + /* Generate a ray starting from the pinhole camera and passing through the + * pixel sample */ + htrdr_camera_ray(cmd->sensor.camera, pix_samp, ray_org, ray_dir); + + r0 = ssp_rng_canonical(args->rng); + r1 = ssp_rng_canonical(args->rng); + r2 = ssp_rng_canonical(args->rng); + + /* Sample a wavelength */ + wlen = htrdr_ran_wlen_sample(cmd->ran_wlen, r0, r1, &band_pdf); + + /* Select the associated band and sample a quadrature point */ + iband = htsky_find_spectral_band(cmd->sky, wlen); + iquad = htsky_spectral_band_sample_quadrature(cmd->sky, r2, iband); + + /* Compute the spectral radiance in W/m^2/sr/m */ + switch(cmd->spectral_type) { + case HTRDR_SPECTRAL_LW: + weight = atmosphere_compute_radiance_lw(cmd, args->ithread, args->rng, + ray_org, ray_dir, wlen, iband, iquad); + break; + case HTRDR_SPECTRAL_SW: + weight = atmosphere_compute_radiance_sw(cmd, args->ithread, args->rng, + ATMOSPHERE_RADIANCE_ALL, ray_org, ray_dir, wlen, iband, iquad); + break; + default: FATAL("Unreachable code.\n"); break; + } + ASSERT(weight >= 0); + /* Importance sampling: correct weight with pdf */ + weight /= band_pdf; /* In W/m^2/sr */ + + /* End the registration of the per realisation time */ + time_sub(&t0, time_current(&t1), &t0); + usec = (double)time_val(&t0, TIME_NSEC) * 0.001; + + /* Update the pixel accumulator of the current channel */ + radiance.sum_weights += weight; + radiance.sum_weights_sqr += weight*weight; + radiance.nweights += 1; + + /* Update the pixel accumulator of per realisation time */ + time.sum_weights += usec; + time.sum_weights_sqr += usec*usec; + time.nweights += 1; + } + + /* Compute the estimation of the pixel radiance */ + htrdr_accum_get_estimation(&radiance, &pixel->radiance); + + /* Save the per realisation integration time */ + pixel->time = time; + + /* Compute the brightness_temperature of the pixel and estimate its standard + * error if the sources were in the medium (<=> longwave) */ + if(cmd->spectral_type == HTRDR_SPECTRAL_LW) { + const double wlen_min = cmd->wlen_range_m[0]; + const double wlen_max = cmd->wlen_range_m[1]; + pixel->radiance_temperature.E = htrdr_radiance_temperature + (cmd->htrdr, wlen_min, wlen_max, pixel->radiance.E); + temp_min = htrdr_radiance_temperature + (cmd->htrdr, wlen_min, wlen_max, pixel->radiance.E - pixel->radiance.SE); + temp_max = htrdr_radiance_temperature + (cmd->htrdr, wlen_min, wlen_max, pixel->radiance.E + pixel->radiance.SE); + pixel->radiance_temperature.SE = temp_max - temp_min; + } +} + +static INLINE void +setup_draw_map_args_rectangle + (struct htrdr_atmosphere* cmd, + struct htrdr_draw_map_args* args) +{ + ASSERT(cmd && cmd->sensor.type == HTRDR_SENSOR_RECTANGLE && args); + *args = HTRDR_DRAW_MAP_ARGS_NULL; + args->draw_pixel = draw_pixel_flux; + args->buffer_layout = cmd->buf_layout; + args->spp = cmd->spp; + args->context = cmd; +} + +static INLINE void +setup_draw_map_args_camera + (struct htrdr_atmosphere* cmd, + struct htrdr_draw_map_args* args) +{ + ASSERT(cmd && cmd->sensor.type == HTRDR_SENSOR_CAMERA && args); + + *args = HTRDR_DRAW_MAP_ARGS_NULL; + args->buffer_layout = cmd->buf_layout; + args->spp = cmd->spp; + args->context = cmd; + + switch(cmd->spectral_type) { + case HTRDR_SPECTRAL_LW: + case HTRDR_SPECTRAL_SW: + args->draw_pixel = draw_pixel_xwave; + break; + case HTRDR_SPECTRAL_SW_CIE_XYZ: + args->draw_pixel = draw_pixel_image; + break; + default: FATAL("Unreachable code.\n"); break; + } +} + +static INLINE void +dump_accum + (const struct htrdr_accum* acc, /* Accum to dump */ + struct htrdr_accum* out_acc, /* May be NULL */ + FILE* stream) +{ + ASSERT(acc && stream); + + if(acc->nweights == 0) { + fprintf(stream, "0 0 "); + } else { + struct htrdr_estimate estimate = HTRDR_ESTIMATE_NULL; + + htrdr_accum_get_estimation(acc, &estimate); + fprintf(stream, "%g %g ", estimate.E, estimate.SE); + + if(out_acc) { + out_acc->sum_weights += acc->sum_weights; + out_acc->sum_weights_sqr += acc->sum_weights_sqr; + out_acc->nweights += acc->nweights; + } + } +} + +static INLINE void +dump_pixel_flux + (const struct atmosphere_pixel_flux* pix, + struct htrdr_accum* time_acc, /* May be NULL */ + struct htrdr_accum* flux_acc, /* May be NULL */ + FILE* stream) +{ + ASSERT(pix && stream); + dump_accum(&pix->flux, flux_acc, stream); + fprintf(stream, "0 0 0 0 "); + dump_accum(&pix->time, time_acc, stream); + fprintf(stream, "\n"); +} + +static INLINE void +dump_pixel_image + (const struct atmosphere_pixel_image* pix, + struct htrdr_accum* time_acc, /* May be NULL */ + FILE* stream) +{ + ASSERT(pix && stream); + fprintf(stream, "%g %g ", pix->X.E, pix->X.SE); + fprintf(stream, "%g %g ", pix->Y.E, pix->Y.SE); + fprintf(stream, "%g %g ", pix->Z.E, pix->Z.SE); + dump_accum(&pix->time, time_acc, stream); + fprintf(stream, "\n"); +} + +static INLINE void +dump_pixel_xwave + (const struct atmosphere_pixel_xwave* pix, + struct htrdr_accum* time_acc, /* May be NULL */ + FILE* stream) +{ + ASSERT(pix && stream); + fprintf(stream, "%g %g %f %f 0 0 ", + pix->radiance_temperature.E, + pix->radiance_temperature.SE, + pix->radiance.E, + pix->radiance.SE); + dump_accum(&pix->time, time_acc, stream); + fprintf(stream, "\n"); +} + +static res_T +dump_buffer + (struct htrdr_atmosphere* cmd, + struct htrdr_buffer* buf, + struct htrdr_accum* time_acc, /* May be NULL */ + struct htrdr_accum* flux_acc, /* May be NULL */ + FILE* stream) +{ + struct atmosphere_pixel_format pixfmt; + struct htrdr_buffer_layout layout; + size_t x, y; + ASSERT(cmd && buf && stream); + + atmosphere_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; + if(flux_acc) *flux_acc = HTRDR_ACCUM_NULL; + + FOR_EACH(y, 0, layout.height) { + FOR_EACH(x, 0, layout.width) { + void* pix_raw = htrdr_buffer_at(buf, x, y); + ASSERT(IS_ALIGNED(pix_raw, pixfmt.alignment)); + + if(cmd->sensor.type == HTRDR_SENSOR_RECTANGLE) { + const struct atmosphere_pixel_flux* pix = pix_raw; + dump_pixel_flux(pix, time_acc, flux_acc, stream); + } else if(cmd->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ) { + const struct atmosphere_pixel_image* pix = pix_raw; + dump_pixel_image(pix, time_acc, stream); + } else { + const struct atmosphere_pixel_xwave* pix = pix_raw; + dump_pixel_xwave(pix, time_acc, stream); + } + } + fprintf(stream, "\n"); + } + + return RES_OK; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +atmosphere_draw_map(struct htrdr_atmosphere* cmd) +{ + struct htrdr_draw_map_args args = HTRDR_DRAW_MAP_ARGS_NULL; + struct htrdr_accum path_time_acc = HTRDR_ACCUM_NULL; + struct htrdr_accum flux_acc = HTRDR_ACCUM_NULL; + struct htrdr_estimate path_time; + struct htrdr_estimate flux; + res_T res = RES_OK; + ASSERT(cmd); + + args.spp = cmd->spp; + + switch(cmd->sensor.type) { + case HTRDR_SENSOR_RECTANGLE: + setup_draw_map_args_rectangle(cmd, &args); + break; + case HTRDR_SENSOR_CAMERA: + setup_draw_map_args_camera(cmd, &args); + break; + default: FATAL("Unreachable code.\n"); break; + } + + res = htrdr_draw_map(cmd->htrdr, &args, cmd->buf); + if(res != RES_OK) goto error; + + /* No more to do on non master processes */ + if(htrdr_get_mpi_rank(cmd->htrdr) != 0) goto exit; + + /* Write buffer to output */ + res = dump_buffer(cmd, cmd->buf, &path_time_acc, &flux_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); + + if(cmd->sensor.type == HTRDR_SENSOR_RECTANGLE) { + htrdr_accum_get_estimation(&flux_acc, &flux); + htrdr_log(cmd->htrdr, + "Radiative flux density (in W/(external m^2)): %g +/- %g\n", + flux.E, flux.SE); + } + +exit: + return res; +error: + goto exit; +} + diff --git a/src/atmosphere/htrdr_atmosphere_ground.c b/src/atmosphere/htrdr_atmosphere_ground.c @@ -0,0 +1,256 @@ +/* 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 "atmosphere/htrdr_atmosphere_ground.h" + +#include "core/htrdr_log.h" +#include "core/htrdr_geometry.h" +#include "core/htrdr_slab.h" + +#include <star/s3d.h> + +#include <rsys/cstr.h> +#include <rsys/double3.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> + +struct trace_slab_context { + struct htrdr_geometry* geom; + const struct s3d_hit* hit_prev; + struct s3d_hit* hit; +}; +#define TRACE_SLAB_CONTEXT_NULL__ { NULL, NULL, NULL } +static const struct trace_slab_context TRACE_SLAB_CONTEXT_NULL = + TRACE_SLAB_CONTEXT_NULL__; + +struct htrdr_atmosphere_ground { + struct htrdr_geometry* geom; + int repeat; /* Make the ground infinite in X and Y */ + struct htrdr* htrdr; + ref_T ref; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE res_T +trace_slab + (const double org[3], + const double dir[3], + const double range[2], + void* context, + int* hit) +{ + struct trace_slab_context* ctx = context; + res_T res = RES_OK; + ASSERT(org && dir && range && context && hit); + + res = htrdr_geometry_trace_ray + (ctx->geom, org, dir, range, ctx->hit_prev, ctx->hit); + if(res != RES_OK) return res; + + *hit = !S3D_HIT_NONE(ctx->hit); + return RES_OK; +} +static void +release_ground(ref_T* ref) +{ + struct htrdr_atmosphere_ground* ground; + struct htrdr* htrdr; + ASSERT(ref); + ground = CONTAINER_OF(ref, struct htrdr_atmosphere_ground, ref); + if(ground->geom) htrdr_geometry_ref_put(ground->geom); + htrdr = ground->htrdr; + MEM_RM(htrdr_get_allocator(ground->htrdr), ground); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_atmosphere_ground_create + (struct htrdr* htrdr, + const char* obj_filename, /* May be NULL */ + struct htrdr_materials* mats, /* May be NULL */ + const int repeat_ground, /* Infinitely repeat the ground in X and Y */ + struct htrdr_atmosphere_ground** out_ground) +{ + struct htrdr_atmosphere_ground* ground = NULL; + res_T res = RES_OK; + ASSERT(htrdr && out_ground); + + ground = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*ground)); + if(!ground) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "%s: could not allocate the ground data structure -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + ref_init(&ground->ref); + ground->repeat = repeat_ground; + htrdr_ref_get(htrdr); + ground->htrdr = htrdr; + + if(!obj_filename) goto exit; + + if(!mats) { + htrdr_log_err(htrdr, "%s: missing materials.\n", FUNC_NAME); + res = RES_BAD_ARG; + goto error; + } + + res = htrdr_geometry_create(ground->htrdr, obj_filename, mats, &ground->geom); + if(res != RES_OK) goto error; + +exit: + *out_ground = ground; + return res; +error: + if(ground) { + htrdr_atmosphere_ground_ref_put(ground); + ground = NULL; + } + goto exit; +} + +void +htrdr_atmosphere_ground_ref_get(struct htrdr_atmosphere_ground* ground) +{ + ASSERT(ground); + ref_get(&ground->ref); +} + +void +htrdr_atmosphere_ground_ref_put(struct htrdr_atmosphere_ground* ground) +{ + ASSERT(ground); + ref_put(&ground->ref, release_ground); +} + +void +htrdr_atmosphere_ground_get_interface + (struct htrdr_atmosphere_ground* ground, + const struct s3d_hit* hit, + struct htrdr_interface* out_interface) +{ + /* Simply wrap the geometry function */ + htrdr_geometry_get_interface(ground->geom, hit, out_interface); +} + +res_T +htrdr_atmosphere_ground_trace_ray + (struct htrdr_atmosphere_ground* ground, + const double org[3], + const double dir[3], /* Must be normalized */ + const double range[2], + const struct s3d_hit* prev_hit, + struct s3d_hit* hit) +{ + res_T res = RES_OK; + ASSERT(ground && org && dir && range && hit); + + if(!ground->geom) { /* No ground geometry */ + *hit = S3D_HIT_NULL; + goto exit; + } + + if(!ground->repeat) { + res = htrdr_geometry_trace_ray + (ground->geom, org, dir, range, prev_hit, hit); + if(res != RES_OK) goto error; + } else { + struct trace_slab_context slab_ctx = TRACE_SLAB_CONTEXT_NULL; + double low[3], upp[3]; + + htrdr_geometry_get_aabb(ground->geom, low, upp); + + *hit = S3D_HIT_NULL; + slab_ctx.geom = ground->geom; + slab_ctx.hit_prev = prev_hit; + slab_ctx.hit = hit; + + res = htrdr_slab_trace_ray(ground->htrdr, org, dir, range, low, upp, + trace_slab, 32, &slab_ctx); + if(res != RES_OK) goto error; + } + +exit: + return res; +error: + goto exit; +} + +res_T +htrdr_atmosphere_ground_find_closest_point + (struct htrdr_atmosphere_ground* ground, + const double pos[3], + const double radius, + struct s3d_hit* hit) +{ + double query_pos[3]; + double query_radius; + res_T res = RES_OK; + ASSERT(ground && pos && hit); + + if(!ground->geom) { /* No ground geometry */ + *hit = S3D_HIT_NULL; + goto exit; + } + + query_radius = radius; + d3_set(query_pos, pos); + + if(ground->repeat) { + double ground_low[3]; + double ground_upp[3]; + double ground_sz[3]; + double translation[2] = {0, 0}; + int64_t xy[2]; + + /* Define the size of the ground geometry pattern */ + htrdr_geometry_get_aabb(ground->geom, ground_low, ground_upp); + ground_sz[0] = ground_upp[0] - ground_low[0]; + ground_sz[1] = ground_upp[1] - ground_low[1]; + ground_sz[2] = ground_upp[2] - ground_low[2]; + + /* Define the 2D index of the current ground instance. (0,0) is the index + * of the non instantiated ground */ + xy[0] = (int64_t)floor((query_pos[0] - ground_low[0]) / ground_sz[0]); + xy[1] = (int64_t)floor((query_pos[1] - ground_low[1]) / ground_sz[1]); + + /* Define the translation along the X and Y axis from world space to local + * ground geometry space */ + translation[0] = -(double)xy[0] * ground_sz[0]; + translation[1] = -(double)xy[1] * ground_sz[1]; + + /* Transform the query pos in local ground geometry space */ + query_pos[0] += translation[0]; + query_pos[1] += translation[1]; + } + + res = htrdr_geometry_find_closest_point + (ground->geom, query_pos, query_radius, hit); + if(res != RES_OK) goto error; + +exit: + return res; +error: + goto exit; +} diff --git a/src/atmosphere/htrdr_atmosphere_ground.h b/src/atmosphere/htrdr_atmosphere_ground.h @@ -0,0 +1,81 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019 Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef HTRDR_ATMOSPHERE_GROUND_H +#define HTRDR_ATMOSPHERE_GROUND_H + +#include <rsys/rsys.h> + +/* Forward declarations */ +struct htrdr; +struct htrdr_atmosphere_ground; +struct htrdr_interface; +struct htrdr_materials; +struct s3d_hit; +struct ssf_bsdf; + +extern LOCAL_SYM res_T +htrdr_atmosphere_ground_create + (struct htrdr* htrdr, + const char* obj_filename, /* May be NULL <=> No ground geometry */ + struct htrdr_materials* mats, /* May be NULL if no ground geometry */ + const int repeat_ground, /* Infinitely repeat the ground in X and Y */ + struct htrdr_atmosphere_ground** ground); + +extern LOCAL_SYM void +htrdr_atmosphere_ground_ref_get + (struct htrdr_atmosphere_ground* ground); + +extern LOCAL_SYM void +htrdr_atmosphere_ground_ref_put + (struct htrdr_atmosphere_ground* ground); + +extern LOCAL_SYM void +htrdr_atmosphere_ground_get_interface + (struct htrdr_atmosphere_ground* ground, + const struct s3d_hit* hit, + struct htrdr_interface* interface); + +extern LOCAL_SYM res_T +htrdr_atmosphere_ground_create_bsdf + (struct htrdr_atmosphere_ground* ground, + const size_t ithread, + const double wavelength, + const double pos[3], + const double dir[3], /* Incoming ray */ + const struct s3d_hit* hit, + struct htrdr_interface* interf, /* NULL <=> do not return the interface */ + struct ssf_bsdf** bsdf); + +extern LOCAL_SYM res_T +htrdr_atmosphere_ground_trace_ray + (struct htrdr_atmosphere_ground* ground, + const double ray_origin[3], + const double ray_direction[3], /* Must be normalized */ + const double ray_range[2], + const struct s3d_hit* prev_hit,/* Previous hit. Avoid self hit. May be NULL*/ + struct s3d_hit* hit); + +extern LOCAL_SYM res_T +htrdr_atmosphere_ground_find_closest_point + (struct htrdr_atmosphere_ground* ground, + const double position[3], + const double radius, + struct s3d_hit* hit); + +#endif /* HTRDR_ATMOSPHERE_GROUND_H */ + diff --git a/src/atmosphere/htrdr_atmosphere_main.c b/src/atmosphere/htrdr_atmosphere_main.c @@ -0,0 +1,86 @@ +/* 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 "atmosphere/htrdr_atmosphere.h" +#include "atmosphere/htrdr_atmosphere_args.h" + +#include "core/htrdr.h" +#include "core/htrdr_log.h" + +#include <rsys/mem_allocator.h> + +int +htrdr_atmosphere_main(int argc, char** argv) +{ + char cmd_name[] = "htrdr-atmosphere"; + struct htrdr_args htrdr_args = HTRDR_ARGS_DEFAULT; + struct htrdr_atmosphere_args cmd_args = HTRDR_ATMOSPHERE_ARGS_DEFAULT; + struct htrdr* htrdr = NULL; + struct htrdr_atmosphere* cmd = NULL; + const size_t memsz_begin = MEM_ALLOCATED_SIZE(&mem_default_allocator); + size_t memsz_end; + int is_mpi_init = 0; + int err = 0; + res_T res = RES_OK; + + /* 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_atmosphere_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_atmosphere_create(htrdr, &cmd_args, &cmd); + if(res != RES_OK) goto error; + + res = htrdr_atmosphere_run(cmd); + if(res != RES_OK) goto error; + +exit: + htrdr_atmosphere_args_release(&cmd_args); + if(is_mpi_init) htrdr_mpi_finalize(); + if(htrdr) htrdr_ref_put(htrdr); + if(cmd) htrdr_atmosphere_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/atmosphere/htrdr_atmosphere_sun.c b/src/atmosphere/htrdr_atmosphere_sun.c @@ -0,0 +1,160 @@ +/* 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 "atmosphere/htrdr_atmosphere_c.h" +#include "atmosphere/htrdr_atmosphere_sun.h" + +#include "core/htrdr.h" +#include "core/htrdr_log.h" + +#include <rsys/algorithm.h> +#include <rsys/double33.h> +#include <rsys/ref_count.h> +#include <rsys/math.h> + +#include <star/ssp.h> + +struct htrdr_atmosphere_sun { + double half_angle; /* In radian */ + double cos_half_angle; + double solid_angle; /* In sr; solid_angle = 2*PI*(1 - cos(half_angle)) */ + double frame[9]; + double temperature; /* In K */ + + ref_T ref; + struct htrdr* htrdr; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +release_sun(ref_T* ref) +{ + struct htrdr_atmosphere_sun* sun; + struct htrdr* htrdr; + ASSERT(ref); + sun = CONTAINER_OF(ref, struct htrdr_atmosphere_sun, ref); + htrdr = sun->htrdr; + MEM_RM(htrdr_get_allocator(htrdr), sun); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_atmosphere_sun_create + (struct htrdr* htrdr, + struct htrdr_atmosphere_sun** out_sun) +{ + const double main_dir[3] = {0, 0, 1}; /* Default main sun direction */ + struct htrdr_atmosphere_sun* sun = NULL; + res_T res = RES_OK; + ASSERT(htrdr && out_sun); + + sun = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*sun)); + if(!sun) { + htrdr_log_err(htrdr, "could not allocate the sun data structure.\n"); + res = RES_MEM_ERR; + goto error; + } + ref_init(&sun->ref); + sun->half_angle = 4.6524e-3; + sun->temperature = 5778; + sun->cos_half_angle = cos(sun->half_angle); + sun->solid_angle = 2*PI*(1-sun->cos_half_angle); + d33_basis(sun->frame, main_dir); + htrdr_ref_get(htrdr); + sun->htrdr = htrdr; + +exit: + *out_sun = sun; + return res; +error: + if(sun) { + htrdr_atmosphere_sun_ref_put(sun); + sun = NULL; + } + goto exit; +} + +void +htrdr_atmosphere_sun_ref_get(struct htrdr_atmosphere_sun* sun) +{ + ASSERT(sun); + ref_get(&sun->ref); +} + +void +htrdr_atmosphere_sun_ref_put(struct htrdr_atmosphere_sun* sun) +{ + ASSERT(sun); + ref_put(&sun->ref, release_sun); +} + +void +htrdr_atmosphere_sun_set_direction + (struct htrdr_atmosphere_sun* sun, + const double dir[3]) +{ + ASSERT(sun && dir && d3_is_normalized(dir)); + d33_basis(sun->frame, dir); +} + +double +htrdr_atmosphere_sun_sample_direction + (struct htrdr_atmosphere_sun* sun, + struct ssp_rng* rng, + double dir[3]) +{ + ASSERT(sun && rng && dir); + ssp_ran_sphere_cap_uniform_local(rng, sun->cos_half_angle, dir, NULL); + d33_muld3(dir, sun->frame, dir); + return 1.0 / htrdr_atmosphere_sun_get_solid_angle(sun); +} + +double +htrdr_atmosphere_sun_get_solid_angle(const struct htrdr_atmosphere_sun* sun) +{ + ASSERT(sun); + return sun->solid_angle; +} + +double +htrdr_atmosphere_sun_get_radiance + (const struct htrdr_atmosphere_sun* sun, + const double wlen/*In nm*/) +{ + return htrdr_planck_monochromatic + (wlen*1.e-9/*From nm to m*/, sun->temperature); +} + +int +htrdr_atmosphere_sun_is_dir_in_solar_cone + (const struct htrdr_atmosphere_sun* sun, + const double dir[3]) +{ + const double* main_dir; + double dot; + ASSERT(sun && dir && d3_is_normalized(dir)); + ASSERT(d3_is_normalized(sun->frame + 6)); + main_dir = sun->frame + 6; + dot = d3_dot(dir, main_dir); + return dot >= sun->cos_half_angle; +} + diff --git a/src/atmosphere/htrdr_atmosphere_sun.h b/src/atmosphere/htrdr_atmosphere_sun.h @@ -0,0 +1,68 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019 Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef HTRDR_ATMOSPHERE_SUN_H +#define HTRDR_ATMOSPHERE_SUN_H + +#include <rsys/rsys.h> + +/* Forward declaration */ +struct htrdr; +struct htrdr_atmosphere_sun; +struct ssp_rng; + +extern LOCAL_SYM res_T +htrdr_atmosphere_sun_create + (struct htrdr* htrdr, + struct htrdr_atmosphere_sun** out_sun); + +extern LOCAL_SYM void +htrdr_atmosphere_sun_ref_get + (struct htrdr_atmosphere_sun* sun); + +extern LOCAL_SYM void +htrdr_atmosphere_sun_ref_put + (struct htrdr_atmosphere_sun* sun); + +/* Setup the direction *toward* the sun "center" */ +extern LOCAL_SYM void +htrdr_atmosphere_sun_set_direction + (struct htrdr_atmosphere_sun* sun, + const double direction[3]); /* Must be normalized */ + +/* Return a pdf of the sampled dir */ +extern LOCAL_SYM double +htrdr_atmosphere_sun_sample_direction + (struct htrdr_atmosphere_sun* sun, + struct ssp_rng* rng, + double dir[3]); + +extern LOCAL_SYM double +htrdr_atmosphere_sun_get_solid_angle + (const struct htrdr_atmosphere_sun* sun); + +extern LOCAL_SYM double /* W/m^2/sr/m */ +htrdr_atmosphere_sun_get_radiance + (const struct htrdr_atmosphere_sun* sun, + const double wavelength); + +extern LOCAL_SYM int +htrdr_atmosphere_sun_is_dir_in_solar_cone + (const struct htrdr_atmosphere_sun* sun, + const double dir[3]); + +#endif /* HTRDR_ATMOSPHERE_SUN_H */ diff --git a/src/commands/htrdr_atmosphere_cmd.c b/src/commands/htrdr_atmosphere_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 "atmosphere/htrdr_atmosphere.h" + +int +main(int argc, char** argv) +{ + return htrdr_atmosphere_main(argc, argv); +} diff --git a/src/commands/htrdr_cmd.c b/src/commands/htrdr_cmd.c @@ -0,0 +1,108 @@ +/* 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 "atmosphere/htrdr_atmosphere.h" +#include "core/htrdr_version.h" + +#include <string.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +print_usage(const char* cmd) +{ + ASSERT(cmd); + printf("Usage: %s [--version] [--help] <mode> [<args>]\n", cmd); +} + +static void +print_help(const char* cmd) +{ + ASSERT(cmd); + + print_usage(cmd); + printf("\n"); + + printf( +" --version display version information and exit.\n"); + printf( +" --help display this help and exit.\n"); + printf("\n"); + + printf("These are %s available modes:\n", cmd); + printf("\n"); + printf( +" atmosphere Radiative transfer computations in a cloudy atmosphere.\n"); + printf( +" combustion Radiative transfer computations in a combustion medium.\n"); + printf("\n"); + + htrdr_fprint_license(cmd, stdout); +} + +/******************************************************************************* + * Program + ******************************************************************************/ +int +main(int argc, char** argv) +{ + int err = 0; + + if(argc < 2) { + print_usage(argv[0]); + err = -1; + goto error; + } + + /* Atmosphere mode */ + if(!strcmp(argv[1], "atmosphere")) { + err = htrdr_atmosphere_main(argc-1, argv+1); + if(err) goto error; + + /* Combustion mode */ + } else if(!strcmp(argv[1], "combustion")) { + /* TODO */ + + /* Version */ + } else if(!strcmp(argv[1], "--version")) { + printf("%s version %d.%d.%d\n", + argv[0], + HTRDR_VERSION_MAJOR, + HTRDR_VERSION_MINOR, + HTRDR_VERSION_PATCH); + goto exit; + + /* Help */ + } else if(!strcmp(argv[1], "--help")) { + print_help(argv[0]); + goto exit; + + /* Fallback */ + } else { + fprintf(stderr, "Unknown option: %s\n", argv[1]); + print_usage(argv[0]); + err = -1; + goto error; + } + +exit: + return err; +error: + goto exit; +} + diff --git a/src/core/htrdr.c b/src/core/htrdr.c @@ -0,0 +1,639 @@ +/* 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 200809L /* stat.st_time support */ + +#include "core/htrdr.h" +#include "core/htrdr_c.h" +#include "core/htrdr_args.h" +#include "core/htrdr_log.h" +#include "core/htrdr_version.h" + +#include <rsys/cstr.h> +#include <rsys/mem_allocator.h> +#include <rsys/str.h> + +#include "high_tune/htsky.h" + +#include <star/s3d.h> +#include <star/ssf.h> + +#include <errno.h> +#include <fcntl.h> /* open */ +#include <libgen.h> /* basename */ +#include <stdarg.h> +#include <stdio.h> +#include <unistd.h> +#include <time.h> +#include <sys/time.h> /* timespec */ +#include <sys/stat.h> /* S_IRUSR & S_IWUSR */ + +#include <omp.h> +#include <mpi.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static const char* +mpi_thread_support_string(const int val) +{ + switch(val) { + case MPI_THREAD_SINGLE: return "MPI_THREAD_SINGLE"; + case MPI_THREAD_FUNNELED: return "MPI_THREAD_FUNNELED"; + case MPI_THREAD_SERIALIZED: return "MPI_THREAD_SERIALIZED"; + case MPI_THREAD_MULTIPLE: return "MPI_THREAD_MULTIPLE"; + default: FATAL("Unreachable code.\n"); break; + } +} + +static const char* +mpi_error_string(struct htrdr* htrdr, const int mpi_err) +{ + const int ithread = omp_get_thread_num(); + char* str; + int strlen_err; + int err; + ASSERT(htrdr && (size_t)ithread < htrdr->nthreads); + str = htrdr->mpi_err_str + ithread*MPI_MAX_ERROR_STRING; + err = MPI_Error_string(mpi_err, str, &strlen_err); + return err == MPI_SUCCESS ? str : "Invalid MPI error"; +} + +static void +release_mpi(struct htrdr* htrdr) +{ + ASSERT(htrdr); + if(htrdr->mpi_working_procs) { + MEM_RM(htrdr->allocator, htrdr->mpi_working_procs); + htrdr->mpi_working_procs = NULL; + } + if(htrdr->mpi_progress_octree) { + MEM_RM(htrdr->allocator, htrdr->mpi_progress_octree); + htrdr->mpi_progress_octree = NULL; + } + if(htrdr->mpi_progress_render) { + MEM_RM(htrdr->allocator, htrdr->mpi_progress_render); + htrdr->mpi_progress_render = NULL; + } + if(htrdr->mpi_err_str) { + MEM_RM(htrdr->allocator, htrdr->mpi_err_str); + htrdr->mpi_err_str = NULL; + } + if(htrdr->mpi_mutex) { + mutex_destroy(htrdr->mpi_mutex); + htrdr->mpi_mutex = NULL; + } +} + +static res_T +mpi_print_proc_info(struct htrdr* htrdr) +{ + char proc_name[MPI_MAX_PROCESSOR_NAME]; + int proc_name_len; + char* proc_names = NULL; + uint32_t* proc_nthreads = NULL; + uint32_t nthreads = 0; + int iproc; + res_T res = RES_OK; + ASSERT(htrdr); + + if(htrdr->mpi_rank == 0) { + proc_names = MEM_CALLOC(htrdr->allocator, (size_t)htrdr->mpi_nprocs, + MPI_MAX_PROCESSOR_NAME*sizeof(*proc_names)); + if(!proc_names) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "could not allocate the temporary memory for MPI process names -- " + "%s.\n", res_to_cstr(res)); + goto error; + } + + proc_nthreads = MEM_CALLOC(htrdr->allocator, (size_t)htrdr->mpi_nprocs, + sizeof(*proc_nthreads)); + if(!proc_nthreads) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "could not allocate the temporary memory for the #threads of the MPI " + "processes -- %s.\n", res_to_cstr(res)); + goto error; + } + } + + /* Gather process name */ + MPI(Get_processor_name(proc_name, &proc_name_len)); + MPI(Gather(proc_name, MPI_MAX_PROCESSOR_NAME, MPI_CHAR, proc_names, + MPI_MAX_PROCESSOR_NAME, MPI_CHAR, 0, MPI_COMM_WORLD)); + + /* Gather process #threads */ + nthreads = (uint32_t)htrdr->nthreads; + MPI(Gather(&nthreads, 1, MPI_UINT32_T, proc_nthreads, 1, MPI_UINT32_T, 0, + MPI_COMM_WORLD)); + + if(htrdr->mpi_rank == 0) { + FOR_EACH(iproc, 0, htrdr->mpi_nprocs) { + htrdr_log(htrdr, "Process %d -- %s; #threads: %u\n", + iproc, proc_names + iproc*MPI_MAX_PROCESSOR_NAME, proc_nthreads[iproc]); + } + } + +exit: + if(proc_names) MEM_RM(htrdr->allocator, proc_names); + if(proc_nthreads) MEM_RM(htrdr->allocator, proc_nthreads); + return res; +error: + goto exit; +} + +static res_T +init_mpi(struct htrdr* htrdr) +{ + size_t n; + int err; + res_T res = RES_OK; + ASSERT(htrdr); + + htrdr->mpi_err_str = MEM_CALLOC + (htrdr->allocator, htrdr->nthreads, MPI_MAX_ERROR_STRING); + if(!htrdr->mpi_err_str) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "could not allocate the MPI error strings -- %s.\n", + res_to_cstr(res)); + goto error; + } + + err = MPI_Comm_rank(MPI_COMM_WORLD, &htrdr->mpi_rank); + if(err != MPI_SUCCESS) { + htrdr_log_err(htrdr, + "could not determine the MPI rank of the calling process -- %s.\n", + mpi_error_string(htrdr, err)); + res = RES_UNKNOWN_ERR; + goto error; + } + + err = MPI_Comm_size(MPI_COMM_WORLD, &htrdr->mpi_nprocs); + if(err != MPI_SUCCESS) { + htrdr_log_err(htrdr, + "could retrieve the size of the MPI group -- %s.\n", + mpi_error_string(htrdr, err)); + res = RES_UNKNOWN_ERR; + goto error; + } + + htrdr->mpi_working_procs = MEM_CALLOC(htrdr->allocator, + (size_t)htrdr->mpi_nprocs, sizeof(*htrdr->mpi_working_procs)); + if(!htrdr->mpi_working_procs) { + htrdr_log_err(htrdr, + "could not allocate the list of working processes.\n"); + res = RES_MEM_ERR; + goto error; + } + + /* Initialy, all the processes are working */ + htrdr->mpi_nworking_procs = (size_t)htrdr->mpi_nprocs; + memset(htrdr->mpi_working_procs, 0xFF, + htrdr->mpi_nworking_procs*sizeof(*htrdr->mpi_working_procs)); + + /* Allocate #processes progress statuses on the master process and only 1 + * progress status on the other ones: the master process will gather the + * status of the other processes to report their progression. */ + n = (size_t)(htrdr->mpi_rank == 0 ? htrdr->mpi_nprocs : 1); + + htrdr->mpi_progress_octree = MEM_CALLOC + (htrdr->allocator, n, sizeof(*htrdr->mpi_progress_octree)); + if(!htrdr->mpi_progress_octree) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "could not allocate the progress state of the octree building -- %s.\n", + res_to_cstr(res)); + goto error; + } + + htrdr->mpi_progress_render = MEM_CALLOC + (htrdr->allocator, n, sizeof(*htrdr->mpi_progress_render)); + if(!htrdr->mpi_progress_render) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "could not allocate the progress state of the scene rendering -- %s.\n", + res_to_cstr(res)); + goto error; + } + + htrdr->mpi_mutex = mutex_create(); + if(!htrdr->mpi_mutex) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "could not create the mutex to protect MPI calls from concurrent " + "threads -- %s.\n", res_to_cstr(res)); + goto error; + } + + mpi_print_proc_info(htrdr); + +exit: + return res; +error: + release_mpi(htrdr); + goto exit; +} + +static void +release_htrdr(ref_T* ref) +{ + struct htrdr* htrdr = CONTAINER_OF(ref, struct htrdr, ref); + ASSERT(ref); + + if(htrdr->s3d) S3D(device_ref_put(htrdr->s3d)); + release_mpi(htrdr); + if(htrdr->lifo_allocators) { + size_t i; + FOR_EACH(i, 0, htrdr->nthreads) { + mem_shutdown_lifo_allocator(&htrdr->lifo_allocators[i]); + } + MEM_RM(htrdr->allocator, htrdr->lifo_allocators); + } + logger_release(&htrdr->logger); + + MEM_RM(htrdr->allocator, htrdr); +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +htrdr_mpi_init(int argc, char** argv) +{ + char str[MPI_MAX_ERROR_STRING]; + int thread_support; + int len; + int err = 0; + res_T res = RES_OK; + + err = MPI_Init_thread(&argc, &argv, MPI_THREAD_SERIALIZED, &thread_support); + if(err != MPI_SUCCESS) { + MPI_Error_string(err, str, &len); + fprintf(stderr, "Error initializing MPI -- %s.\n", str); + res = RES_UNKNOWN_ERR; + goto error; + } + + if(thread_support != MPI_THREAD_SERIALIZED) { + fprintf(stderr, "The provided MPI implementation does not support " + "serialized API calls from multiple threads. Provided thread support: " + "%s.\n", mpi_thread_support_string(thread_support)); + res = RES_BAD_OP; + goto error; + } + +exit: + return res; +error: + goto exit; +} + +void +htrdr_mpi_finalize(void) +{ + MPI_Finalize(); +} + +res_T +htrdr_create + (struct mem_allocator* mem_allocator, + const struct htrdr_args* args, + struct htrdr** out_htrdr) +{ + struct mem_allocator* allocator = NULL; + struct htrdr* htrdr = NULL; + size_t ithread; + int nthreads_max; + res_T res = RES_OK; + ASSERT(args && out_htrdr); + + allocator = mem_allocator ? mem_allocator : &mem_default_allocator; + htrdr = MEM_CALLOC(allocator, 1, sizeof(*htrdr)); + if(!htrdr) { + fprintf(stderr, HTRDR_LOG_ERROR_PREFIX + "Could not allocate the htrdr data structure.\n"); + res = RES_MEM_ERR; + goto error; + } + htrdr->allocator = allocator; + ref_init(&htrdr->ref); + nthreads_max = MMAX(omp_get_max_threads(), omp_get_num_procs()); + htrdr->verbose = args->verbose; + htrdr->nthreads = MMIN(args->nthreads, (unsigned)nthreads_max); + + setup_logger(htrdr); + + res = init_mpi(htrdr); + if(res != RES_OK) goto error; + + /* Disable the Star-3D verbosity since the Embree backend prints some messages + * on stdout rather than stderr. This is annoying since stdout may be used by + * htrdr to write output data */ + res = s3d_device_create(&htrdr->logger, htrdr->allocator, 0, &htrdr->s3d); + if(res != RES_OK) { + htrdr_log_err(htrdr, + "%s: could not create the Star-3D device -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + + htrdr->lifo_allocators = MEM_CALLOC + (htrdr->allocator, htrdr->nthreads, sizeof(*htrdr->lifo_allocators)); + if(!htrdr->lifo_allocators) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "%s: could not allocate the list of per thread LIFO allocator -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + + FOR_EACH(ithread, 0, htrdr->nthreads) { + res = mem_init_lifo_allocator + (&htrdr->lifo_allocators[ithread], htrdr->allocator, 16384); + if(res != RES_OK) { + htrdr_log_err(htrdr, + "%s: could not initialise the LIFO allocator of the thread %lu -- %s.\n", + FUNC_NAME, (unsigned long)ithread, res_to_cstr(res)); + goto error; + } + } + +exit: + *out_htrdr = htrdr; + return res; +error: + if(htrdr) { + htrdr_ref_put(htrdr); + htrdr = NULL; + } + goto exit; +} + +void +htrdr_ref_get(struct htrdr* htrdr) +{ + ASSERT(htrdr); + ref_get(&htrdr->ref); +} + +void +htrdr_ref_put(struct htrdr* htrdr) +{ + ASSERT(htrdr); + ref_put(&htrdr->ref, release_htrdr); +} + +size_t +htrdr_get_threads_count(const struct htrdr* htrdr) +{ + ASSERT(htrdr); + return htrdr->nthreads; +} + +size_t +htrdr_get_procs_count(const struct htrdr* htrdr) +{ + ASSERT(htrdr); + return (size_t)htrdr->mpi_nprocs; +} + +int +htrdr_get_mpi_rank(const struct htrdr* htrdr) +{ + ASSERT(htrdr); + return htrdr->mpi_rank; +} + +struct mem_allocator* +htrdr_get_allocator(struct htrdr* htrdr) +{ + ASSERT(htrdr); + return htrdr->allocator; +} + +struct mem_allocator* +htrdr_get_thread_allocator(struct htrdr* htrdr, const size_t ithread) +{ + ASSERT(htrdr && ithread < htrdr_get_threads_count(htrdr)); + return htrdr->lifo_allocators + ithread; +} + +struct logger* +htrdr_get_logger(struct htrdr* htrdr) +{ + ASSERT(htrdr); + return &htrdr->logger; +} + +int +htrdr_get_verbosity_level(const struct htrdr* htrdr) +{ + ASSERT(htrdr); + return htrdr->verbose; +} + +struct s3d_device* +htrdr_get_s3d(struct htrdr* htrdr) +{ + ASSERT(htrdr); + return htrdr->s3d; +} + +res_T +htrdr_open_output_stream + (struct htrdr* htrdr, + const char* filename, + const int read, + int force_overwrite, + FILE** out_fp) +{ + FILE* fp = NULL; + int fd = -1; + const char* mode; + res_T res = RES_OK; + ASSERT(htrdr && filename && out_fp); + + mode = read ? "w+" : "w"; + + if(force_overwrite) { + fp = fopen(filename, mode); + if(!fp) { + htrdr_log_err(htrdr, "could not open the output file `%s'.\n", filename); + goto error; + } + } else { + const int access_flags = read ? O_RDWR : O_WRONLY; + fd = open(filename, O_CREAT|O_EXCL|O_TRUNC|access_flags, S_IRUSR|S_IWUSR); + if(fd >= 0) { + fp = fdopen(fd, mode); + if(fp == NULL) { + htrdr_log_err(htrdr, "could not open the output file `%s'.\n", filename); + goto error; + } + } else if(errno == EEXIST) { + htrdr_log_err(htrdr, "the output file `%s' already exists. \n", + filename); + goto error; + } else { + htrdr_log_err(htrdr, + "unexpected error while opening the output file `%s'.\n", filename); + goto error; + } + } +exit: + *out_fp = fp; + return res; +error: + res = RES_IO_ERR; + if(fp) { + CHK(fclose(fp) == 0); + fp = NULL; + } else if(fd >= 0) { + CHK(close(fd) == 0); + } + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +void +send_mpi_progress + (struct htrdr* htrdr, const enum htrdr_mpi_message msg, int32_t percent) +{ + ASSERT(htrdr); + ASSERT(msg == HTRDR_MPI_PROGRESS_RENDERING); + (void)htrdr; + mutex_lock(htrdr->mpi_mutex); + MPI(Send(&percent, 1, MPI_INT32_T, 0, msg, MPI_COMM_WORLD)); + mutex_unlock(htrdr->mpi_mutex); +} + +void +fetch_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg) +{ + struct timespec t; + int32_t* progress = NULL; + int iproc; + ASSERT(htrdr && htrdr->mpi_rank == 0); + + t.tv_sec = 0; + t.tv_nsec = 10000000; /* 10ms */ + + switch(msg) { + case HTRDR_MPI_PROGRESS_RENDERING: + progress = htrdr->mpi_progress_render; + break; + default: FATAL("Unreachable code.\n"); break; + } + + FOR_EACH(iproc, 1, htrdr->mpi_nprocs) { + /* Flush the last sent percentage of the process `iproc' */ + for(;;) { + MPI_Request req; + int flag; + int complete; + + mutex_lock(htrdr->mpi_mutex); + MPI(Iprobe(iproc, msg, MPI_COMM_WORLD, &flag, MPI_STATUS_IGNORE)); + mutex_unlock(htrdr->mpi_mutex); + + if(flag == 0) break; /* No more message */ + + mutex_lock(htrdr->mpi_mutex); + MPI(Irecv(&progress[iproc], 1, MPI_INT32_T, iproc, msg, MPI_COMM_WORLD, &req)); + mutex_unlock(htrdr->mpi_mutex); + for(;;) { + mutex_lock(htrdr->mpi_mutex); + MPI(Test(&req, &complete, MPI_STATUS_IGNORE)); + mutex_unlock(htrdr->mpi_mutex); + if(complete) break; + nanosleep(&t, NULL); + } + } + } +} + +void +print_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg) +{ + ASSERT(htrdr && htrdr->mpi_rank == 0); + + if(htrdr->mpi_nprocs == 1) { + switch(msg) { + case HTRDR_MPI_PROGRESS_RENDERING: + htrdr_log(htrdr, "\33[2K\r"); /* Erase the line */ + htrdr_log(htrdr, "Rendering: %3d%%\r", htrdr->mpi_progress_render[0]); + break; + default: FATAL("Unreachable code.\n"); break; + } + } else { + int iproc; + FOR_EACH(iproc, 0, htrdr->mpi_nprocs) { + switch(msg) { + case HTRDR_MPI_PROGRESS_RENDERING: + htrdr_log(htrdr, "Process %d -- rendering: %3d%%%c", + iproc, htrdr->mpi_progress_render[iproc], + iproc == htrdr->mpi_nprocs - 1 ? '\r' : '\n'); + break; + default: FATAL("Unreachable code.\n"); break; + } + } + } +} + +void +clear_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg) +{ + ASSERT(htrdr); + (void)msg; + if(htrdr->mpi_nprocs > 1) { + int iproc; + FOR_EACH(iproc, 0, htrdr->mpi_nprocs) { + htrdr_log(htrdr, "\33[2K\r"); /* Erase the line */ + if(iproc != htrdr->mpi_nprocs-1) { + htrdr_log(htrdr, "\033[1A\r"); /* Move up */ + } + } + } +} + +int +total_mpi_progress(const struct htrdr* htrdr, const enum htrdr_mpi_message msg) +{ + const int* progress = NULL; + int total = 0; + int iproc; + ASSERT(htrdr && htrdr->mpi_rank == 0); + + switch(msg) { + case HTRDR_MPI_PROGRESS_RENDERING: + progress = htrdr->mpi_progress_render; + break; + default: FATAL("Unreachable code.\n"); break; + } + + FOR_EACH(iproc, 0, htrdr->mpi_nprocs) { + total += progress[iproc]; + } + total = total / htrdr->mpi_nprocs; + return total; +} + diff --git a/src/core/htrdr.h b/src/core/htrdr.h @@ -0,0 +1,162 @@ +/* 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_H +#define HTRDR_H + +#include <rsys/rsys.h> +#include <stdio.h> + +/* Library symbol management */ +#if defined(HTRDR_CORE_SHARED_BUILD) /* Build shared library */ + #define HTRDR_CORE_API extern EXPORT_SYM +#elif defined(HTRDR_CORE_STATIC) /* Use/build static library */ + #define HTRDR_CORE_API extern LOCAL_SYM +#else /* Use shared library */ + #define HTRDR_CORE_API extern IMPORT_SYM +#endif + +#if defined(HTRDR_SHARED_BUILD) /* Build shared library */ + #define HTRDR_API extern EXPORT_SYM +#elif defined(HTRDR_STATIC) /* Use/build static library */ + #define HTRDR_API extern LOCAL_SYM +#else /* Use shared library */ + #define HTRDR_API extern IMPORT_SYM +#endif + +/* Helper macro that asserts if the invocation of the htrdr function `Func' + * returns an error. One should use this macro on htrdr function calls for + * which no explicit error checking is performed */ +#ifndef NDEBUG + #define HTRDR(Func) ASSERT(htrdr_ ## Func == RES_OK) +#else + #define HTRDR(Func) htrdr_ ## Func +#endif + +/* Forward declarations */ +struct htrdr_buffer; +struct mem_allocator; +struct mutex; + +struct htrdr_args { + unsigned nthreads; /* #threads of the process */ + int verbose; /* Verbosity level */ +}; +#define HTRDR_ARGS_DEFAULT__ { (unsigned)~0, 1 } +static const struct htrdr_args HTRDR_ARGS_DEFAULT = HTRDR_ARGS_DEFAULT__; + +/* Forward declaration */ +struct htrdr; + +static INLINE void +htrdr_fprint_copyright(const char* cmd, FILE* stream) +{ + (void)cmd; + fprintf(stream, +"Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> <contact@meso-star.com>.\n" +"Copyright (C) 2018, 2019, 2021 CNRS.\n" +"Copyright (C) 2018, 2019 Université Paul Sabatier.\n"); +} + +static INLINE void +htrdr_fprint_license(const char* cmd, FILE* stream) +{ + ASSERT(cmd); + fprintf(stream, +"%s is free software released under the GNU GPL license, version\n" +"3 or later. You are free to change or redistribute it under certain\n" +"conditions <http://gnu.org/licenses/gpl.html>.\n", cmd); +} + +BEGIN_DECLS + +/* Initialize the MPI execution environment. Must be called priorly to any MPI + * invocation, e.g. at the beginning of the main function */ +HTRDR_CORE_API res_T +htrdr_mpi_init + (int argc, + char** argv); + +/* Terminate the MPI execution environment */ +HTRDR_CORE_API void +htrdr_mpi_finalize + (void); + +/******************************************************************************* + * HTRDR api + ******************************************************************************/ +HTRDR_CORE_API res_T +htrdr_create + (struct mem_allocator* allocator, + const struct htrdr_args* args, + struct htrdr** htrdr); + +HTRDR_CORE_API void +htrdr_ref_get + (struct htrdr* htrdr); + +HTRDR_CORE_API void +htrdr_ref_put + (struct htrdr* htrdr); + +/* Return the number of threads used by the process */ +HTRDR_CORE_API size_t +htrdr_get_threads_count + (const struct htrdr* htrdr); + +/* Return the number of running processes for the current htrdr instance */ +HTRDR_CORE_API size_t +htrdr_get_procs_count + (const struct htrdr* htrdr); + +HTRDR_CORE_API int +htrdr_get_mpi_rank + (const struct htrdr* htrdr); + +HTRDR_CORE_API struct mem_allocator* +htrdr_get_allocator + (struct htrdr* htrdr); + +HTRDR_CORE_API struct mem_allocator* +htrdr_get_thread_allocator + (struct htrdr* htrdr, + const size_t ithread); + +HTRDR_CORE_API struct logger* +htrdr_get_logger + (struct htrdr* htrdr); + +HTRDR_CORE_API int +htrdr_get_verbosity_level + (const struct htrdr* htrdr); + +HTRDR_CORE_API struct s3d_device* +htrdr_get_s3d + (struct htrdr* htrdr); + +HTRDR_CORE_API res_T +htrdr_open_output_stream + (struct htrdr* htrdr, + const char* filename, + const int read, + int force_overwrite, + FILE** out_fp); + +END_DECLS + +#endif /* HTRDR_H */ + diff --git a/src/core/htrdr_accum.h b/src/core/htrdr_accum.h @@ -0,0 +1,64 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019 Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef HTRDR_ACCUM_H +#define HTRDR_ACCUM_H + +#include <rsys/rsys.h> +#include <rsys/math.h> + +/* Monte carlo accumulator */ +struct htrdr_accum { + double sum_weights; /* Sum of Monte-Carlo weights */ + double sum_weights_sqr; /* Sum of Monte-Carlo square weights */ + size_t nweights; /* #accumlated weights */ + size_t nfailures; /* #failures */ +}; +#define HTRDR_ACCUM_NULL__ {0, 0, 0, 0} +static const struct htrdr_accum HTRDR_ACCUM_NULL = HTRDR_ACCUM_NULL__; + +/* Monte carlo estimate */ +struct htrdr_estimate { + double E; /* Expected value */ + double SE; /* Standard error */ +}; +#define HTRDR_ESTIMATE_NULL__ {0, 0} +static const struct htrdr_estimate HTRDR_ESTIMATE_NULL = HTRDR_ESTIMATE_NULL__; + +static FINLINE void +htrdr_accum_get_estimation + (const struct htrdr_accum* acc, + struct htrdr_estimate* estimate) +{ + ASSERT(acc && estimate); + + if(!acc->nweights) { + estimate->E = 0; + estimate->SE = 0; + } else { + const double N = (double)acc->nweights; + double E, V, SE; + E = acc->sum_weights / N; + V = MMAX(acc->sum_weights_sqr / N - E*E, 0); + SE = sqrt(V/N); + + estimate->E = E; + estimate->SE = SE; + } +} + +#endif /* HTRDR_SOLVE_H */ diff --git a/src/core/htrdr_args.c b/src/core/htrdr_args.c @@ -0,0 +1,393 @@ +/* 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 2 /* strtok_r support */ + +#include "core/htrdr.h" +#include "core/htrdr_args.h" +#include "core/htrdr_version.h" + +#include <rsys/cstr.h> +#include <rsys/double3.h> + +#include <string.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE res_T +parse_doubleX(const char* str, double* val, const size_t sz) +{ + size_t len; + res_T res = RES_OK; + ASSERT(str && val); + res = cstr_to_list_double(str, ',', val, &len, sz); + if(res == RES_OK && len != sz) res = RES_BAD_ARG; + return res; +} + +static INLINE res_T +parse_definition(const char* str, unsigned val[2]) +{ + size_t len; + res_T res = RES_OK; + ASSERT(str && val); + res = cstr_to_list_uint(str, 'x', val, &len, 2); + if(res != RES_OK) return res; + if(len != 2) return RES_BAD_ARG; + if(val[0] > 16384 || val[1] > 16384) return RES_BAD_ARG; + return RES_OK; +} + +static res_T +parse_fov(const char* str, double* out_fov) +{ + double fov; + res_T res = RES_OK; + ASSERT(str && out_fov); + + res = cstr_to_double(str, &fov); + if(res != RES_OK) { + fprintf(stderr, "Invalid field of view `%s'.\n", str); + return RES_BAD_ARG; + } + if(fov <= 0 || fov >= 180) { + fprintf(stderr, "The field of view %g is not in [30, 120].\n", fov); + return RES_BAD_ARG; + } + *out_fov = fov; + return RES_OK; +} + +static res_T +parse_image_parameter(void* args, const char* str) +{ + char buf[128]; + struct htrdr_args_image* img = args; + char* key; + char* val; + char* ctx; + res_T res = RES_OK; + ASSERT(str && img); + + if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { + fprintf(stderr, + "Could not duplicate the image option string `%s'.\n", str); + res = RES_MEM_ERR; + goto error; + } + strncpy(buf, str, sizeof(buf)); + + key = strtok_r(buf, "=", &ctx); + val = strtok_r(NULL, "", &ctx); + + if(!val) { + fprintf(stderr, "Missing a value to the image option `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + + #define PARSE(Name, Func) \ + res = Func; \ + if(res != RES_OK) { \ + fprintf(stderr, "Invalid image "Name" `%s'.\n", val); \ + goto error; \ + } (void)0 + if(!strcmp(key, "def")) { + PARSE("definition", parse_definition(val, img->definition)); + } else if(!strcmp(key, "spp")) { + PARSE("#samples per pixel", cstr_to_uint(val, &img->spp)); + } else { + fprintf(stderr, "Invalid image parameter `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + #undef PARSE + + if(!img->definition[0] || !img->definition[1]) { + fprintf(stderr, "The image definition cannot be null.n"); + res = RES_BAD_ARG; + goto error; + } + if(!img->spp) { + fprintf(stderr, "The number of samples per pixel cannot be null.\n"); + res = RES_BAD_ARG; + goto error; + } + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_camera_parameter(void* args, const char* str) +{ + char buf[128]; + struct htrdr_args_camera* cam = args; + char* key; + char* val; + char* ctx; + res_T res = RES_OK; + ASSERT(cam && str); + + if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { + fprintf(stderr, + "Could not duplicate the camera option string `%s'.\n", str); + res = RES_MEM_ERR; + goto error; + } + strncpy(buf, str, sizeof(buf)); + + key = strtok_r(buf, "=", &ctx); + val = strtok_r(NULL, "", &ctx); + + if(!val) { + fprintf(stderr, "Missing value to the camera option `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + + #define PARSE(Name, Func) { \ + if(RES_OK != (res = Func)) { \ + fprintf(stderr, "Invalid camera "Name" `%s'.\n", val); \ + goto error; \ + } \ + } (void)0 + if(!strcmp(key, "pos")) { + PARSE("position", parse_doubleX(val, cam->position, 3)); + } else if(!strcmp(key, "tgt")) { + PARSE("target", parse_doubleX(val, cam->target, 3)); + } else if(!strcmp(key, "up")) { + PARSE("up vector", parse_doubleX(val, cam->up, 3)); + } else if(!strcmp(key, "fov")) { + PARSE("field-of-view", parse_fov(val, &cam->fov_y)); + } else { + fprintf(stderr, "Invalid camera parameter `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + #undef PARSE +exit: + return res; +error: + goto exit; +} + +static res_T +parse_rectangle_parameter(void* args, const char* str) +{ + char buf[128]; + struct htrdr_args_rectangle* rect = args; + char* key; + char* val; + char* ctx; + res_T res = RES_OK; + ASSERT(rect && str); + + if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { + fprintf(stderr, + "Could not duplicate the rectangle option string `%s'.\n", str); + res = RES_MEM_ERR; + goto error; + } + strncpy(buf, str, sizeof(buf)); + + /* pos=0,0,10.1; key <- pos, val <- 0,0,10 */ + key = strtok_r(buf, "=", &ctx); + val = strtok_r(NULL, "", &ctx); + + if(!val) { + fprintf(stderr, "Missing value to the rectangle option `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + + #define PARSE(Name, Func) { \ + if(RES_OK != (res = Func)) { \ + fprintf(stderr, "Invalid rectangle "Name" `%s'.\n", val); \ + goto error; \ + } \ + } (void)0 + if(!strcmp(key, "pos")) { + PARSE("position", parse_doubleX(val, rect->position, 3)); + } else if(!strcmp(key, "tgt")) { + PARSE("target", parse_doubleX(val, rect->target, 3)); + } else if(!strcmp(key, "up")) { + PARSE("up vector", parse_doubleX(val, rect->up, 3)); + } else if(!strcmp(key, "sz")) { + PARSE("size", parse_doubleX(val, rect->size, 2)); + } else { + fprintf(stderr, "Invalid rectangle parameter `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + #undef PARSE +exit: + return res; +error: + goto exit; +} + +static res_T +parse_spectral_range(const char* str, double wlen_range[2]) +{ + double range[2]; + size_t len; + res_T res = RES_OK; + ASSERT(wlen_range && str); + + res = cstr_to_list_double(str, ',', range, &len, 2); + if(res == RES_OK && len != 2) res = RES_BAD_ARG; + if(res == RES_OK && range[0] > range[1]) res = RES_BAD_ARG; + if(res != RES_OK) { + fprintf(stderr, "Invalid spectral range `%s'.\n", str); + goto error; + } + wlen_range[0] = range[0]; + wlen_range[1] = range[1]; + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_spectral_parameter(void* ptr, const char* str) +{ + char buf[128]; + struct htrdr_args_spectral* args = ptr; + char* key; + char* val; + char* ctx; + res_T res = RES_OK; + ASSERT(args && str); + + if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { + fprintf(stderr, + "Could not duplicate the spectral option string `%s'.\n", str); + res = RES_MEM_ERR; + goto error; + } + strncpy(buf, str, sizeof(buf)); + + key = strtok_r(buf, "=", &ctx); + val = strtok_r(NULL, "", &ctx); + + if(!strcmp(key, "cie_xyz")) { + args->spectral_type = HTRDR_SPECTRAL_SW_CIE_XYZ; + args->wlen_range[0] = HTRDR_CIE_XYZ_RANGE_DEFAULT[0]; + args->wlen_range[1] = HTRDR_CIE_XYZ_RANGE_DEFAULT[1]; + } else { + if(!val) { + fprintf(stderr, "Missing value to the spectral option `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + if(!strcmp(key, "sw")) { + args->spectral_type = HTRDR_SPECTRAL_SW; + res = parse_spectral_range(val, args->wlen_range); + if(res != RES_OK) goto error; + } else if(!strcmp(key, "lw")) { + args->spectral_type = HTRDR_SPECTRAL_LW; + res = parse_spectral_range(val, args->wlen_range); + if(res != RES_OK) goto error; + } else if(!strcmp(key, "Tref")) { + res = cstr_to_double(val, &args->ref_temperature); + if(res == RES_OK && args->ref_temperature < 0) res = RES_BAD_ARG; + if(res != RES_OK) { + fprintf(stderr, "Invalid reference temperature Tref=%s.\n", val); + goto error; + } + } else { + fprintf(stderr, "Invalid spectral parameter `%s'.\n", key); + res = RES_BAD_ARG; + goto error; + } + } + +exit: + return res; +error: + goto exit; +} + +static res_T +parse_multiple_parameters + (void* args, + const char* str, + res_T (*parse_parameter)(void* args, const char* str)) +{ + char buf[512]; + char* tk; + char* ctx; + res_T res = RES_OK; + ASSERT(args && str); + + if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { + fprintf(stderr, "Could not duplicate the option string `%s'.\n", str); + res = RES_MEM_ERR; + goto error; + } + strncpy(buf, str, sizeof(buf)); + + tk = strtok_r(buf, ":", &ctx); + do { + res = parse_parameter(args, tk); + if(res != RES_OK) goto error; + tk = strtok_r(NULL, ":", &ctx); + } while(tk); + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +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); +} + +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); +} + +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); +} + +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); +} + diff --git a/src/core/htrdr_args.h.in b/src/core/htrdr_args.h.in @@ -0,0 +1,111 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019, Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef HTRDR_ARGS_H +#define HTRDR_ARGS_H + +#include "core/htrdr_cie_xyz.h" +#include "core/htrdr_spectral.h" + +#include <rsys/rsys.h> + +/* Arguments of a pinhole camera sensor */ +struct htrdr_args_camera { + double position[3]; /* Focal point */ + double target[3]; /* Targeted position */ + double up[3]; /* Up vector of the camera */ + double fov_y; /* Vertical field of view of the camera, in degrees */ +}; +#define HTRDR_ARGS_CAMERA_DEFAULT__ { \ + {@HTRDR_ARGS_DEFAULT_CAMERA_POS@}, /* position */ \ + {@HTRDR_ARGS_DEFAULT_CAMERA_TGT@}, /* target */ \ + {@HTRDR_ARGS_DEFAULT_CAMERA_UP@}, /* Camera up */ \ + @HTRDR_ARGS_DEFAULT_CAMERA_FOV@, /* Vertical field of view */ \ +} +static const struct htrdr_args_camera HTRDR_ARGS_CAMERA_DEFAULT = + HTRDR_ARGS_CAMERA_DEFAULT__; + +/* Arguments of a rectangular sensor */ +struct htrdr_args_rectangle { + double position[3]; /* Center of the renctangle */ + double target[3]; /* Targeted point (rectangle.normal = target - position) */ + double up[3]; /* Up vector of the rectangle */ + double size[2]; /* Plane size */ +}; +#define HTRDR_ARGS_RECTANGLE_DEFAULT__ { \ + {@HTRDR_ARGS_DEFAULT_RECTANGLE_POS@}, /* Rectangle center */ \ + {@HTRDR_ARGS_DEFAULT_RECTANGLE_TGT@}, /* Rectangle target */ \ + {@HTRDR_ARGS_DEFAULT_RECTANGLE_UP@}, /* Rectangle up */ \ + {@HTRDR_ARGS_DEFAULT_RECTANGLE_SZ@}, /* Rectangle size */ \ +} +static const struct htrdr_args_rectangle HTRDR_ARGS_RECTANGLE_DEFAULT = + HTRDR_ARGS_RECTANGLE_DEFAULT__; + +/* Arguments of an image */ +struct htrdr_args_image { + unsigned definition[2]; /* #pixels in X and Y */ + unsigned spp; /* #samples per pixel */ +}; +#define HTRDR_ARGS_IMAGE_DEFAULT__ { \ + {@HTRDR_ARGS_DEFAULT_IMG_WIDTH@, @HTRDR_ARGS_DEFAULT_IMG_HEIGHT@}, \ + @HTRDR_ARGS_DEFAULT_IMG_SPP@ \ +} +static const struct htrdr_args_image HTRDR_ARGS_IMAGE_DEFAULT = + HTRDR_ARGS_IMAGE_DEFAULT__; + +/* Arguments of the spectral domain */ +struct htrdr_args_spectral { + double wlen_range[2]; /* Spectral range of integration in nm */ + double ref_temperature; /* Planck reference temperature in Kelvin */ + enum htrdr_spectral_type spectral_type; +}; +#define HTRDR_ARGS_SPECTRAL_DEFAULT__ { \ + HTRDR_CIE_XYZ_RANGE_DEFAULT__, /* Spectral range */ \ + -1, /* Reference temperature */ \ + HTRDR_SPECTRAL_SW_CIE_XYZ, /* Spectral type */ \ +} +static const struct htrdr_args_spectral HTRDR_ARGS_SPECTRAL_DEFAULT = + HTRDR_ARGS_SPECTRAL_DEFAULT__; + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_args_camera_parse + (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, + const char* str); + +HTRDR_CORE_API res_T +htrdr_args_spectral_parse + (struct htrdr_args_spectral* spectral, + const char* str); + +END_DECLS + +#endif /* HTRDR_ARGS_H */ diff --git a/src/core/htrdr_buffer.c b/src/core/htrdr_buffer.c @@ -0,0 +1,135 @@ +/* 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 "core/htrdr.h" +#include "core/htrdr_buffer.h" +#include "core/htrdr_log.h" + +#include <rsys/math.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> + +struct htrdr_buffer { + struct htrdr_buffer_layout layout; + char* mem; + + struct htrdr* htrdr; + ref_T ref; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +buffer_release(ref_T* ref) +{ + struct htrdr_buffer* buf = NULL; + struct htrdr* htrdr = NULL; + ASSERT(ref); + buf = CONTAINER_OF(ref, struct htrdr_buffer, ref); + htrdr = buf->htrdr; + if(buf->mem) MEM_RM(htrdr_get_allocator(htrdr), buf->mem); + MEM_RM(htrdr_get_allocator(htrdr), buf); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_buffer_create + (struct htrdr* htrdr, + const struct htrdr_buffer_layout* layout, + struct htrdr_buffer** out_buf) +{ + struct htrdr_buffer* buf = NULL; + size_t memsz = 0; + res_T res = RES_OK; + ASSERT(htrdr && layout && out_buf); + + if(!htrdr_buffer_layout_check(layout)) { + htrdr_log_err(htrdr, "Invalid buffer memory layout.\n"); + res = RES_BAD_ARG; + goto error; + } + + buf = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*buf)); + if(!buf) { + res = RES_MEM_ERR; + goto error; + } + ref_init(&buf->ref); + buf->layout = *layout; + htrdr_ref_get(htrdr); + buf->htrdr = htrdr; + + memsz = buf->layout.pitch * buf->layout.height; + buf->mem = MEM_ALLOC_ALIGNED + (htrdr_get_allocator(htrdr), memsz, buf->layout.alignment); + if(!buf->mem) { + res = RES_MEM_ERR; + goto error; + } + +exit: + *out_buf = buf; + return res; +error: + if(buf) { + htrdr_buffer_ref_put(buf); + buf = NULL; + } + goto exit; +} + +void +htrdr_buffer_ref_get(struct htrdr_buffer* buf) +{ + ASSERT(buf); + ref_get(&buf->ref); +} + +void +htrdr_buffer_ref_put(struct htrdr_buffer* buf) +{ + ASSERT(buf); + ref_put(&buf->ref, buffer_release); +} + +void +htrdr_buffer_get_layout + (const struct htrdr_buffer* buf, + struct htrdr_buffer_layout* layout) +{ + ASSERT(buf && layout); + *layout = buf->layout; +} + +void* +htrdr_buffer_get_data(struct htrdr_buffer* buf) +{ + ASSERT(buf); + return buf->mem; +} + +void* +htrdr_buffer_at(struct htrdr_buffer* buf, const size_t x, const size_t y) +{ + ASSERT(buf && x < buf->layout.width && y < buf->layout.height); + return buf->mem + y*buf->layout.pitch + x*buf->layout.elmt_size; +} + diff --git a/src/core/htrdr_buffer.h b/src/core/htrdr_buffer.h @@ -0,0 +1,102 @@ +/* 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_BUFFER_H +#define HTRDR_BUFFER_H + +#include "core/htrdr.h" + +#include <rsys/math.h> +#include <rsys/rsys.h> + +/* + * Row major ordered 2D buffer + */ + +struct htrdr_buffer_layout { + size_t width; /* #elements in X */ + size_t height; /* #elements in Y */ + size_t pitch; /* #Bytes between 2 consecutive line */ + size_t elmt_size; /* Size of an element in the buffer */ + size_t alignment; /* Alignement of the memory */ +}; +#define HTRDR_BUFFER_LAYOUT_NULL__ {0,0,0,0,0} +static const struct htrdr_buffer_layout HTRDR_BUFFER_LAYOUT_NULL = + HTRDR_BUFFER_LAYOUT_NULL__; + +static INLINE int +htrdr_buffer_layout_eq + (const struct htrdr_buffer_layout* a, + const struct htrdr_buffer_layout* b) +{ + ASSERT(a && b); + return a->width == b->width + && a->height == b->height + && a->pitch == b->pitch + && a->elmt_size == b->elmt_size + && a->alignment == b->alignment; +} + +static INLINE int +htrdr_buffer_layout_check(const struct htrdr_buffer_layout* layout) +{ + return layout + && layout->width + && layout->height + && layout->elmt_size + && layout->width*layout->elmt_size <= layout->pitch + && IS_POW2(layout->alignment); +} + +/* Forward declarations */ +struct htrdr; +struct htrdr_buffer; + +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_buffer_create + (struct htrdr* htrdr, + const struct htrdr_buffer_layout* layout, + struct htrdr_buffer** buf); + +HTRDR_CORE_API void +htrdr_buffer_ref_get + (struct htrdr_buffer* buf); + +HTRDR_CORE_API void +htrdr_buffer_ref_put + (struct htrdr_buffer* buf); + +HTRDR_CORE_API void +htrdr_buffer_get_layout + (const struct htrdr_buffer* buf, + struct htrdr_buffer_layout* layout); + +HTRDR_CORE_API void* +htrdr_buffer_get_data + (struct htrdr_buffer* buf); + +HTRDR_CORE_API void* +htrdr_buffer_at + (struct htrdr_buffer* buf, + const size_t x, + const size_t y); + +END_DECLS + +#endif /* HTRDR_BUFFER_H */ diff --git a/src/core/htrdr_c.h b/src/core/htrdr_c.h @@ -0,0 +1,116 @@ +/* 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_C_H +#define HTRDR_C_H + +#include <rsys/logger.h> +#include <rsys/ref_count.h> +#include <rsys/rsys.h> + +#ifndef NDEBUG + #define MPI(Func) ASSERT(MPI_##Func == MPI_SUCCESS) +#else + #define MPI(Func) MPI_##Func +#endif + +enum htrdr_mpi_message { + HTRDR_MPI_PROGRESS_RENDERING, + HTRDR_MPI_STEAL_REQUEST, + HTRDR_MPI_WORK_STEALING, + HTRDR_MPI_TILE_DATA +}; + +struct s3d_device; + +struct htrdr { + struct s3d_device* s3d; + + unsigned nthreads; /* #threads of the process */ + + int mpi_rank; /* Rank of the process in the MPI group */ + int mpi_nprocs; /* Overall #processes in the MPI group */ + char* mpi_err_str; /* Temp buffer used to store MPI error string */ + int8_t* mpi_working_procs; /* Define the rank of active processes */ + size_t mpi_nworking_procs; + + /* Process progress percentage */ + int32_t* mpi_progress_octree; + int32_t* mpi_progress_render; + + struct mutex* mpi_mutex; /* Protect MPI calls from concurrent threads */ + + int verbose; + + struct logger logger; + struct mem_allocator* allocator; + struct mem_allocator* lifo_allocators; /* Per thread lifo allocator */ + + ref_T ref; +}; + +extern LOCAL_SYM void +setup_logger + (struct htrdr* htrdr); + +/* Return the minimum length in nanometer of the sky spectral bands + * clamped to in [range[0], range[1]]. */ +extern LOCAL_SYM void +send_mpi_progress + (struct htrdr* htrdr, + const enum htrdr_mpi_message progress, + const int32_t percent); + +extern LOCAL_SYM void +fetch_mpi_progress + (struct htrdr* htrdr, + const enum htrdr_mpi_message progress); + +extern LOCAL_SYM void +print_mpi_progress + (struct htrdr* htrdr, + const enum htrdr_mpi_message progress); + +extern LOCAL_SYM void +clear_mpi_progress + (struct htrdr* htrdr, + const enum htrdr_mpi_message progress); + +extern int32_t +total_mpi_progress + (const struct htrdr* htrdr, + const enum htrdr_mpi_message progress); + +static INLINE void +update_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message progress) +{ + ASSERT(htrdr); + fetch_mpi_progress(htrdr, progress); + clear_mpi_progress(htrdr, progress); + print_mpi_progress(htrdr, progress); +} + +static FINLINE int +cmp_dbl(const void* a, const void* b) +{ + const double d0 = *((const double*)a); + const double d1 = *((const double*)b); + return d0 < d1 ? -1 : (d0 > d1 ? 1 : 0); +} + +#endif /* HTRDR_C_H */ + diff --git a/src/core/htrdr_camera.c b/src/core/htrdr_camera.c @@ -0,0 +1,157 @@ +/* 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 "core/htrdr.h" +#include "core/htrdr_camera.h" +#include "core/htrdr_log.h" + +#include <rsys/double3.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> + +struct htrdr_camera { + /* Orthogonal basis of the camera */ + double axis_x[3]; + double axis_y[3]; + double axis_z[3]; + + double position[3]; + + ref_T ref; + struct htrdr* htrdr; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +camera_release(ref_T* ref) +{ + struct htrdr_camera* cam; + struct htrdr* htrdr; + ASSERT(ref); + cam = CONTAINER_OF(ref, struct htrdr_camera, ref); + htrdr = cam->htrdr; + MEM_RM(htrdr_get_allocator(htrdr), cam); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_camera_create + (struct htrdr* htrdr, + const double position[3], + const double target[3], + const double up[3], + const double proj_ratio, + const double fov, /* In radian */ + struct htrdr_camera** out_cam) +{ + double x[3], y[3], z[3]; + double img_plane_depth; + struct htrdr_camera* cam = NULL; + res_T res = RES_OK; + ASSERT(htrdr && position && target && up && out_cam); + + cam = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*cam)); + if(!cam) { + htrdr_log_err(htrdr, "could not allocate the camera data structure.\n"); + res = RES_MEM_ERR; + goto error; + } + ref_init(&cam->ref); + htrdr_ref_get(htrdr); + cam->htrdr = htrdr; + + if(fov <= 0 || fov >= PI) { + htrdr_log_err(htrdr, "invalid horizontal camera field of view `%g'\n", fov); + res = RES_BAD_ARG; + goto error; + } + + if(proj_ratio <= 0) { + htrdr_log_err(htrdr, "invalid projection ratio `%g'\n", proj_ratio); + res = RES_BAD_ARG; + goto error; + } + + if(d3_normalize(z, d3_sub(z, target, position)) <= 0 + || d3_normalize(x, d3_cross(x, z, up)) <= 0 + || d3_normalize(y, d3_cross(y, z, x)) <= 0) { + htrdr_log_err(htrdr, + "invalid camera point of view:\n" + " position = %g %g %g\n" + " target = %g %g %g\n" + " up = %g %g %g\n", + SPLIT3(position), SPLIT3(target), SPLIT3(up)); + res = RES_BAD_ARG; + goto error; + } + + img_plane_depth = 1.0/tan(fov*0.5); + d3_muld(cam->axis_x, x, proj_ratio); + d3_set(cam->axis_y, y); + d3_muld(cam->axis_z, z, img_plane_depth); + d3_set(cam->position, position); + +exit: + *out_cam = cam; + return res; +error: + if(cam) { + htrdr_camera_ref_put(cam); + cam = NULL; + } + goto exit; +} + +void +htrdr_camera_ref_get(struct htrdr_camera* cam) +{ + ASSERT(cam); + ref_get(&cam->ref); +} + +void +htrdr_camera_ref_put(struct htrdr_camera* cam) +{ + ASSERT(cam); + ref_put(&cam->ref, camera_release); +} + +void +htrdr_camera_ray + (const struct htrdr_camera* cam, + const double sample[2], + double ray_org[3], + double ray_dir[3]) +{ + double x[3], y[3], len; + (void)len; + ASSERT(cam && sample && ray_org && ray_dir); + ASSERT(sample[0] >= 0 || sample[0] < 1); + ASSERT(sample[1] >= 0 || sample[1] < 1); + d3_muld(x, cam->axis_x, sample[0]*2-1); + d3_muld(y, cam->axis_y, sample[1]*2-1); + d3_add(ray_dir, d3_add(ray_dir, x, y), cam->axis_z); + len = d3_normalize(ray_dir, ray_dir); + ASSERT(len >= 1.e-6); + d3_set(ray_org, cam->position); +} + diff --git a/src/core/htrdr_camera.h b/src/core/htrdr_camera.h @@ -0,0 +1,58 @@ +/* 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_CAMERA_H +#define HTRDR_CAMERA_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +/* Forward declarations */ +struct htrdr; +struct htrdr_camera; + +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_camera_create + (struct htrdr* htrdr, + const double position[3], + const double target[3], + const double up[3], + const double proj_ratio, /* Width / Height */ + const double fov, /* In radian */ + struct htrdr_camera** cam); + +HTRDR_CORE_API void +htrdr_camera_ref_get + (struct htrdr_camera* cam); + +HTRDR_CORE_API void +htrdr_camera_ref_put + (struct htrdr_camera* cam); + +HTRDR_CORE_API void +htrdr_camera_ray + (const struct htrdr_camera* cam, + const double sample[2], /* In [0, 1[ */ + double ray_org[3], + double ray_dir[3]); + +END_DECLS + +#endif /* HTRDR_CAMERA_H */ + diff --git a/src/core/htrdr_cie_xyz.c b/src/core/htrdr_cie_xyz.c @@ -0,0 +1,391 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#define _POSIX_C_SOURCE 200112L /* nextafter */ + +#include "core/htrdr.h" +#include "core/htrdr_c.h" +#include "core/htrdr_cie_xyz.h" +#include "core/htrdr_log.h" + +#include <high_tune/htsky.h> + +#include <rsys/algorithm.h> +#include <rsys/cstr.h> +#include <rsys/dynamic_array_double.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> + +#include <math.h> /* nextafter */ + +struct htrdr_cie_xyz { + struct darray_double cdf_X; + struct darray_double cdf_Y; + struct darray_double cdf_Z; + double rcp_integral_X; + double rcp_integral_Y; + double rcp_integral_Z; + double range[2]; /* Boundaries of the handled CIE XYZ color space */ + double band_len; /* Length in nanometers of a band */ + + struct htrdr* htrdr; + ref_T ref; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE double +trapezoidal_integration + (const double lambda_lo, /* Integral lower bound. In nanometer */ + const double lambda_hi, /* Integral upper bound. In nanometer */ + double (*f_bar)(const double lambda)) /* Function to integrate */ +{ + double dlambda; + size_t i, n; + double integral = 0; + ASSERT(lambda_lo <= lambda_hi); + ASSERT(lambda_lo > 0); + + n = (size_t)(lambda_hi - lambda_lo) + 1; + dlambda = (lambda_hi - lambda_lo) / (double)n; + + FOR_EACH(i, 0, n) { + const double lambda1 = lambda_lo + dlambda*(double)(i+0); + const double lambda2 = lambda_lo + dlambda*(double)(i+1); + const double f1 = f_bar(lambda1); + const double f2 = f_bar(lambda2); + integral += (f1 + f2)*dlambda*0.5; + } + return integral; +} + +/* The following 3 functions are used to fit the CIE Xbar, Ybar and Zbar curved + * has defined by the 1931 standard. These analytical fits are propsed by C. + * Wyman, P. P. Sloan & P. Shirley in "Simple Analytic Approximations to the + * CIE XYZ Color Matching Functions" - JCGT 2013. */ +static INLINE double +fit_x_bar_1931(const double lambda) +{ + const double a = (lambda - 442.0) * (lambda < 442.0 ? 0.0624 : 0.0374); + const double b = (lambda - 599.8) * (lambda < 599.8 ? 0.0264 : 0.0323); + const double c = (lambda - 501.1) * (lambda < 501.1 ? 0.0490 : 0.0382); + return 0.362*exp(-0.5*a*a) + 1.056*exp(-0.5f*b*b) - 0.065*exp(-0.5*c*c); +} + +static FINLINE double +fit_y_bar_1931(const double lambda) +{ + const double a = (lambda - 568.8) * (lambda < 568.8 ? 0.0213 : 0.0247); + const double b = (lambda - 530.9) * (lambda < 530.9 ? 0.0613 : 0.0322); + return 0.821*exp(-0.5*a*a) + 0.286*exp(-0.5*b*b); +} + +static FINLINE double +fit_z_bar_1931(const double lambda) +{ + const double a = (lambda - 437.0) * (lambda < 437.0 ? 0.0845 : 0.0278); + const double b = (lambda - 459.0) * (lambda < 459.0 ? 0.0385 : 0.0725); + return 1.217*exp(-0.5*a*a) + 0.681*exp(-0.5*b*b); +} + +static INLINE double +sample_cie_xyz + (const struct htrdr_cie_xyz* cie, + const double* cdf, + const size_t cdf_length, + double (*f_bar)(const double lambda), /* Function to integrate */ + const double r0, /* Canonical number in [0, 1[ */ + const double r1) /* Canonical number in [0, 1[ */ +{ + double r0_next = nextafter(r0, DBL_MAX); + double* find; + double f_min, f_max; /* CIE 1931 value for the band boundaries */ + double lambda; /* Sampled wavelength */ + double lambda_min, lambda_max; /* Boundaries of the sampled band */ + double lambda_1, lambda_2; /* Solutions if the equation to solve */ + double a, b, c, d; /* Equation parameters */ + double delta, sqrt_delta; + size_t iband; /* Index of the sampled band */ + ASSERT(cie && cdf && cdf_length); + ASSERT(0 <= r0 && r0 < 1); + ASSERT(0 <= r1 && r1 < 1); + + /* Use r_next rather than r in order to find the first entry that is not less + * than *or equal* to r */ + find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl); + ASSERT(find); + + /* Define and check the sampled band */ + iband = (size_t)(find - cdf); + ASSERT(iband < cdf_length); + ASSERT(cdf[iband] > r0 && (!iband || cdf[iband-1] <= r0)); + + /* Define the boundaries of the sampled band */ + lambda_min = cie->range[0] + cie->band_len * (double)iband; + lambda_max = lambda_min + cie->band_len; + + /* Define the value of the CIE 1931 function for the boudaries of the sampled + * band */ + f_min = f_bar(lambda_min); + f_max = f_bar(lambda_max); + + /* Compute the equation constants */ + a = 0.5 * (f_max - f_min) / cie->band_len; + b = (lambda_max * f_min - lambda_min * f_max) / cie->band_len; + c = -lambda_min * f_min + lambda_min*lambda_min * a; + d = 0.5 * (f_max + f_min) * cie->band_len; + + delta = b*b - 4*a*(c-d*r1); + if(delta < 0 && eq_eps(delta, 0, 1.e-6)) { + delta = 0; + } + ASSERT(delta > 0); + sqrt_delta = sqrt(delta); + + /* Compute the roots that solve the equation */ + lambda_1 = (-b - sqrt_delta) / (2*a); + lambda_2 = (-b + sqrt_delta) / (2*a); + + /* Select the solution */ + if(lambda_min <= lambda_1 && lambda_1 < lambda_max) { + lambda = lambda_1; + } else if(lambda_min <= lambda_2 && lambda_2 < lambda_max) { + lambda = lambda_2; + } else { + htrdr_log_warn(cie->htrdr, + "%s: cannot sample a wavelength in [%g, %g[. The possible wavelengths" + "were %g and %g.\n", + FUNC_NAME, lambda_min, lambda_max, lambda_1, lambda_2); + /* Arbitrarly choose the wavelength at the center of the sampled band */ + lambda = (lambda_min + lambda_max)*0.5; + } + + return lambda; +} + +static res_T +setup_cie_xyz + (struct htrdr_cie_xyz* cie, + const char* func_name, + const size_t nbands) +{ + enum { X, Y, Z }; /* Helper constant */ + double* pdf[3] = {NULL, NULL, NULL}; + double* cdf[3] = {NULL, NULL, NULL}; + double sum[3] = {0, 0, 0}; + size_t i; + res_T res = RES_OK; + + ASSERT(cie && func_name && nbands); + ASSERT(cie->range[0] >= HTRDR_CIE_XYZ_RANGE_DEFAULT[0]); + ASSERT(cie->range[1] <= HTRDR_CIE_XYZ_RANGE_DEFAULT[1]); + ASSERT(cie->range[0] < cie->range[1]); + + /* Allocate and reset the memory space for the tristimulus CDF */ + #define SETUP_STIMULUS(Stimulus) { \ + res = darray_double_resize(&cie->cdf_ ## Stimulus, nbands); \ + if(res != RES_OK) { \ + htrdr_log_err(cie->htrdr, \ + "%s: Could not reserve the memory space for the CDF " \ + "of the "STR(X)" stimulus -- %s.\n", func_name, res_to_cstr(res)); \ + goto error; \ + } \ + cdf[Stimulus] = darray_double_data_get(&cie->cdf_ ## Stimulus); \ + pdf[Stimulus] = cdf[Stimulus]; \ + memset(cdf[Stimulus], 0, nbands*sizeof(double)); \ + } (void)0 + SETUP_STIMULUS(X); + SETUP_STIMULUS(Y); + SETUP_STIMULUS(Z); + #undef SETUP_STIMULUS + + /* Compute the *unormalized* pdf of the tristimulus */ + FOR_EACH(i, 0, nbands) { + const double lambda_lo = cie->range[0] + (double)i * cie->band_len; + const double lambda_hi = MMIN(lambda_lo + cie->band_len, cie->range[1]); + ASSERT(lambda_lo <= lambda_hi); + ASSERT(lambda_lo >= cie->range[0]); + ASSERT(lambda_hi <= cie->range[1]); + pdf[X][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_x_bar_1931); + pdf[Y][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_y_bar_1931); + pdf[Z][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_z_bar_1931); + sum[X] += pdf[X][i]; + sum[Y] += pdf[Y][i]; + sum[Z] += pdf[Z][i]; + } + #define CHK_SUM(Sum, Range, Fit) \ + ASSERT(eq_eps(Sum, trapezoidal_integration(Range[0], Range[1], Fit), 1.e-3)) + CHK_SUM(sum[X], cie->range, fit_x_bar_1931); + CHK_SUM(sum[Y], cie->range, fit_y_bar_1931); + CHK_SUM(sum[Z], cie->range, fit_z_bar_1931); + #undef CHK_SUM + cie->rcp_integral_X = 1.0 / sum[X]; + cie->rcp_integral_Y = 1.0 / sum[Y]; + cie->rcp_integral_Z = 1.0 / sum[Z]; + + FOR_EACH(i, 0, nbands) { + /* Normalize the pdf */ + pdf[X][i] /= sum[X]; + pdf[Y][i] /= sum[Y]; + pdf[Z][i] /= sum[Z]; + /* Setup the cumulative */ + if(i == 0) { + cdf[X][i] = pdf[X][i]; + cdf[Y][i] = pdf[Y][i]; + cdf[Z][i] = pdf[Z][i]; + } else { + cdf[X][i] = pdf[X][i] + cdf[X][i-1]; + cdf[Y][i] = pdf[Y][i] + cdf[Y][i-1]; + cdf[Z][i] = pdf[Z][i] + cdf[Z][i-1]; + ASSERT(cdf[X][i] >= cdf[X][i-1]); + ASSERT(cdf[Y][i] >= cdf[Y][i-1]); + ASSERT(cdf[Z][i] >= cdf[Z][i-1]); + } + } + ASSERT(eq_eps(cdf[X][nbands-1], 1, 1.e-6)); + ASSERT(eq_eps(cdf[Y][nbands-1], 1, 1.e-6)); + ASSERT(eq_eps(cdf[Z][nbands-1], 1, 1.e-6)); + + /* Handle numerical issue */ + cdf[X][nbands-1] = 1.0; + cdf[Y][nbands-1] = 1.0; + cdf[Z][nbands-1] = 1.0; + +exit: + return res; +error: + darray_double_clear(&cie->cdf_X); + darray_double_clear(&cie->cdf_Y); + darray_double_clear(&cie->cdf_Z); + goto exit; +} + +static void +release_cie_xyz(ref_T* ref) +{ + struct htrdr_cie_xyz* cie = NULL; + struct htrdr* htrdr = NULL; + ASSERT(ref); + cie = CONTAINER_OF(ref, struct htrdr_cie_xyz, ref); + darray_double_release(&cie->cdf_X); + darray_double_release(&cie->cdf_Y); + darray_double_release(&cie->cdf_Z); + htrdr = cie->htrdr; + MEM_RM(htrdr_get_allocator(cie->htrdr), cie); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_cie_xyz_create + (struct htrdr* htrdr, + const double range[2], /* Must be included in [380, 780] nanometers */ + const size_t bands_count, /* # bands used to discretize the CIE tristimulus */ + struct htrdr_cie_xyz** out_cie) +{ + struct htrdr_cie_xyz* cie = NULL; + size_t nbands = bands_count; + res_T res = RES_OK; + ASSERT(htrdr && range && nbands && out_cie); + + cie = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*cie)); + if(!cie) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "%s: could not allocate the CIE XYZ data structure -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + ref_init(&cie->ref); + darray_double_init(htrdr_get_allocator(htrdr), &cie->cdf_X); + darray_double_init(htrdr_get_allocator(htrdr), &cie->cdf_Y); + darray_double_init(htrdr_get_allocator(htrdr), &cie->cdf_Z); + cie->range[0] = range[0]; + cie->range[1] = range[1]; + htrdr_ref_get(htrdr); + cie->htrdr = htrdr; + + cie->band_len = (range[1] - range[0]) / (double)nbands; + + res = setup_cie_xyz(cie, FUNC_NAME, nbands); + if(res != RES_OK) goto error; + + htrdr_log(htrdr, "CIE XYZ spectral interval defined on [%g, %g] nanometers.\n", + range[0], range[1]); + +exit: + *out_cie = cie; + return res; +error: + if(cie) htrdr_cie_xyz_ref_put(cie); + goto exit; +} + +void +htrdr_cie_xyz_ref_get(struct htrdr_cie_xyz* cie) +{ + ASSERT(cie); + ref_get(&cie->ref); +} + +void +htrdr_cie_xyz_ref_put(struct htrdr_cie_xyz* cie) +{ + ASSERT(cie); + ref_put(&cie->ref, release_cie_xyz); +} + +double +htrdr_cie_xyz_sample_X + (struct htrdr_cie_xyz* cie, + const double r0, + const double r1, + double* pdf) +{ + const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_X), + darray_double_size_get(&cie->cdf_X), fit_x_bar_1931, r0, r1); + if(pdf) *pdf = cie->rcp_integral_X; + return wlen; +} + +double +htrdr_cie_xyz_sample_Y + (struct htrdr_cie_xyz* cie, + const double r0, + const double r1, + double* pdf) +{ + const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Y), + darray_double_size_get(&cie->cdf_Y), fit_y_bar_1931, r0, r1); + if(pdf) *pdf = cie->rcp_integral_Y; + return wlen; +} + +double +htrdr_cie_xyz_sample_Z + (struct htrdr_cie_xyz* cie, + const double r0, + const double r1, + double* pdf) +{ + const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Z), + darray_double_size_get(&cie->cdf_Z), fit_z_bar_1931, r0, r1); + if(pdf) *pdf = cie->rcp_integral_Z; + return wlen; +} + diff --git a/src/core/htrdr_cie_xyz.h b/src/core/htrdr_cie_xyz.h @@ -0,0 +1,72 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef HTRDR_CIE_XYZ_H +#define HTRDR_CIE_XYZ_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +/* Wavelength boundaries of the CIE XYZ color space in nanometers */ +#define HTRDR_CIE_XYZ_RANGE_DEFAULT__ {380, 780} +static const double HTRDR_CIE_XYZ_RANGE_DEFAULT[2] = + HTRDR_CIE_XYZ_RANGE_DEFAULT__; + +/* Forward declarations */ +struct htrdr; +struct htrdr_cie_xyz; + +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_cie_xyz_create + (struct htrdr* htrdr, + const double range[2], /* Must be included in [380, 780] nanometers */ + const size_t nbands, /* # bands used to discretisze the CIE tristimulus s*/ + struct htrdr_cie_xyz** cie); + +HTRDR_CORE_API void +htrdr_cie_xyz_ref_get + (struct htrdr_cie_xyz* cie); + +HTRDR_CORE_API void +htrdr_cie_xyz_ref_put + (struct htrdr_cie_xyz* cie); + +/* Return a wavelength in nanometer */ +HTRDR_CORE_API double +htrdr_cie_xyz_sample_X + (struct htrdr_cie_xyz* cie, + const double r0, const double r1, /* Canonical numbers in [0, 1[ */ + double* pdf); /* In nm^-1. May be NULL */ + +/* Return a wavelength in nanometer */ +HTRDR_CORE_API double +htrdr_cie_xyz_sample_Y + (struct htrdr_cie_xyz* cie, + const double r0, const double r1, /* Canonical number in [0, 1[ */ + double* pdf); /* In nm^-1. May be NULL */ + +/* Return a wavelength in nanometer */ +HTRDR_CORE_API double +htrdr_cie_xyz_sample_Z + (struct htrdr_cie_xyz* cie, + const double r0, const double r1, /* Canonical number in [0, 1[ */ + double* pdf); /* In nm^-1. May be NULL */ + +END_DECLS + +#endif /* HTRDR_cie_xyz_H */ + diff --git a/src/core/htrdr_draw_map.c b/src/core/htrdr_draw_map.c @@ -0,0 +1,815 @@ +/* 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 /* nanosleep */ + +#include "core/htrdr.h" +#include "core/htrdr_c.h" +#include "core/htrdr_buffer.h" +#include "core/htrdr_draw_map.h" +#include "core/htrdr_log.h" + +#include <rsys/clock_time.h> +#include <rsys/cstr.h> +#include <rsys/dynamic_array_u32.h> +#include <rsys/list.h> +#include <rsys/math.h> +#include <rsys/morton.h> +#include <rsys/mutex.h> +#include <rsys/ref_count.h> + +#include <star/ssp.h> + +#include <omp.h> +#include <mpi.h> +#include <time.h> +#include <unistd.h> + +#define RNG_SEQUENCE_SIZE 10000 + +#define TILE_MCODE_NULL UINT32_MAX +#define TILE_SIZE 32 /* Definition in X & Y of a tile */ +STATIC_ASSERT(IS_POW2(TILE_SIZE), TILE_SIZE_must_be_a_power_of_2); + +/* Tile of row ordered image pixels */ +struct tile { + struct list_node node; + struct mem_allocator* allocator; + ref_T ref; + + struct tile_data { + size_t pixsz; /* Sizeof on pixel */ + size_t pixal; /* Pixel alignment */ + uint16_t x, y; /* 2D coordinates of the tile in tile space */ + /* Simulate the flexible array member of the C99 standard */ + char ALIGN(16) pixels[1/*Dummy element*/]; + } data; +}; + +/* List of tile to compute onto the MPI process. */ +struct proc_work { + struct mutex* mutex; + struct darray_u32 tiles; /* #tiles to render */ + size_t itile; /* Next tile to render in the above list of tiles */ +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE int +check_draw_map_args(const struct htrdr_draw_map_args* args) +{ + return args + && args->draw_pixel + && args->spp + && htrdr_buffer_layout_check(&args->buffer_layout); +} + +static INLINE void +tile_ref_get(struct tile* tile) +{ + ASSERT(tile); + tile_ref_get(tile); +} + +static INLINE void +release_tile(ref_T* ref) +{ + struct tile* tile = CONTAINER_OF(ref, struct tile, ref); + ASSERT(ref); + MEM_RM(tile->allocator, tile); +} + +static INLINE void +tile_ref_put(struct tile* tile) +{ + ASSERT(tile); + ref_put(&tile->ref, release_tile); +} + +static FINLINE struct tile* +tile_create + (struct mem_allocator* allocator, + const size_t pixel_size, + const size_t pixel_alignment) +{ + struct tile* tile = NULL; + const size_t tile_sz = sizeof(*tile) - 1/* rm dummy octet in flexible array */; + const size_t buf_sz = TILE_SIZE*TILE_SIZE*pixel_size; + ASSERT(allocator); + ASSERT(IS_ALIGNED(pixel_size, pixel_alignment)); + ASSERT(IS_POW2(pixel_alignment)); + + tile = MEM_ALLOC_ALIGNED(allocator, tile_sz + buf_sz, 16); + if(!tile) goto error; + ref_init(&tile->ref); + list_init(&tile->node); + tile->allocator = allocator; + tile->data.pixsz = pixel_size; + tile->data.pixal = pixel_alignment; + tile->data.x = 0; + tile->data.y = 0; + CHK(IS_ALIGNED(tile->data.pixels, pixel_alignment)); + +exit: + return tile; +error: + if(tile) { + tile_ref_put(tile); + tile = NULL; + } + goto exit; +} + +static FINLINE void* +tile_at + (struct tile* tile, + const size_t x, /* In tile space */ + const size_t y) /* In tile space */ +{ + ASSERT(tile && x < TILE_SIZE && y < TILE_SIZE); + return (void*)(tile->data.pixels + (y*TILE_SIZE + x)*tile->data.pixsz); +} + +static void +write_tile_data + (struct htrdr* htrdr, + struct htrdr_buffer* buf, + const struct tile_data* tile_data) +{ + struct htrdr_buffer_layout layout = HTRDR_BUFFER_LAYOUT_NULL; + size_t icol, irow; + size_t irow_tile; + size_t ncols_tile, nrows_tile; + size_t tile_pitch; + char* buf_mem; + ASSERT(htrdr && buf && tile_data); + (void)htrdr; + + htrdr_buffer_get_layout(buf, &layout); + buf_mem = htrdr_buffer_get_data(buf); + ASSERT(layout.elmt_size == tile_data->pixsz); + + /* Compute the row/column of the tile origin into the buffer */ + icol = tile_data->x * (size_t)TILE_SIZE; + irow = tile_data->y * (size_t)TILE_SIZE; + + /* Define the number of tile row/columns to write into the buffer */ + ncols_tile = MMIN(icol + TILE_SIZE, layout.width) - icol; + nrows_tile = MMIN(irow + TILE_SIZE, layout.height) - irow; + + tile_pitch = TILE_SIZE * tile_data->pixsz; + + /* Copy the row ordered tile data */ + FOR_EACH(irow_tile, 0, nrows_tile) { + char* buf_row = buf_mem + (irow + irow_tile) * layout.pitch; + char* dst_tile_row = buf_row + icol * layout.elmt_size; + const char* src_tile_row = tile_data->pixels + irow_tile*tile_pitch; + + memcpy(dst_tile_row, src_tile_row, ncols_tile*tile_data->pixsz); + } +} + +static INLINE void +proc_work_init(struct mem_allocator* allocator, struct proc_work* work) +{ + ASSERT(work); + darray_u32_init(allocator, &work->tiles); + work->itile = 0; + CHK(work->mutex = mutex_create()); +} + +static INLINE void +proc_work_release(struct proc_work* work) +{ + darray_u32_release(&work->tiles); + mutex_destroy(work->mutex); +} + +static INLINE void +proc_work_reset(struct proc_work* work) +{ + ASSERT(work); + mutex_lock(work->mutex); + darray_u32_clear(&work->tiles); + work->itile = 0; + mutex_unlock(work->mutex); +} + +static INLINE void +proc_work_add_tile(struct proc_work* work, const uint32_t mcode) +{ + mutex_lock(work->mutex); + CHK(darray_u32_push_back(&work->tiles, &mcode) == RES_OK); + mutex_unlock(work->mutex); +} + +static INLINE uint32_t +proc_work_get_tile(struct proc_work* work) +{ + uint32_t mcode; + ASSERT(work); + mutex_lock(work->mutex); + if(work->itile >= darray_u32_size_get(&work->tiles)) { + mcode = TILE_MCODE_NULL; + } else { + mcode = darray_u32_cdata_get(&work->tiles)[work->itile]; + ++work->itile; + } + mutex_unlock(work->mutex); + return mcode; +} + +static INLINE size_t +proc_work_get_ntiles(struct proc_work* work) +{ + size_t sz = 0; + ASSERT(work); + mutex_lock(work->mutex); + sz = darray_u32_size_get(&work->tiles); + mutex_unlock(work->mutex); + return sz; +} + +static void +mpi_wait_for_request(struct htrdr* htrdr, MPI_Request* req) +{ + ASSERT(htrdr && req); + + /* Wait for process synchronisation */ + for(;;) { + struct timespec t; + int complete; + t.tv_sec = 0; + t.tv_nsec = 10000000; /* 10ms */ + + mutex_lock(htrdr->mpi_mutex); + MPI(Test(req, &complete, MPI_STATUS_IGNORE)); + mutex_unlock(htrdr->mpi_mutex); + if(complete) break; + + nanosleep(&t, NULL); + } +} + +static void +mpi_probe_thieves + (struct htrdr* htrdr, + struct proc_work* work, + ATOMIC* probe_thieves) +{ + uint32_t tiles[UINT8_MAX]; + struct timespec t; + ASSERT(htrdr && work && probe_thieves); + + if(htrdr->mpi_nprocs == 1) /* The process is alone. No thief is possible */ + return; + + t.tv_sec = 0; + + /* Protect MPI calls of multiple invocations from concurrent threads */ + #define P_MPI(Func) { \ + mutex_lock(htrdr->mpi_mutex); \ + MPI(Func); \ + mutex_unlock(htrdr->mpi_mutex); \ + } (void)0 + + while(ATOMIC_GET(probe_thieves)) { + MPI_Status status; + size_t itile; + int msg; + + /* Probe if a steal request was submitted by any processes */ + P_MPI(Iprobe(MPI_ANY_SOURCE, HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD, &msg, + &status)); + + if(msg) { /* A steal request was posted */ + MPI_Request req; + uint8_t ntiles_to_steal; + + /* Asynchronously receive the steal request */ + P_MPI(Irecv(&ntiles_to_steal, 1, MPI_UINT8_T, status.MPI_SOURCE, + HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD, &req)); + + /* Wait for the completion of the steal request */ + mpi_wait_for_request(htrdr, &req); + + /* Thief some tiles */ + FOR_EACH(itile, 0, ntiles_to_steal) { + tiles[itile] = proc_work_get_tile(work); + } + P_MPI(Send(&tiles, ntiles_to_steal, MPI_UINT32_T, status.MPI_SOURCE, + HTRDR_MPI_WORK_STEALING, MPI_COMM_WORLD)); + } + t.tv_nsec = 500000000; /* 500ms */ + nanosleep(&t, NULL); + } + #undef P_MPI +} + +static int +mpi_sample_working_process(struct htrdr* htrdr, struct ssp_rng* rng) +{ + int iproc, i; + int dst_rank; + ASSERT(htrdr && rng && htrdr->mpi_nworking_procs); + + /* Sample the index of the 1st active process */ + iproc = (int)(ssp_rng_canonical(rng) * (double)htrdr->mpi_nworking_procs); + + /* Find the rank of the sampled active process. Use a simple linear search + * since the overall number of processes should be quite low; at most few + * dozens. */ + i = 0; + FOR_EACH(dst_rank, 0, htrdr->mpi_nprocs) { + if(htrdr->mpi_working_procs[dst_rank] == 0) continue; /* Inactive process */ + if(i == iproc) break; /* The rank of the sampled process is found */ + ++i; + } + ASSERT(dst_rank < htrdr->mpi_nprocs); + return dst_rank; +} + +/* Return the number of stolen tiles */ +static size_t +mpi_steal_work + (struct htrdr* htrdr, + struct ssp_rng* rng, + struct proc_work* work) +{ + MPI_Request req; + size_t itile; + size_t nthieves = 0; + uint32_t tiles[UINT8_MAX]; /* Morton code of the stolen tile */ + int proc_to_steal; /* Process to steal */ + uint8_t ntiles_to_steal = MMIN((uint8_t)(htrdr->nthreads*2), 16); + ASSERT(htrdr && rng && work && htrdr->nthreads < UINT8_MAX); + + /* Protect MPI calls of multiple invocations from concurrent threads */ + #define P_MPI(Func) { \ + mutex_lock(htrdr->mpi_mutex); \ + MPI(Func); \ + mutex_unlock(htrdr->mpi_mutex); \ + } (void)0 + + /* No more working process => nohting to steal */ + if(!htrdr->mpi_nworking_procs) return 0; + + /* Sample a process to steal */ + proc_to_steal = mpi_sample_working_process(htrdr, rng); + + /* Send a steal request to the sampled process and wait for a response */ + P_MPI(Send(&ntiles_to_steal, 1, MPI_UINT8_T, proc_to_steal, + HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD)); + + /* Receive the stolen tile from the sampled process */ + P_MPI(Irecv(tiles, ntiles_to_steal, MPI_UINT32_T, proc_to_steal, + HTRDR_MPI_WORK_STEALING, MPI_COMM_WORLD, &req)); + + mpi_wait_for_request(htrdr, &req); + + FOR_EACH(itile, 0, ntiles_to_steal) { + if(tiles[itile] == TILE_MCODE_NULL) { + ASSERT(htrdr->mpi_working_procs[proc_to_steal] != 0); + htrdr->mpi_working_procs[proc_to_steal] = 0; + htrdr->mpi_nworking_procs--; + break; + } + proc_work_add_tile(work, tiles[itile]); + ++nthieves; + } + #undef P_MPI + return nthieves; +} + +static res_T +mpi_gather_tiles + (struct htrdr* htrdr, + const struct htrdr_buffer_layout* buf_layout, + struct htrdr_buffer* buf, + const size_t ntiles, + struct list_node* tiles) +{ + /* Compute the size of the tile_data */ + size_t msg_sz = 0; + struct list_node* node = NULL; + struct tile* tile = NULL; + res_T res = RES_OK; + ASSERT(htrdr && tiles && htrdr_buffer_layout_check(buf_layout)); + ASSERT(htrdr->mpi_rank != 0 || buf); + (void)ntiles; + + /* Compute the size of the tile data */ + msg_sz = + sizeof(struct tile_data) + + TILE_SIZE*TILE_SIZE*buf_layout->elmt_size + - 1/* Dummy octet of the flexible array */; + ASSERT(msg_sz <= INT_MAX); + + if(htrdr->mpi_rank != 0) { /* Non master process */ + /* Send the computed tile to the master process */ + LIST_FOR_EACH(node, tiles) { + struct tile* t = CONTAINER_OF(node, struct tile, node); + MPI(Send(&t->data, (int)msg_sz, MPI_CHAR, 0, + HTRDR_MPI_TILE_DATA, MPI_COMM_WORLD)); + } + } else { /* Master process */ + size_t itile = 0; + ASSERT(buf); + +#ifndef NDEBUG + { + /* Check data consistency */ + struct htrdr_buffer_layout layout = HTRDR_BUFFER_LAYOUT_NULL; + htrdr_buffer_get_layout(buf, &layout); + ASSERT(htrdr_buffer_layout_eq(&layout, buf_layout)); + } +#endif + + LIST_FOR_EACH(node, tiles) { + struct tile* t = CONTAINER_OF(node, struct tile, node); + write_tile_data(htrdr, buf, &t->data); + ++itile; + } + + if(itile != ntiles) { + ASSERT(htrdr->mpi_nprocs > 1); + + /* Create a temporary tile to receive the tile data computed by the + * concurrent MPI processes */ + tile = tile_create + (htrdr->allocator, + buf_layout->elmt_size, + buf_layout->alignment); + if(!tile) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "Could not allocate the temporary tile used to gather the process " + "output data -- %s.\n", res_to_cstr(res)); + goto error; + } + + /* Receive the tile data of the concurrent MPI processes */ + FOR_EACH(itile, itile, ntiles) { + MPI(Recv(&tile->data, (int)msg_sz, MPI_CHAR, MPI_ANY_SOURCE, + HTRDR_MPI_TILE_DATA, MPI_COMM_WORLD, MPI_STATUS_IGNORE)); + write_tile_data(htrdr, buf, &tile->data); + } + } + } + +exit: + if(tile) tile_ref_put(tile); + return res; +error: + goto exit; +} + +static res_T +draw_tile + (struct htrdr* htrdr, + const struct htrdr_draw_map_args* args, + const size_t ithread, + const int64_t tile_mcode, /* For debug only */ + const uint16_t tile_org[2], /* Origin of the tile in pixel space */ + const size_t tile_sz[2], /* Definition of the tile */ + const double pix_sz[2], /* Size of a pixel in the normalized image plane */ + struct ssp_rng* rng, + struct tile* tile) +{ + struct htrdr_draw_pixel_args pix_args = HTRDR_DRAW_PIXEL_ARGS_NULL; + size_t npixels; + size_t mcode; /* Morton code of tile pixel */ + ASSERT(htrdr && tile_org && tile_sz && pix_sz && rng && tile); + ASSERT(check_draw_map_args(args)); + (void)tile_mcode; + + /* Adjust the #pixels to process them wrt a morton order */ + npixels = round_up_pow2(MMAX(tile_sz[0], tile_sz[1])); + npixels *= npixels; + + /* Setup the shared pixel arguments */ + pix_args.pixel_normalized_size[0] = pix_sz[0]; + pix_args.pixel_normalized_size[1] = pix_sz[1]; + pix_args.rng = rng; + pix_args.spp = args->spp; + pix_args.ithread = ithread; + pix_args.context = args->context; + + FOR_EACH(mcode, 0, npixels) { + void* pixel = NULL; + uint16_t ipix_tile[2]; /* Pixel coord in the tile */ + ASSERT(mcode <= UINT32_MAX); + + morton_xy_decode_u16((uint32_t)mcode, ipix_tile); + if(ipix_tile[0] >= tile_sz[0] || ipix_tile[1] >= tile_sz[1]) + continue; /* Pixel is out of tile */ + + /* Fetch the pixel */ + pixel = tile_at(tile, ipix_tile[0], ipix_tile[1]); + + /* Compute the pixel coordinate */ + pix_args.pixel_coord[0] = (size_t)(tile_org[0] + ipix_tile[0]); + pix_args.pixel_coord[1] = (size_t)(tile_org[1] + ipix_tile[1]); + + /* Invoque the draw pixel functor */ + args->draw_pixel(htrdr, &pix_args, pixel); + } + return RES_OK; +} + +static res_T +draw_map + (struct htrdr* htrdr, + const struct htrdr_draw_map_args* args, + const size_t ntiles_x, + const size_t ntiles_y, + const size_t ntiles_adjusted, + const double pix_sz[2], /* Pixel size in the normalized image plane */ + struct proc_work* work, + struct list_node* tiles) +{ + struct ssp_rng* rng_proc = NULL; + size_t nthreads = 0; + size_t nthieves = 0; + size_t proc_ntiles = 0; + ATOMIC nsolved_tiles = 0; + ATOMIC res = RES_OK; + ASSERT(htrdr && check_draw_map_args(args) && work && tiles); + ASSERT(ntiles_x && ntiles_y && ntiles_adjusted >= ntiles_x*ntiles_y); + ASSERT(pix_sz && pix_sz[0] > 0 && pix_sz[1] > 0); + (void)ntiles_x, (void)ntiles_y; + + res = ssp_rng_create(htrdr->allocator, &ssp_rng_mt19937_64, &rng_proc); + if(res != RES_OK) { + htrdr_log_err(htrdr, "Could not create the RNG used to sample a process " + "to steal -- %s.\n", res_to_cstr((res_T)res)); + goto error; + } + + proc_ntiles = proc_work_get_ntiles(work); + nthreads = MMIN(htrdr->nthreads, proc_ntiles); + + /* The process is not considered as a working process for himself */ + htrdr->mpi_working_procs[htrdr->mpi_rank] = 0; + --htrdr->mpi_nworking_procs; + + omp_set_num_threads((int)nthreads); + #pragma omp parallel + for(;;) { + const int ithread = omp_get_thread_num(); + struct ssp_rng_proxy* rng_proxy = NULL; + struct ssp_rng* rng; + struct tile* tile; + uint32_t mcode = TILE_MCODE_NULL; + uint16_t tile_org[2]; + size_t tile_sz[2]; + size_t n; + res_T res_local = RES_OK; + int32_t pcent; + + /* Get a tile to draw */ + #pragma omp critical + { + mcode = proc_work_get_tile(work); + if(mcode == TILE_MCODE_NULL) { /* No more work on this process */ + /* Try to steal works to concurrent processes */ + proc_work_reset(work); + nthieves = mpi_steal_work(htrdr, rng_proc, work); + if(nthieves != 0) { + mcode = proc_work_get_tile(work); + } + } + } + if(mcode == TILE_MCODE_NULL) break; /* No more work */ + + /* Decode the morton code to retrieve the tile index */ + morton_xy_decode_u16(mcode, tile_org); + ASSERT(tile_org[0] < ntiles_x && tile_org[1] < ntiles_y); + + /* Create the tile */ + tile = tile_create + (htrdr->allocator, + args->buffer_layout.elmt_size, + args->buffer_layout.alignment); + if(!tile) { + ATOMIC_SET(&res, RES_MEM_ERR); + htrdr_log_err(htrdr, + "could not allocate the memory space of the tile (%lu, %lu) -- %s.\n", + (unsigned long)tile_org[0], (unsigned long)tile_org[1], + res_to_cstr((res_T)ATOMIC_GET(&res))); + break; + } + + /* Register the tile */ + #pragma omp critical + list_add_tail(tiles, &tile->node); + + tile->data.x = (uint16_t)tile_org[0]; + tile->data.y = (uint16_t)tile_org[1]; + + /* Define the tile origin in pixel space */ + tile_org[0] = (uint16_t)(tile_org[0] * TILE_SIZE); + tile_org[1] = (uint16_t)(tile_org[1] * TILE_SIZE); + + /* Compute the size of the tile clamped by the borders of the buffer */ + tile_sz[0] = MMIN(TILE_SIZE, args->buffer_layout.width - tile_org[0]); + tile_sz[1] = MMIN(TILE_SIZE, args->buffer_layout.height - tile_org[1]); + + /* Create a proxy RNG for the current tile. This proxy is used for the + * current thread only and thus it has to manage only one RNG. This proxy + * is initialised in order to ensure that an unique and predictable set of + * random numbers is used for the current tile. */ + SSP(rng_proxy_create2 + (&htrdr->lifo_allocators[ithread], + &ssp_rng_threefry, + RNG_SEQUENCE_SIZE * (size_t)mcode, /* Offset */ + RNG_SEQUENCE_SIZE, /* Size */ + RNG_SEQUENCE_SIZE * (size_t)ntiles_adjusted, /* Pitch */ + 1, &rng_proxy)); + SSP(rng_proxy_create_rng(rng_proxy, 0, &rng)); + + /* Launch the tile rendering */ + res_local = draw_tile(htrdr, args, (size_t)ithread, mcode, + tile_org, tile_sz, pix_sz, rng, tile); + + SSP(rng_proxy_ref_put(rng_proxy)); + SSP(rng_ref_put(rng)); + + if(res_local != RES_OK) { + ATOMIC_SET(&res, res_local); + break; + } + + /* Update the progress status */ + n = (size_t)ATOMIC_INCR(&nsolved_tiles); + pcent = (int32_t)((double)n * 100.0 / (double)proc_ntiles + 0.5/*round*/); + + #pragma omp critical + if(pcent > htrdr->mpi_progress_render[0]) { + htrdr->mpi_progress_render[0] = pcent; + if(htrdr->mpi_rank == 0) { + update_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING); + } else { /* Send the progress percentage to the master process */ + send_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING, pcent); + } + } + } + + if(ATOMIC_GET(&res) != RES_OK) goto error; + + /* Asynchronously wait for processes completion. Use an asynchronous barrier to + * avoid a dead lock with the `mpi_probe_thieves' thread that requires also + * the `mpi_mutex'. */ + { + MPI_Request req; + + mutex_lock(htrdr->mpi_mutex); + MPI(Ibarrier(MPI_COMM_WORLD, &req)); + mutex_unlock(htrdr->mpi_mutex); + + mpi_wait_for_request(htrdr, &req); + } + +exit: + if(rng_proc) SSP(rng_ref_put(rng_proc)); + return (res_T)res; +error: + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_draw_map + (struct htrdr* htrdr, + const struct htrdr_draw_map_args* args, + struct htrdr_buffer* buf) +{ + char strbuf[128]; + struct time t0, t1; + struct list_node tiles; + size_t ntiles_x, ntiles_y, ntiles, ntiles_adjusted; + size_t itile; + struct proc_work work; + size_t proc_ntiles_adjusted; + double pix_sz[2]; + + ATOMIC probe_thieves = 1; + ATOMIC res = RES_OK; + ASSERT(htrdr && check_draw_map_args(args)); + ASSERT(htrdr->mpi_rank != 0 || buf); + +#ifndef NDEBUG + if(htrdr_get_mpi_rank(htrdr) == 0) { + /* Check data consistency */ + struct htrdr_buffer_layout layout = HTRDR_BUFFER_LAYOUT_NULL; + htrdr_buffer_get_layout(buf, &layout); + ASSERT(htrdr_buffer_layout_eq(&layout, &args->buffer_layout)); + } +#endif + + list_init(&tiles); + proc_work_init(htrdr->allocator, &work); + + /* Compute the overall number of tiles */ + ntiles_x = (args->buffer_layout.width + (TILE_SIZE-1)/*ceil*/)/TILE_SIZE; + ntiles_y = (args->buffer_layout.height+ (TILE_SIZE-1)/*ceil*/)/TILE_SIZE; + ntiles = ntiles_x * ntiles_y; + + /* Compute the pixel size in the normalized image plane */ + pix_sz[0] = 1.0 / (double)args->buffer_layout.width; + pix_sz[1] = 1.0 / (double)args->buffer_layout.height; + + /* Adjust the #tiles for the morton-encoding procedure */ + ntiles_adjusted = round_up_pow2(MMAX(ntiles_x, ntiles_y)); + ntiles_adjusted *= ntiles_adjusted; + + /* Define the initial number of tiles of the current process */ + proc_ntiles_adjusted = ntiles_adjusted / (size_t)htrdr->mpi_nprocs; + if(htrdr->mpi_rank == 0) { /* Affect the remaining tiles to the master proc */ + proc_ntiles_adjusted += + ntiles_adjusted - proc_ntiles_adjusted*(size_t)htrdr->mpi_nprocs; + } + + /* Define the initial list of tiles of the process */ + FOR_EACH(itile, 0, proc_ntiles_adjusted) { + uint16_t tile_org[2]; + const uint32_t mcode = + (uint32_t)itile*(uint32_t)htrdr->mpi_nprocs + (uint32_t)htrdr->mpi_rank; + + morton_xy_decode_u16(mcode, tile_org); + if(tile_org[0] >= ntiles_x || tile_org[1] >= ntiles_y) continue; + proc_work_add_tile(&work, mcode); + } + + if(htrdr->mpi_rank == 0) { + fetch_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING); + print_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING); + } + + time_current(&t0); + + omp_set_nested(1); /* Enable nested threads for draw_image */ + #pragma omp parallel sections num_threads(2) + { + #pragma omp section + mpi_probe_thieves(htrdr, &work, &probe_thieves); + + #pragma omp section + { + draw_map(htrdr, args, ntiles_x, ntiles_y, ntiles_adjusted, pix_sz, &work, + &tiles); + /* The processes have no more work to do. Stop probing for thieves */ + ATOMIC_SET(&probe_thieves, 0); + } + } + + if(htrdr->mpi_rank == 0) { + update_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING); + htrdr_log(htrdr, "\n"); /* Add a new line after the progress statuses */ + } + + time_sub(&t0, time_current(&t1), &t0); + time_dump(&t0, TIME_ALL, NULL, strbuf, sizeof(strbuf)); + htrdr_log(htrdr, "Rendering time: %s\n", strbuf); + + /* Gather tiles to master process */ + time_current(&t0); + res = mpi_gather_tiles(htrdr, &args->buffer_layout, buf, ntiles, &tiles); + if(res != RES_OK) goto error; + time_sub(&t0, time_current(&t1), &t0); + time_dump(&t0, TIME_ALL, NULL, strbuf, sizeof(strbuf)); + htrdr_log(htrdr, "Image gathering time: %s\n", strbuf); + +exit: + { /* Free allocated tiles */ + struct list_node* node; + struct list_node* tmp; + LIST_FOR_EACH_SAFE(node, tmp, &tiles) { + struct tile* tile = CONTAINER_OF(node, struct tile, node); + list_del(node); + tile_ref_put(tile); + } + } + proc_work_release(&work); + return (res_T)res; +error: + goto exit; +} + diff --git a/src/core/htrdr_draw_map.h b/src/core/htrdr_draw_map.h @@ -0,0 +1,94 @@ +/* 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_DRAW_MAP_H +#define HTRDR_DRAW_MAP_H + +#include "core/htrdr.h" +#include "core/htrdr_buffer.h" + +#include <rsys/rsys.h> + +struct htrdr_draw_pixel_args { + size_t pixel_coord[2]; /* Image plane pixel coordinates */ + double pixel_normalized_size[2]; /* Pixel size in the normalized img plane */ + struct ssp_rng* rng; /* Random Number Generator */ + size_t spp; /* #samples per pixel */ + size_t ithread; /* Id of the thread drawing the pixel */ + void* context; /* User defined data */ +}; + +#define HTRDR_DRAW_PIXEL_ARGS_NULL__ { \ + {0, 0}, /* Image plane pixel coordinates */ \ + {0, 0}, /* Pixel size in the normalized img plane */ \ + NULL, /* RNG */ \ + 0, /* SPP */ \ + 0, /* Thread id */ \ + NULL /* User data */ \ +} +static const struct htrdr_draw_pixel_args HTRDR_DRAW_PIXEL_ARGS_NULL = + HTRDR_DRAW_PIXEL_ARGS_NULL__; + +typedef void +(*htrdr_draw_pixel_T) + (struct htrdr* htrdr, + const struct htrdr_draw_pixel_args* args, + void* pixel); /* Output data */ + +struct htrdr_draw_map_args { + htrdr_draw_pixel_T draw_pixel; + struct htrdr_buffer_layout buffer_layout; + struct ssp_rng* rng; + size_t spp; /* Samples per pixel */ + void* context; /* User defined data */ +}; + +#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 */ \ +} +static const struct htrdr_draw_map_args HTRDR_DRAW_MAP_ARGS_NULL = + HTRDR_DRAW_MAP_ARGS_NULL__; + +static INLINE int +htrdr_draw_pixel_args_check(const struct htrdr_draw_pixel_args* args) +{ + return args + && args->pixel_normalized_size[0] > 0 + && args->pixel_normalized_size[1] > 0 + && args->rng + && args->spp > 0; +} + +/******************************************************************************* + * Exported symbols + ******************************************************************************/ +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_draw_map + (struct htrdr* htrdr, + const struct htrdr_draw_map_args* args, + struct htrdr_buffer* buf); /* May be NULL for non master processes */ + +END_DECLS + +#endif /* HTRDR_DRAW_MAP_H */ + diff --git a/src/core/htrdr_geometry.c b/src/core/htrdr_geometry.c @@ -0,0 +1,686 @@ +/* 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 "core/htrdr.h" +#include "core/htrdr_geometry.h" +#include "core/htrdr_interface.h" +#include "core/htrdr_log.h" +#include "core/htrdr_materials.h" +#include "core/htrdr_slab.h" + +#include <aw.h> +#include <star/s3d.h> + +#include <rsys/clock_time.h> +#include <rsys/cstr.h> +#include <rsys/dynamic_array_double.h> +#include <rsys/dynamic_array_size_t.h> +#include <rsys/double2.h> +#include <rsys/double3.h> +#include <rsys/float2.h> +#include <rsys/float3.h> +#include <rsys/hash_table.h> +#include <rsys/ref_count.h> + +#include <string.h> /* strtok_r */ + +/* Define the hash table that maps an Obj vertex id to its position into the + * vertex buffer */ +#define HTABLE_NAME vertex +#define HTABLE_KEY size_t /* Obj vertex id */ +#define HTABLE_DATA size_t +#include <rsys/hash_table.h> + +/* Define the hash table that maps the Star-3D shape id to its interface */ +#define HTABLE_NAME interface +#define HTABLE_KEY unsigned /* Star-3D shape id */ +#define HTABLE_DATA struct htrdr_interface +#include <rsys/hash_table.h> + +struct mesh { + const struct darray_double* positions; + const struct darray_size_t* indices; +}; +static const struct mesh MESH_NULL; + +struct ray_context { + float range[2]; + struct s3d_hit hit_prev; +}; +#define RAY_CONTEXT_NULL__ {{0,INF}, S3D_HIT_NULL__} +static const struct ray_context RAY_CONTEXT_NULL = RAY_CONTEXT_NULL__; + +struct htrdr_geometry { + struct s3d_scene_view* view; + float lower[3]; /* Ground lower bound */ + float upper[3]; /* Ground upper bound */ + int repeat; /* Make the geom infinite in X and Y */ + + struct htable_interface interfaces; /* Map a Star3D shape to its interface */ + + struct htrdr* htrdr; + ref_T ref; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +/* Check that `hit' roughly lies on an edge. For triangular primitives, a + * simple but approximative way is to test that its position have at least one + * barycentric coordinate roughly equal to 0 or 1. */ +static FINLINE int +hit_on_edge(const struct s3d_hit* hit) +{ + const float on_edge_eps = 1.e-4f; + float w; + ASSERT(hit && !S3D_HIT_NONE(hit)); + w = 1.f - hit->uv[0] - hit->uv[1]; + return eq_epsf(hit->uv[0], 0.f, on_edge_eps) + || eq_epsf(hit->uv[0], 1.f, on_edge_eps) + || eq_epsf(hit->uv[1], 0.f, on_edge_eps) + || eq_epsf(hit->uv[1], 1.f, on_edge_eps) + || eq_epsf(w, 0.f, on_edge_eps) + || eq_epsf(w, 1.f, on_edge_eps); +} + +static int +geometry_filter + (const struct s3d_hit* hit, + const float ray_org[3], + const float ray_dir[3], + void* ray_data, + void* filter_data) +{ + const struct ray_context* ray_ctx = ray_data; + (void)ray_org, (void)ray_dir, (void)filter_data; + + if(!ray_ctx) return 0; + + if(S3D_PRIMITIVE_EQ(&hit->prim, &ray_ctx->hit_prev.prim)) return 1; + + if(!S3D_HIT_NONE(&ray_ctx->hit_prev) && eq_epsf(hit->distance, 0, 1.e-1f)) { + /* If the targeted point is near of the origin, check that it lies on an + * edge/vertex shared by the 2 primitives. */ + return hit_on_edge(&ray_ctx->hit_prev) && hit_on_edge(hit); + } + + return hit->distance <= ray_ctx->range[0] + || hit->distance >= ray_ctx->range[1]; +} + +static res_T +parse_shape_interface + (struct htrdr* htrdr, + struct htrdr_materials* mats, + const char* name, + struct htrdr_interface* interf) +{ + struct str str; + char* mtl_name0 = NULL; + char* mtl_name1 = NULL; + char* mtl_name2 = NULL; + char* mtl_name_front = NULL; + char* mtl_name_thin = NULL; + char* mtl_name_back = NULL; + char* tk_ctx = NULL; + int has_front = 0; + int has_thin = 0; + int has_back = 0; + res_T res = RES_OK; + ASSERT(htrdr && mats && name && interf); + + str_init(htrdr_get_allocator(htrdr), &str); + + /* Locally copy the string to parse */ + res = str_set(&str, name); + if(res != RES_OK) { + htrdr_log_err(htrdr, + "Could not locally copy the shape material string `%s' -- %s.\n", + name, res_to_cstr(res)); + goto error; + } + + /* Reset the interface */ + memset(interf, 0, sizeof(*interf)); + + /* Parse the name of the front/back/thin materials */ + mtl_name0 = strtok_r(str_get(&str), ":", &tk_ctx); + mtl_name1 = strtok_r(NULL, ":", &tk_ctx); + mtl_name2 = strtok_r(NULL, ":", &tk_ctx); + ASSERT(mtl_name0); /* This can't be NULL */ + mtl_name_front = mtl_name0; + if(mtl_name2) { + mtl_name_thin = mtl_name1; + mtl_name_back = mtl_name2; + } else { + mtl_name_thin = NULL; + mtl_name_back = mtl_name1; + } + + if(!mtl_name_back) { + htrdr_log_err(htrdr, + "The back material name is missing `%s'.\n", name); + res = RES_BAD_ARG; + goto error; + } + + /* Fetch the interface material if any */ + if(mtl_name_thin) { + has_thin = htrdr_materials_find_mtl(mats, mtl_name_thin, &interf->mtl_thin); + if(!has_thin) { + htrdr_log_err(htrdr, + "Invalid interface `%s'. The interface material `%s' is unknown.\n", + name, mtl_name_thin); + res = RES_BAD_ARG; + goto error; + } + } + + /* Fetch the front material */ + has_front = htrdr_materials_find_mtl(mats, mtl_name_front, &interf->mtl_front); + if(!has_front) { + htrdr_log_err(htrdr, + "Invalid interface `%s'. The front material `%s' is unknown.\n", + name, mtl_name_front); + res = RES_BAD_ARG; + goto error; + } + + /* Fetch the back material */ + has_back = htrdr_materials_find_mtl(mats, mtl_name_back, &interf->mtl_back); + if(!has_back) { + htrdr_log_err(htrdr, + "Invalid interface `%s'. The back material `%s' is unknown.\n", + name, mtl_name_back); + res = RES_BAD_ARG; + goto error; + } + +exit: + str_release(&str); + return res; +error: + *interf = HTRDR_INTERFACE_NULL; + goto exit; +} + +static res_T +setup_mesh + (struct htrdr* htrdr, + const char* filename, + struct aw_obj* obj, + struct aw_obj_named_group* mtl, + struct darray_double* positions, + struct darray_size_t* indices, + struct htable_vertex* vertices) /* Scratch data structure */ +{ + size_t iface; + res_T res = RES_OK; + ASSERT(htrdr && filename && obj && mtl && positions && indices && vertices); + + darray_double_clear(positions); + darray_size_t_clear(indices); + htable_vertex_clear(vertices); + + FOR_EACH(iface, mtl->face_id, mtl->face_id+mtl->faces_count) { + struct aw_obj_face face; + size_t ivertex; + + AW(obj_get_face(obj, iface, &face)); + if(face.vertices_count != 3) { + htrdr_log_err(htrdr, + "The obj `%s' has non-triangulated polygons " + "while currently only triangular meshes are supported.\n", + filename); + res = RES_BAD_ARG; + goto error; + } + + FOR_EACH(ivertex, 0, face.vertices_count) { + struct aw_obj_vertex vertex; + size_t id; + size_t* pid; + + AW(obj_get_vertex(obj, face.vertex_id + ivertex, &vertex)); + pid = htable_vertex_find(vertices, &vertex.position_id); + if(pid) { + id = *pid; + } else { + struct aw_obj_vertex_data vdata; + + id = darray_double_size_get(positions) / 3; + + res = darray_double_resize(positions, id*3 + 3); + if(res != RES_OK) { + htrdr_log_err(htrdr, + "Could not locally copy the vertex position -- %s.\n", + res_to_cstr(res)); + goto error; + } + + AW(obj_get_vertex_data(obj, &vertex, &vdata)); + darray_double_data_get(positions)[id*3+0] = vdata.position[0]; + darray_double_data_get(positions)[id*3+1] = vdata.position[1]; + darray_double_data_get(positions)[id*3+2] = vdata.position[2]; + + res = htable_vertex_set(vertices, &vertex.position_id, &id); + if(res != RES_OK) { + htrdr_log_err(htrdr, + "Could not register the vertex position -- %s.\n", + res_to_cstr(res)); + goto error; + } + } + + res = darray_size_t_push_back(indices, &id); + if(res != RES_OK) { + htrdr_log_err(htrdr, + "Could not locally copy the face index -- %s\n", + res_to_cstr(res)); + goto error; + } + } + } +exit: + return res; +error: + darray_double_clear(positions); + darray_size_t_clear(indices); + htable_vertex_clear(vertices); + goto exit; +} + +static void +get_position(const unsigned ivert, float position[3], void* ctx) +{ + const struct mesh* mesh = ctx; + const double* pos = NULL; + ASSERT(mesh); + ASSERT(ivert < darray_double_size_get(mesh->positions) / 3); + pos = darray_double_cdata_get(mesh->positions) + ivert*3; + position[0] = (float)pos[0]; + position[1] = (float)pos[1]; + position[2] = (float)pos[2]; +} + +static void +get_indices(const unsigned itri, unsigned indices[3], void* ctx) +{ + const struct mesh* mesh = ctx; + const size_t* ids = NULL; + ASSERT(mesh); + ASSERT(itri < darray_size_t_size_get(mesh->indices) / 3); + ids = darray_size_t_cdata_get(mesh->indices) + itri*3; + indices[0] = (unsigned)ids[0]; + indices[1] = (unsigned)ids[1]; + indices[2] = (unsigned)ids[2]; +} + +static res_T +create_s3d_shape + (struct htrdr* htrdr, + const struct darray_double* positions, + const struct darray_size_t* indices, + struct s3d_shape** out_shape) +{ + struct s3d_shape* shape = NULL; + struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL; + struct mesh mesh = MESH_NULL; + res_T res = RES_OK; + ASSERT(htrdr && positions && indices && out_shape); + ASSERT(darray_double_size_get(positions) != 0); + ASSERT(darray_size_t_size_get(indices) != 0); + ASSERT(darray_double_size_get(positions)%3 == 0); + ASSERT(darray_size_t_size_get(indices)%3 == 0); + + mesh.positions = positions; + mesh.indices = indices; + + res = s3d_shape_create_mesh(htrdr_get_s3d(htrdr), &shape); + if(res != RES_OK) { + htrdr_log_err(htrdr, "Error creating a Star-3D shape -- %s.\n", + res_to_cstr(res)); + goto error; + } + + vdata.usage = S3D_POSITION; + vdata.type = S3D_FLOAT3; + vdata.get = get_position; + + res = s3d_mesh_setup_indexed_vertices + (shape, + (unsigned int)(darray_size_t_size_get(indices)/3), + get_indices, + (unsigned int)(darray_double_size_get(positions)/3), + &vdata, 1, + &mesh); + if(res != RES_OK){ + htrdr_log_err(htrdr, "Could not setup the Star-3D shape -- %s.\n", + res_to_cstr(res)); + goto error; + } + + res = s3d_mesh_set_hit_filter_function(shape, geometry_filter, NULL); + if(res != RES_OK) { + htrdr_log_err(htrdr, + "Could not setup the Star-3D hit filter function of the geometry " + "-- %s.\n", res_to_cstr(res)); + goto error; + } + +exit: + *out_shape = shape; + return res; +error: + if(shape) { + S3D(shape_ref_put(shape)); + shape = NULL; + } + goto exit; +} + +static res_T +setup_geometry + (struct htrdr_geometry* geom, + struct htrdr_materials* mats, + const char* obj_filename) +{ + struct aw_obj_desc desc; + struct htable_vertex vertices; + struct darray_double positions; + struct darray_size_t indices; + struct aw_obj* obj = NULL; + struct s3d_shape* shape = NULL; + struct s3d_scene* scene = NULL; + size_t iusemtl; + + res_T res = RES_OK; + ASSERT(geom && mats && obj_filename); + + htable_vertex_init(htrdr_get_allocator(geom->htrdr), &vertices); + darray_double_init(htrdr_get_allocator(geom->htrdr), &positions); + darray_size_t_init(htrdr_get_allocator(geom->htrdr), &indices); + + res = aw_obj_create + (htrdr_get_logger(geom->htrdr), + htrdr_get_allocator(geom->htrdr), + htrdr_get_verbosity_level(geom->htrdr), + &obj); + if(res != RES_OK) { + htrdr_log_err(geom->htrdr, "Could not create the obj loader -- %s.\n", + res_to_cstr(res)); + goto error; + } + + res = s3d_scene_create(htrdr_get_s3d(geom->htrdr), &scene); + if(res != RES_OK) { + htrdr_log_err(geom->htrdr, "Could not create the Star-3D scene -- %s.\n", + res_to_cstr(res)); + goto error; + } + + /* Load the geometry data */ + res = aw_obj_load(obj, obj_filename); + if(res != RES_OK) goto error; + + /* Fetch the descriptor of the loaded geometry */ + AW(obj_get_desc(obj, &desc)); + + if(desc.usemtls_count == 0) { + htrdr_log_err(geom->htrdr, "The obj `%s' has no material.\n", obj_filename); + res = RES_BAD_ARG; + goto error; + } + + /* Setup the geometry */ + FOR_EACH(iusemtl, 0, desc.usemtls_count) { + struct aw_obj_named_group mtl; + struct htrdr_interface interf; + unsigned ishape; + + AW(obj_get_mtl(obj, iusemtl , &mtl)); + + res = parse_shape_interface(geom->htrdr, mats, mtl.name, &interf); + if(res != RES_OK) goto error; + + res = setup_mesh + (geom->htrdr, obj_filename, obj, &mtl, &positions, &indices, &vertices); + if(res != RES_OK) goto error; + + res = create_s3d_shape(geom->htrdr, &positions, &indices, &shape); + if(res != RES_OK) goto error; + + S3D(shape_get_id(shape, &ishape)); + res = htable_interface_set(&geom->interfaces, &ishape, &interf); + if(res != RES_OK) { + htrdr_log_err(geom->htrdr, + "Could not map the Star-3D shape to its Star-Materials -- %s.\n", + res_to_cstr(res)); + goto error; + } + + res = s3d_scene_attach_shape(scene, shape); + if(res != RES_OK) { + htrdr_log_err(geom->htrdr, + "Could not attach a Star-3D shape to the Star-3D scene -- %s.\n", + res_to_cstr(res)); + goto error; + } + + S3D(shape_ref_put(shape)); + shape = NULL; + } + + res = s3d_scene_view_create(scene, S3D_GET_PRIMITIVE|S3D_TRACE, &geom->view); + if(res != RES_OK) { + htrdr_log_err(geom->htrdr, + "Could not create the Star-3D scene view -- %s.\n", + res_to_cstr(res)); + goto error; + } + + res = s3d_scene_view_get_aabb(geom->view, geom->lower, geom->upper); + if(res != RES_OK) { + htrdr_log_err(geom->htrdr, + "Could not get the bounding box of the geometry -- %s.\n", + res_to_cstr(res)); + goto error; + } + +exit: + if(obj) AW(obj_ref_put(obj)); + if(shape) S3D(shape_ref_put(shape)); + if(scene) S3D(scene_ref_put(scene)); + htable_vertex_release(&vertices); + darray_double_release(&positions); + darray_size_t_release(&indices); + return res; +error: + goto exit; +} + +static void +release_ground(ref_T* ref) +{ + struct htrdr_geometry* geom; + struct htrdr* htrdr; + ASSERT(ref); + geom = CONTAINER_OF(ref, struct htrdr_geometry, ref); + if(geom->view) S3D(scene_view_ref_put(geom->view)); + htable_interface_release(&geom->interfaces); + htrdr = geom->htrdr; + MEM_RM(htrdr_get_allocator(geom->htrdr), geom); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_geometry_create + (struct htrdr* htrdr, + const char* obj_filename, /* May be NULL */ + struct htrdr_materials* mats, + struct htrdr_geometry** out_ground) +{ + char buf[128]; + struct htrdr_geometry* geom = NULL; + struct time t0, t1; + res_T res = RES_OK; + ASSERT(htrdr && obj_filename && mats && out_ground); + + geom = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*geom)); + if(!geom) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "%s: could not allocate the geom data structure -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + ref_init(&geom->ref); + f3_splat(geom->lower, (float)INF); + f3_splat(geom->upper,-(float)INF); + htable_interface_init(htrdr_get_allocator(htrdr), &geom->interfaces); + htrdr_ref_get(htrdr); + geom->htrdr = htrdr; + + htrdr_log(geom->htrdr, "Loading geometry from `%s'.\n", obj_filename); + time_current(&t0); + res = setup_geometry(geom, mats, obj_filename); + if(res != RES_OK) goto error; + time_sub(&t0, time_current(&t1), &t0); + time_dump(&t0, TIME_ALL, NULL, buf, sizeof(buf)); + htrdr_log(geom->htrdr, "Setup geom in %s\n", buf); + +exit: + *out_ground = geom; + return res; +error: + if(geom) { + htrdr_geometry_ref_put(geom); + geom = NULL; + } + goto exit; +} + +void +htrdr_geometry_ref_get(struct htrdr_geometry* geom) +{ + ASSERT(geom); + ref_get(&geom->ref); +} + +void +htrdr_geometry_ref_put(struct htrdr_geometry* geom) +{ + ASSERT(geom); + ref_put(&geom->ref, release_ground); +} + +void +htrdr_geometry_get_interface + (struct htrdr_geometry* geom, + const struct s3d_hit* hit, + struct htrdr_interface* out_interface) +{ + struct htrdr_interface* interf = NULL; + ASSERT(geom && hit && out_interface); + + interf = htable_interface_find(&geom->interfaces, &hit->prim.geom_id); + ASSERT(interf); + + *out_interface = *interf; +} + +res_T +htrdr_geometry_trace_ray + (struct htrdr_geometry* geom, + const double org[3], + const double dir[3], /* Must be normalized */ + const double range[2], + const struct s3d_hit* prev_hit, + struct s3d_hit* hit) +{ + struct ray_context ray_ctx = RAY_CONTEXT_NULL; + float ray_org[3]; + float ray_dir[3]; + res_T res = RES_OK; + ASSERT(geom && org && dir && range && hit); + + f3_set_d3(ray_org, org); + f3_set_d3(ray_dir, dir); + f2_set_d2(ray_ctx.range, range); + ray_ctx.hit_prev = prev_hit ? *prev_hit : S3D_HIT_NULL; + + res = s3d_scene_view_trace_ray + (geom->view, ray_org, ray_dir, ray_ctx.range, &ray_ctx, hit); + if(res != RES_OK) { + htrdr_log_err(geom->htrdr, + "%s: could not trace the ray against the geometry -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + +exit: + return res; +error: + goto exit; +} + +res_T +htrdr_geometry_find_closest_point + (struct htrdr_geometry* geom, + const double pos[3], + const double radius, + struct s3d_hit* hit) +{ + float query_pos[3]; + float query_radius; + res_T res = RES_OK; + ASSERT(geom && pos && hit); + + query_radius = (float)radius; + f3_set_d3(query_pos, pos); + + /* Closest point query */ + res = s3d_scene_view_closest_point + (geom->view, query_pos, query_radius, NULL, hit); + if(res != RES_OK) { + htrdr_log_err(geom->htrdr, + "%s: could not query the closest point to the geometry -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + +exit: + return res; +error: + goto exit; +} + +void +htrdr_geometry_get_aabb + (const struct htrdr_geometry* geom, + double lower[3], + double upper[3]) +{ + ASSERT(geom && lower && upper); + d3_set_f3(lower, geom->lower); + d3_set_f3(upper, geom->upper); +} diff --git a/src/core/htrdr_geometry.h b/src/core/htrdr_geometry.h @@ -0,0 +1,90 @@ +/* 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_GEOMETRY_H +#define HTRDR_GEOMETRY_H + +#include "core/htrdr.h" + +/* Forware declarations */ +struct htrdr; +struct htrdr_geometry; +struct htrdr_interface; +struct htrdr_materials; +struct s3d_hit; +struct ssf_bsdf; + +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_geometry_create + (struct htrdr* htrdr, + const char* obj_filename, + struct htrdr_materials* mats, /* Library of materials */ + struct htrdr_geometry** geometry); + +HTRDR_CORE_API void +htrdr_geometry_ref_get + (struct htrdr_geometry* geom); + +HTRDR_CORE_API void +htrdr_geometry_ref_put + (struct htrdr_geometry* geom); + +HTRDR_CORE_API void +htrdr_geometry_get_interface + (struct htrdr_geometry* geom, + const struct s3d_hit* hit, + struct htrdr_interface* interface); + +HTRDR_CORE_API res_T +htrdr_geometry_create_bsdf + (struct htrdr_geometry* geom, + const size_t ithread, + const double wavelength, + const double pos[3], + const double dir[3], /* Incoming ray */ + const struct s3d_hit* hit, + struct htrdr_interface* interf, /* NULL <=> do not return the interface */ + struct ssf_bsdf** bsdf); + +HTRDR_CORE_API res_T +htrdr_geometry_trace_ray + (struct htrdr_geometry* geom, + const double ray_origin[3], + const double ray_direction[3], /* Must be normalized */ + const double ray_range[2], + const struct s3d_hit* prev_hit,/* Previous hit. Avoid self hit. May be NULL*/ + struct s3d_hit* hit); + +HTRDR_CORE_API res_T +htrdr_geometry_find_closest_point + (struct htrdr_geometry* geom, + const double position[3], + const double radius, + struct s3d_hit* hit); + +HTRDR_CORE_API void +htrdr_geometry_get_aabb + (const struct htrdr_geometry* geom, + double lower[3], + double upper[3]); + +END_DECLS + +#endif /* HTRDR_GEOMETRY_H */ + diff --git a/src/core/htrdr_interface.h b/src/core/htrdr_interface.h @@ -0,0 +1,83 @@ +/* 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_INTERFACE_H +#define HTRDR_INTERFACE_H + +#include "core/htrdr_materials.h" +#include <star/s3d.h> +#include <rsys/double3.h> + +/* Forward declaration of external data type */ +struct mrumtl; +struct s3d_hit; +struct ssf_bsdf; +struct ssp_rng; + +struct htrdr_interface { + struct htrdr_mtl mtl_front; + struct htrdr_mtl mtl_back; + struct htrdr_mtl mtl_thin; /* != NULL <=> thin material */ +}; +static const struct htrdr_interface HTRDR_INTERFACE_NULL; + +static INLINE const struct htrdr_mtl* +htrdr_interface_fetch_hit_mtl + (const struct htrdr_interface* interf, + const double dir[3], /* Incoming ray */ + struct s3d_hit* hit) +{ + const struct htrdr_mtl* mtl = NULL; + enum { FRONT, BACK }; + ASSERT(interf && dir && d3_is_normalized(dir) && hit && !S3D_HIT_NONE(hit)); + ASSERT(interf->mtl_front.mrumtl + || interf->mtl_back.mrumtl + || interf->mtl_thin.mrumtl); + + if(interf->mtl_thin.mrumtl) { + mtl = &interf->mtl_thin; + } else { + double N[3]; + int hit_side; + d3_normalize(N, d3_set_f3(N, hit->normal)); + hit_side = d3_dot(N, dir) < 0 ? FRONT : BACK; + + /* Retrieve the brdf of the material on the *other side* of the hit side */ + switch(hit_side) { + case BACK: mtl = &interf->mtl_front; break; + case FRONT: mtl = &interf->mtl_back; break; + default: FATAL("Unreachable code.\n"); break; + } + + /* Due to numerical issue the hit side might be wrong and thus the fetched + * material might be undefined (e.g. semi-transparent materials). Handle this + * issue by fetching the other material. */ + if(!mtl->mrumtl) { + switch(hit_side) { + case BACK: mtl = &interf->mtl_back; break; + case FRONT: mtl = &interf->mtl_front; break; + default: FATAL("Unreachable code.\n"); break; + } + } + ASSERT(mtl->mrumtl); + } + + return mtl; +} + +#endif /* HTRDR_INTERFACE_H */ + diff --git a/src/core/htrdr_log.c b/src/core/htrdr_log.c @@ -0,0 +1,114 @@ +/* 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 "core/htrdr.h" +#include "core/htrdr_c.h" +#include "core/htrdr_log.h" + +#include <rsys/logger.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +print_out(const char* msg, void* ctx) +{ + ASSERT(msg); + (void)ctx; + fprintf(stderr, HTRDR_LOG_INFO_PREFIX"%s", msg); +} + +static void +print_err(const char* msg, void* ctx) +{ + ASSERT(msg); + (void)ctx; + fprintf(stderr, HTRDR_LOG_ERROR_PREFIX"%s", msg); +} + +static void +print_warn(const char* msg, void* ctx) +{ + ASSERT(msg); + (void)ctx; + fprintf(stderr, HTRDR_LOG_WARNING_PREFIX"%s", msg); +} + +static void +log_msg + (struct htrdr* htrdr, + const enum log_type stream, + const char* msg, + va_list vargs) +{ + ASSERT(htrdr && msg); + if(htrdr->verbose) { + CHK(logger_vprint(&htrdr->logger, stream, msg, vargs) == RES_OK); + } +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +void +htrdr_log(struct htrdr* htrdr, const char* msg, ...) +{ + ASSERT(htrdr && msg); + /* Log standard message only on master process */ + if(htrdr->mpi_rank == 0) { + va_list vargs_list; + va_start(vargs_list, msg); + log_msg(htrdr, LOG_OUTPUT, msg, vargs_list); + va_end(vargs_list); + } +} + +void +htrdr_log_err(struct htrdr* htrdr, const char* msg, ...) +{ + va_list vargs_list; + ASSERT(htrdr && msg); + /* Log errors on all processes */ + va_start(vargs_list, msg); + log_msg(htrdr, LOG_ERROR, msg, vargs_list); + va_end(vargs_list); +} + +void +htrdr_log_warn(struct htrdr* htrdr, const char* msg, ...) +{ + ASSERT(htrdr && msg); + /* Log warnings only on master process */ + if(htrdr->mpi_rank == 0) { + va_list vargs_list; + va_start(vargs_list, msg); + log_msg(htrdr, LOG_WARNING, msg, vargs_list); + va_end(vargs_list); + } +} + +/******************************************************************************* + * Local function + ******************************************************************************/ +void +setup_logger(struct htrdr* htrdr) +{ + logger_init(htrdr->allocator, &htrdr->logger); + logger_set_stream(&htrdr->logger, LOG_OUTPUT, print_out, NULL); + logger_set_stream(&htrdr->logger, LOG_ERROR, print_err, NULL); + logger_set_stream(&htrdr->logger, LOG_WARNING, print_warn, NULL); +} diff --git a/src/core/htrdr_log.h b/src/core/htrdr_log.h @@ -0,0 +1,64 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019, Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef HTRDR_LOG_H +#define HTRDR_LOG_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +#define HTRDR_LOG_INFO_PREFIX "\x1b[1m\x1b[32m>\x1b[0m " +#define HTRDR_LOG_ERROR_PREFIX "\x1b[31merror:\x1b[0m " +#define HTRDR_LOG_WARNING_PREFIX "\x1b[33mwarning:\x1b[0m " + +struct htrdr; + +BEGIN_DECLS + +HTRDR_CORE_API void +htrdr_log + (struct htrdr* htrdr, + const char* msg, + ...) +#ifdef COMPILER_GCC + __attribute((format(printf, 2, 3))) +#endif + ; + +HTRDR_CORE_API void +htrdr_log_err + (struct htrdr* htrdr, + const char* msg, + ...) +#ifdef COMPILER_GCC + __attribute((format(printf, 2, 3))) +#endif + ; + +HTRDR_CORE_API void +htrdr_log_warn + (struct htrdr* htrdr, + const char* msg, + ...) +#ifdef COMPILER_GCC + __attribute((format(printf, 2, 3))) +#endif + ; + +END_DECLS + +#endif /* HTRDR_LOG_H */ diff --git a/src/core/htrdr_materials.c b/src/core/htrdr_materials.c @@ -0,0 +1,456 @@ +/* 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 and wordexp support */ + +#include "core/htrdr.h" +#include "core/htrdr_log.h" +#include "core/htrdr_materials.h" + +#include <modradurb/mrumtl.h> +#include <star/ssf.h> +#include <star/ssp.h> + +#include <rsys/cstr.h> +#include <rsys/double3.h> +#include <rsys/hash_table.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> +#include <rsys/str.h> +#include <rsys/text_reader.h> + +#include <string.h> +#include <wordexp.h> + +struct mtl { + struct mrumtl* mrumtl; + double temperature; /* In Kelvin */ +}; +static const struct mtl MTL_NULL = {NULL, -1}; + +/* Generate the hash table that maps a material name to its data */ +#define HTABLE_NAME name2mtl +#define HTABLE_DATA struct mtl +#define HTABLE_KEY struct str +#define HTABLE_KEY_FUNCTOR_INIT str_init +#define HTABLE_KEY_FUNCTOR_RELEASE str_release +#define HTABLE_KEY_FUNCTOR_COPY str_copy +#define HTABLE_KEY_FUNCTOR_COPY_AND_RELEASE str_copy_and_release +#define HTABLE_KEY_FUNCTOR_HASH str_hash +#define HTABLE_KEY_FUNCTOR_EQ str_eq +#include <rsys/hash_table.h> + +struct htrdr_materials { + struct htable_name2mtl name2mtl; + struct htrdr* htrdr; + ref_T ref; +}; + +/******************************************************************************* + * Local functions + ******************************************************************************/ +static res_T +parse_material + (struct htrdr_materials* mats, + struct txtrdr* txtrdr, + struct str* str) /* Scratch string */ +{ + wordexp_t wexp; + char* tk = NULL; + char* tk_ctx = NULL; + struct mtl mtl = MTL_NULL; + int err = 0; + int wexp_is_allocated = 0; + res_T res = RES_OK; + ASSERT(mats && txtrdr); + + tk = strtok_r(txtrdr_get_line(txtrdr), " \t", &tk_ctx); + ASSERT(tk); + + res = str_set(str, tk); + if(res != RES_OK) { + htrdr_log_err(mats->htrdr, + "%s:%lu: could not copy the material name `%s' -- %s.\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk, + res_to_cstr(res)); + goto error; + } + + tk = strtok_r(NULL, "", &tk_ctx); + if(!tk) { + htrdr_log_err(mats->htrdr, + "%s:%lu: missing the MruMtl file for the material `%s'.\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), + str_cget(str)); + res = RES_BAD_ARG; + goto error; + } + + err = wordexp(tk, &wexp, 0); + if(err) { + htrdr_log_err(mats->htrdr, + "%s:%lu: error in word expension of the mrumtl path.\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr)); + res = RES_BAD_ARG; + goto error; + } + wexp_is_allocated = 1; + + if(wexp.we_wordc < 1) { + htrdr_log_err(mats->htrdr, + "%s:%lu: missing the MruMtl file for the material `%s'.\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), + str_cget(str)); + res = RES_BAD_ARG; + goto error; + } + + /* Parse the mrumtl file if any */ + if(strcmp(wexp.we_wordv[0], "none")) { + res = mrumtl_create + (htrdr_get_logger(mats->htrdr), + htrdr_get_allocator(mats->htrdr), + htrdr_get_verbosity_level(mats->htrdr), + &mtl.mrumtl); + if(res != RES_OK) { + htrdr_log_err(mats->htrdr, + "%s:%lu: error creating the MruMtl loader for the material `%s'-- %s.\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), + str_cget(str), res_to_cstr(res)); + goto error; + } + + res = mrumtl_load(mtl.mrumtl, wexp.we_wordv[0]); + if(res != RES_OK) goto error; + } + + if(wexp.we_wordc < 2) { + if(mtl.mrumtl) { + htrdr_log_err(mats->htrdr, + "%s:%lu: missing temperature for the material `%s'.\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), + str_cget(str)); + res = RES_BAD_ARG; + goto error; + } + } else { + /* Parse the temperature */ + res = cstr_to_double(wexp.we_wordv[1], &mtl.temperature); + if(res != RES_OK) { + htrdr_log_err(mats->htrdr, + "%s:%lu: error parsing the temperature `%s' for the material `%s' " + "-- %s.\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), + wexp.we_wordv[1], str_cget(str), res_to_cstr(res)); + goto error; + } + } + + /* Register the material */ + res = htable_name2mtl_set(&mats->name2mtl, str, &mtl); + if(res != RES_OK) { + htrdr_log_err(mats->htrdr, + "%s:%lu: could not register the material `%s' -- %s.\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), + str_cget(str), res_to_cstr(res)); + goto error; + } + + if(wexp.we_wordc > 2) { + htrdr_log_warn(mats->htrdr, "%s:%lu: unexpected text `%s'.\n", + txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), + wexp.we_wordv[2]); + } + +exit: + if(wexp_is_allocated) wordfree(&wexp); + return res; +error: + if(mtl.mrumtl) MRUMTL(ref_put(mtl.mrumtl)); + goto exit; +} + +static res_T +parse_materials_list + (struct htrdr_materials* mats, + const char* filename, + const char* func_name) +{ + struct txtrdr* txtrdr = NULL; + struct str str; + res_T res = RES_OK; + ASSERT(mats && filename && func_name); + + str_init(htrdr_get_allocator(mats->htrdr), &str); + + res = txtrdr_file(htrdr_get_allocator(mats->htrdr), filename, '#', &txtrdr); + if(res != RES_OK) { + htrdr_log_err(mats->htrdr, + "%s: could not create the text reader for the material file `%s' -- %s.\n", + func_name, filename, res_to_cstr(res)); + goto error; + } + + for(;;) { + res = txtrdr_read_line(txtrdr); + if(res != RES_OK) { + htrdr_log_err(mats->htrdr, + "%s: error reading a line in the material file `%s' -- %s.\n", + func_name, filename, res_to_cstr(res)); + goto error; + } + + if(!txtrdr_get_cline(txtrdr)) break; + + res = parse_material(mats, txtrdr, &str); + if(res != RES_OK) goto error; + } + +exit: + str_release(&str); + if(txtrdr) txtrdr_ref_put(txtrdr); + return res; +error: + goto exit; +} + +static void +materials_release(ref_T* ref) +{ + struct htable_name2mtl_iterator it, it_end; + struct htrdr_materials* mats; + struct htrdr* htrdr; + ASSERT(ref); + mats = CONTAINER_OF(ref, struct htrdr_materials, ref); + + htable_name2mtl_begin(&mats->name2mtl, &it); + htable_name2mtl_end(&mats->name2mtl, &it_end); + while(!htable_name2mtl_iterator_eq(&it, &it_end)) { + struct mtl* mtl = htable_name2mtl_iterator_data_get(&it); + /* The mrumtl can be NULL for semi transparent materials */ + if(mtl->mrumtl) MRUMTL(ref_put(mtl->mrumtl)); + htable_name2mtl_iterator_next(&it); + } + htable_name2mtl_release(&mats->name2mtl); + htrdr = mats->htrdr; + MEM_RM(htrdr_get_allocator(htrdr), mats); + htrdr_ref_put(htrdr); +} + +static res_T +create_bsdf_diffuse + (struct htrdr* htrdr, + const struct mrumtl_brdf* brdf, + const size_t ithread, + struct ssf_bsdf** out_bsdf) +{ + struct ssf_bsdf* bsdf = NULL; + double reflectivity = 0; + res_T res = RES_OK; + ASSERT(htrdr && brdf && out_bsdf); + ASSERT(mrumtl_brdf_get_type(brdf) == MRUMTL_BRDF_LAMBERTIAN); + + res = ssf_bsdf_create(htrdr_get_thread_allocator(htrdr, ithread), + &ssf_lambertian_reflection, &bsdf); + if(res != RES_OK) goto error; + + reflectivity = mrumtl_brdf_lambertian_get_reflectivity(brdf); + res = ssf_lambertian_reflection_setup(bsdf, reflectivity); + if(res != RES_OK) goto error; + +exit: + *out_bsdf = bsdf; + return res; +error: + if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; } + goto exit; +} + +static res_T +create_bsdf_specular + (struct htrdr* htrdr, + const struct mrumtl_brdf* brdf, + const size_t ithread, + struct ssf_bsdf** out_bsdf) +{ + struct ssf_bsdf* bsdf = NULL; + struct ssf_fresnel* fresnel = NULL; + struct mem_allocator* allocator = NULL; + double reflectivity = 0; + res_T res = RES_OK; + ASSERT(htrdr && brdf && out_bsdf); + ASSERT(mrumtl_brdf_get_type(brdf) == MRUMTL_BRDF_SPECULAR); + + allocator = htrdr_get_thread_allocator(htrdr, ithread); + + res = ssf_bsdf_create(allocator, &ssf_specular_reflection, &bsdf); + if(res != RES_OK) goto error; + + res = ssf_fresnel_create(allocator, &ssf_fresnel_constant, &fresnel); + if(res != RES_OK) goto error; + + reflectivity = mrumtl_brdf_specular_get_reflectivity(brdf); + res = ssf_fresnel_constant_setup(fresnel, reflectivity); + if(res != RES_OK) goto error; + + res = ssf_specular_reflection_setup(bsdf, fresnel); + if(res != RES_OK) goto error; + +exit: + if(fresnel) SSF(fresnel_ref_put(fresnel)); + *out_bsdf = bsdf; + return res; +error: + if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; } + goto exit; +} + +/******************************************************************************* + * Local symbol + ******************************************************************************/ +res_T +htrdr_materials_create + (struct htrdr* htrdr, + const char* filename, + struct htrdr_materials** out_mtl) +{ + struct htrdr_materials* mats = NULL; + res_T res = RES_OK; + ASSERT(htrdr && filename && out_mtl); + + mats = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*mats)); + if(!mats) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "%s: could not allocate the mats data structure -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + ref_init(&mats->ref); + htrdr_ref_get(htrdr); + mats->htrdr = htrdr; + htable_name2mtl_init(htrdr_get_allocator(htrdr), &mats->name2mtl); + + res = parse_materials_list(mats, filename, FUNC_NAME); + if(res != RES_OK) goto error; + +exit: + if(out_mtl) *out_mtl = mats; + return res; +error: + if(mats) { + htrdr_materials_ref_put(mats); + mats = NULL; + } + goto exit; +} + +void +htrdr_materials_ref_get(struct htrdr_materials* mats) +{ + ASSERT(mats); + ref_get(&mats->ref); +} + +void +htrdr_materials_ref_put(struct htrdr_materials* mats) +{ + ASSERT(mats); + ref_put(&mats->ref, materials_release); +} + +int +htrdr_materials_find_mtl + (struct htrdr_materials* mats, + const char* name, + struct htrdr_mtl* htrdr_mtl) +{ + struct str str; + struct htable_name2mtl_iterator it, it_end; + int found = 0; + ASSERT(mats && name && htrdr_mtl); + + str_init(htrdr_get_allocator(mats->htrdr), &str); + CHK(str_set(&str, name) == RES_OK); + + htable_name2mtl_find_iterator(&mats->name2mtl, &str, &it); + htable_name2mtl_end(&mats->name2mtl, &it_end); + if(htable_name2mtl_iterator_eq(&it, &it_end)) { /* No material found */ + *htrdr_mtl = HTRDR_MTL_NULL; + found = 0; + } else { + struct mtl* mtl = htable_name2mtl_iterator_data_get(&it); + ASSERT(mtl != NULL); + htrdr_mtl->name = str_cget(htable_name2mtl_iterator_key_get(&it)); + htrdr_mtl->mrumtl = mtl->mrumtl; + htrdr_mtl->temperature = mtl->temperature; + found = 1; + } + str_release(&str); + + return found; +} + +res_T +htrdr_mtl_create_bsdf + (struct htrdr* htrdr, + const struct htrdr_mtl* mtl, + const size_t ithread, + const double wavelength, + struct ssp_rng* rng, + struct ssf_bsdf** out_bsdf) +{ + struct ssf_bsdf* bsdf = NULL; + const struct mrumtl_brdf* brdf = NULL; + double r; + res_T res = RES_OK; + ASSERT(htrdr && mtl && wavelength && rng && out_bsdf); + + r = ssp_rng_canonical(rng); + + res = mrumtl_fetch_brdf2(mtl->mrumtl, wavelength, r, &brdf); + if(res != RES_OK) { + htrdr_log_err(htrdr, + "%s: error retrieving the MruMtl BRDF for the wavelength %g.\n", + FUNC_NAME, wavelength); + res = RES_BAD_ARG; + goto error; + } + + switch(mrumtl_brdf_get_type(brdf)) { + case MRUMTL_BRDF_LAMBERTIAN: + res = create_bsdf_diffuse(htrdr, brdf, ithread, &bsdf); + break; + case MRUMTL_BRDF_SPECULAR: + res = create_bsdf_specular(htrdr, brdf, ithread, &bsdf); + break; + default: FATAL("Unreachable code.\n"); break; + } + if(res != RES_OK) { + htrdr_log_err(htrdr, "%s: could not create the BSDF -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + +exit: + *out_bsdf = bsdf; + return res; +error: + if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; } + goto exit; +} + diff --git a/src/core/htrdr_materials.h b/src/core/htrdr_materials.h @@ -0,0 +1,74 @@ +/* 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_MATERIALS_H +#define HTRDR_MATERIALS_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +/* Forward declarations */ +struct htrdr_materials; +struct mrumtl; +struct s3d_hit; +struct ssf_bsdf; +struct ssp_rng; + +struct htrdr_mtl { + const char* name; + const struct mrumtl* mrumtl; + double temperature; +}; +#define HTRDR_MTL_NULL__ {NULL, NULL, 0} +static const struct htrdr_mtl HTRDR_MTL_NULL = HTRDR_MTL_NULL__; + +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_materials_create + (struct htrdr* htrdr, + const char* filename, + struct htrdr_materials** mats); + +HTRDR_CORE_API void +htrdr_materials_ref_get + (struct htrdr_materials* mats); + +HTRDR_CORE_API void +htrdr_materials_ref_put + (struct htrdr_materials* mats); + +/* Return 1 if the material exist and 0 otherwise */ +HTRDR_CORE_API int +htrdr_materials_find_mtl + (struct htrdr_materials* mats, + const char* mtl_name, + struct htrdr_mtl* mtl); + +HTRDR_CORE_API res_T +htrdr_mtl_create_bsdf + (struct htrdr* htrdr, + const struct htrdr_mtl* mtl, + const size_t ithread, + const double wavelength, + struct ssp_rng* rng, + struct ssf_bsdf** bsdf); + +END_DECLS + +#endif /* HTRDR_MATERIALS_H */ + diff --git a/src/core/htrdr_ran_wlen.c b/src/core/htrdr_ran_wlen.c @@ -0,0 +1,365 @@ +/* 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 /* nextafter */ + +#include "core/htrdr.h" +#include "core/htrdr_c.h" +#include "core/htrdr_log.h" +#include "core/htrdr_ran_wlen.h" +#include "core/htrdr_spectral.h" + +#include <high_tune/htsky.h> + +#include <rsys/algorithm.h> +#include <rsys/cstr.h> +#include <rsys/dynamic_array_double.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> + +#include <math.h> /* nextafter */ + +struct htrdr_ran_wlen { + struct darray_double pdf; + struct darray_double cdf; + double range[2]; /* Boundaries of the spectral integration interval */ + double band_len; /* Length in nanometers of a band */ + double ref_temperature; /* In Kelvin */ + size_t nbands; /* # bands */ + + struct htrdr* htrdr; + ref_T ref; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static res_T +setup_wlen_ran_cdf + (struct htrdr_ran_wlen* wlen_ran, + const char* func_name) +{ + double* pdf = NULL; + double* cdf = NULL; + double sum = 0; + size_t i; + res_T res = RES_OK; + ASSERT(wlen_ran && func_name && wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE); + + res = darray_double_resize(&wlen_ran->cdf, wlen_ran->nbands); + if(res != RES_OK) { + htrdr_log_err(wlen_ran->htrdr, + "%s: Error allocating the CDF of the spectral bands -- %s.\n", + func_name, res_to_cstr(res)); + goto error; + } + res = darray_double_resize(&wlen_ran->pdf, wlen_ran->nbands); + if(res != RES_OK) { + htrdr_log_err(wlen_ran->htrdr, + "%s: Error allocating the pdf of the spectral bands -- %s.\n", + func_name, res_to_cstr(res)); + goto error; + } + + cdf = darray_double_data_get(&wlen_ran->cdf); + pdf = darray_double_data_get(&wlen_ran->pdf); /* Now save pdf to correct weight */ + + htrdr_log(wlen_ran->htrdr, + "Number of bands of the spectrum cumulative: %lu\n", + (unsigned long)wlen_ran->nbands); + + /* Compute the *unnormalized* probability to sample a small band */ + FOR_EACH(i, 0, wlen_ran->nbands) { + double lambda_lo = wlen_ran->range[0] + (double)i * wlen_ran->band_len; + double lambda_hi = MMIN(lambda_lo + wlen_ran->band_len, wlen_ran->range[1]); + ASSERT(lambda_lo<= lambda_hi); + ASSERT(lambda_lo > wlen_ran->range[0] + || eq_eps(lambda_lo, wlen_ran->range[0], 1.e-6)); + ASSERT(lambda_lo < wlen_ran->range[1] + || eq_eps(lambda_lo, wlen_ran->range[1], 1.e-6)); + + /* Convert from nanometer to meter */ + lambda_lo *= 1.e-9; + lambda_hi *= 1.e-9; + + /* Compute the probability of the current band */ + pdf[i] = htrdr_blackbody_fraction + (lambda_lo, lambda_hi, wlen_ran->ref_temperature); + + /* Update the norm */ + sum += pdf[i]; + } + + /* Compute the cumulative of the previously computed probabilities */ + FOR_EACH(i, 0, wlen_ran->nbands) { + /* Normalize the probability */ + pdf[i] /= sum; + + /* Setup the cumulative */ + if(i == 0) { + cdf[i] = pdf[i]; + } else { + cdf[i] = pdf[i] + cdf[i-1]; + ASSERT(cdf[i] >= cdf[i-1]); + } + } + ASSERT(eq_eps(cdf[wlen_ran->nbands-1], 1, 1.e-6)); + cdf[wlen_ran->nbands - 1] = 1.0; /* Handle numerical issue */ + +exit: + return res; +error: + darray_double_clear(&wlen_ran->cdf); + darray_double_clear(&wlen_ran->pdf); + goto exit; +} + +static double +wlen_ran_sample_continue + (const struct htrdr_ran_wlen* wlen_ran, + const double r, + const double range[2], /* In nanometer */ + const char* func_name, + double* pdf) +{ + /* Numerical parameters of the dichotomy search */ + const size_t MAX_ITER = 100; + const double EPSILON_LAMBDA_M = 1e-15; + const double EPSILON_BF = 1e-6; + + /* Local variables */ + double bf = 0; + double bf_prev = 0; + double bf_min_max = 0; + double lambda_m = 0; + double lambda_m_prev = 0; + double lambda_m_min = 0; + double lambda_m_max = 0; + double range_m[2] = {0, 0}; + size_t i; + + /* Check precondition */ + ASSERT(wlen_ran && func_name); + ASSERT(range && range[0] < range[1]); + ASSERT(0 <= r && r < 1); + + /* Convert the wavelength range in meters */ + range_m[0] = range[0] * 1.0e-9; + range_m[1] = range[1] * 1.0e-9; + + /* Setup the dichotomy search */ + lambda_m_min = range_m[0]; + lambda_m_max = range_m[1]; + bf_min_max = htrdr_blackbody_fraction + (range_m[0], range_m[1], wlen_ran->ref_temperature); + + /* Numerically search the lambda corresponding to the submitted canonical + * number */ + FOR_EACH(i, 0, MAX_ITER) { + double r_test; + lambda_m = (lambda_m_min + lambda_m_max) * 0.5; + bf = htrdr_blackbody_fraction + (range_m[0], lambda_m, wlen_ran->ref_temperature); + + r_test = bf / bf_min_max; + if(r_test < r) { + lambda_m_min = lambda_m; + } else { + lambda_m_max = lambda_m; + } + + if(fabs(lambda_m_prev - lambda_m) < EPSILON_LAMBDA_M + || fabs(bf_prev - bf) < EPSILON_BF) + break; + + lambda_m_prev = lambda_m; + bf_prev = bf; + } + if(i >= MAX_ITER) { + htrdr_log_warn(wlen_ran->htrdr, + "%s: could not sample a wavelength in the range [%g, %g] nanometers " + "for the reference temperature %g Kelvin.\n", + func_name, SPLIT2(range), wlen_ran->ref_temperature); + } + + if(pdf) { + const double Tref = wlen_ran->ref_temperature; + const double B_lambda = 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])); + } + + return lambda_m * 1.0e9; /* Convert in nanometers */ +} + +static double +wlen_ran_sample_discrete + (const struct htrdr_ran_wlen* wlen_ran, + const double r0, + const double r1, + const char* func_name, + double* pdf) +{ + const double* cdf = NULL; + const double* find = NULL; + double r0_next = nextafter(r0, DBL_MAX); + double band_range[2]; + double lambda = 0; + double pdf_continue = 0; + double pdf_band = 0; + size_t cdf_length = 0; + size_t i; + ASSERT(wlen_ran && wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE); + ASSERT(0 <= r0 && r0 < 1); + ASSERT(0 <= r1 && r1 < 1); + (void)func_name; + (void)pdf_band; + + cdf = darray_double_cdata_get(&wlen_ran->cdf); + cdf_length = darray_double_size_get(&wlen_ran->cdf); + ASSERT(cdf_length > 0); + + /* Use r_next rather than r0 in order to find the first entry that is not less + * than *or equal* to r0 */ + find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl); + ASSERT(find); + + i = (size_t)(find - cdf); + ASSERT(i < cdf_length && cdf[i] > r0 && (!i || cdf[i-1] <= r0)); + + band_range[0] = wlen_ran->range[0] + (double)i*wlen_ran->band_len; + band_range[1] = band_range[0] + wlen_ran->band_len; + + /* Fetch the pdf of the sampled band */ + pdf_band = darray_double_cdata_get(&wlen_ran->pdf)[i]; + + /* Uniformly sample a wavelength in the sampled band */ + lambda = band_range[0] + (band_range[1] - band_range[0]) * r1; + pdf_continue = 1.0 / ((band_range[1] - band_range[0])*1.e-9); + + if(pdf) { + *pdf = pdf_band * pdf_continue; + } + + return lambda; +} + +static void +release_wlen_ran(ref_T* ref) +{ + struct htrdr_ran_wlen* wlen_ran = NULL; + struct htrdr* htrdr = NULL; + ASSERT(ref); + wlen_ran = CONTAINER_OF(ref, struct htrdr_ran_wlen, ref); + darray_double_release(&wlen_ran->cdf); + darray_double_release(&wlen_ran->pdf); + htrdr = wlen_ran->htrdr; + MEM_RM(htrdr_get_allocator(htrdr), wlen_ran); + htrdr_ref_put(wlen_ran->htrdr); +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +htrdr_ran_wlen_create + (struct htrdr* htrdr, + /* range must be included in [200,1000] nm for shortwave or in [1000,100000] + * nanometers for longwave (thermal) */ + const double range[2], + const size_t nbands, /* # bands used to discretized CDF */ + const double ref_temperature, + struct htrdr_ran_wlen** out_wlen_ran) +{ + struct htrdr_ran_wlen* wlen_ran = NULL; + res_T res = RES_OK; + ASSERT(htrdr && range && out_wlen_ran && ref_temperature > 0); + ASSERT(ref_temperature > 0); + ASSERT(range[0] <= range[1]); + + wlen_ran = MEM_CALLOC(htrdr->allocator, 1, sizeof(*wlen_ran)); + if(!wlen_ran) { + res = RES_MEM_ERR; + htrdr_log_err(htrdr, + "%s: could not allocate longwave random variate data structure -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + ref_init(&wlen_ran->ref); + darray_double_init(htrdr->allocator, &wlen_ran->cdf); + darray_double_init(htrdr->allocator, &wlen_ran->pdf); + htrdr_ref_get(htrdr); + wlen_ran->htrdr = htrdr; + + wlen_ran->range[0] = range[0]; + wlen_ran->range[1] = range[1]; + wlen_ran->ref_temperature = ref_temperature; + wlen_ran->nbands = nbands; + + if(nbands == HTRDR_WLEN_RAN_CONTINUE) { + wlen_ran->band_len = 0; + } else { + wlen_ran->band_len = (range[1] - range[0]) / (double)wlen_ran->nbands; + + res = setup_wlen_ran_cdf(wlen_ran, FUNC_NAME); + if(res != RES_OK) goto error; + } + + htrdr_log(htrdr, "Spectral interval defined on [%g, %g] nanometers.\n", + range[0], range[1]); + +exit: + *out_wlen_ran = wlen_ran; + return res; +error: + if(wlen_ran) htrdr_ran_wlen_ref_put(wlen_ran); + goto exit; +} + +void +htrdr_ran_wlen_ref_get(struct htrdr_ran_wlen* wlen_ran) +{ + ASSERT(wlen_ran); + ref_get(&wlen_ran->ref); +} + +void +htrdr_ran_wlen_ref_put(struct htrdr_ran_wlen* wlen_ran) +{ + ASSERT(wlen_ran); + ref_put(&wlen_ran->ref, release_wlen_ran); +} + +double +htrdr_ran_wlen_sample + (const struct htrdr_ran_wlen* wlen_ran, + const double r0, + const double r1, + double* pdf) +{ + ASSERT(wlen_ran); + if(wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE) { /* Discrete */ + return wlen_ran_sample_discrete(wlen_ran, r0, r1, FUNC_NAME, pdf); + } else if(eq_eps(wlen_ran->range[0], wlen_ran->range[1], 1.e-6)) { + if(pdf) *pdf = 1; + return wlen_ran->range[0]; + } else { /* Continue */ + return wlen_ran_sample_continue + (wlen_ran, r0, wlen_ran->range, FUNC_NAME, pdf); + } +} + diff --git a/src/core/htrdr_ran_wlen.h b/src/core/htrdr_ran_wlen.h @@ -0,0 +1,61 @@ +/* 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_RAN_WLEN_H +#define HTRDR_RAN_WLEN_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +#define HTRDR_WLEN_RAN_CONTINUE 0 + +/* Forward declarations */ +struct htrdr; +struct htrdr_ran_wlen; + +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_ran_wlen_create + (struct htrdr* htrdr, + const double range[2], + /* # bands used to discretisze the spectral domain. HTRDR_WLEN_RAN_CONTINUE + * <=> no discretisation */ + const size_t nbands, /* Hint on #bands used to discretised th CDF */ + const double ref_temperature, /* Reference temperature */ + struct htrdr_ran_wlen** wlen_ran); + +HTRDR_CORE_API void +htrdr_ran_wlen_ref_get + (struct htrdr_ran_wlen* wlen_ran); + +HTRDR_CORE_API void +htrdr_ran_wlen_ref_put + (struct htrdr_ran_wlen* wlen_ran); + +/* Return a wavelength in nanometer */ +HTRDR_CORE_API double +htrdr_ran_wlen_sample + (const struct htrdr_ran_wlen* wlen_ran, + const double r0, /* Canonical number in [0, 1[ */ + const double r1, /* Canonical number in [0, 1[ */ + double* pdf); /* May be NULL */ + +END_DECLS + +#endif /* HTRDR_RAN_WLEN_H */ + diff --git a/src/core/htrdr_rectangle.c b/src/core/htrdr_rectangle.c @@ -0,0 +1,148 @@ +/* Copyright (C) 2018, 2019, 2020, 2021 |Meso|Star> (contact@meso-star.com) + * Copyright (C) 2018, 2019, 2021 CNRS + * Copyright (C) 2018, 2019 Université Paul Sabatier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "core/htrdr.h" +#include "core/htrdr_log.h" +#include "core/htrdr_rectangle.h" + +#include <rsys/double3.h> +#include <rsys/mem_allocator.h> +#include <rsys/ref_count.h> + +struct htrdr_rectangle { + /* Frame of the rectangle in world space */ + double axis_x[3]; + double axis_y[3]; + + double normal[3]; + double position[3]; /* Center of the rectangle */ + struct htrdr* htrdr; + ref_T ref; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +rectangle_release(ref_T* ref) +{ + struct htrdr_rectangle* rect; + struct htrdr* htrdr; + ASSERT(ref); + rect = CONTAINER_OF(ref, struct htrdr_rectangle, ref); + htrdr = rect->htrdr; + MEM_RM(htrdr_get_allocator(htrdr), rect); + htrdr_ref_put(htrdr); +} + +/******************************************************************************* + * Local fuuction + ******************************************************************************/ +res_T +htrdr_rectangle_create + (struct htrdr* htrdr, + const double sz[2], + const double pos[3], + const double tgt[3], + const double up[3], + struct htrdr_rectangle** out_rect) +{ + struct htrdr_rectangle* rect = NULL; + double x[3], y[3], z[3]; + res_T res = RES_OK; + ASSERT(htrdr && pos && tgt && up && sz && out_rect); + + rect = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*rect)); + if(!rect) { + htrdr_log_err(htrdr, "could not allocate the rectangle data structure.\n"); + res = RES_MEM_ERR; + goto error; + } + ref_init(&rect->ref); + htrdr_ref_get(htrdr); + rect->htrdr = htrdr; + + if(sz[0] <= 0 || sz[1] <= 0) { + htrdr_log_err(htrdr, + "invalid rectangle size `%g %g'. It must be strictly positive.\n", + SPLIT2(sz)); + res = RES_BAD_ARG; + goto error; + } + + if(d3_normalize(z, d3_sub(z, tgt, pos)) <= 0 + || d3_normalize(x, d3_cross(x, z, up)) <= 0 + || d3_normalize(y, d3_cross(y, z, x)) <= 0) { + htrdr_log_err(htrdr, "invalid rectangle frame:\n" + "\tposition = %g %g %g\n" + "\ttarget = %g %g %g\n" + "\tup = %g %g %g\n", + SPLIT3(pos), SPLIT3(tgt), SPLIT3(up)); + res = RES_BAD_ARG; + goto error; + } + + d3_muld(rect->axis_x, x, sz[0]*0.5); + d3_muld(rect->axis_y, y, sz[1]*0.5); + d3_set(rect->normal, z); + d3_set(rect->position, pos); + +exit: + *out_rect = rect; + return res; +error: + if(rect) { + htrdr_rectangle_ref_put(rect); + rect = NULL; + } + goto exit; +} + +void +htrdr_rectangle_sample_pos + (const struct htrdr_rectangle* rect, + const double sample[2], /* In [0, 1[ */ + double pos[3]) +{ + double x[3], y[3]; + ASSERT(rect && sample && pos); + d3_muld(x, rect->axis_x, sample[0]*2.0 - 1.0); + d3_muld(y, rect->axis_y, sample[1]*2.0 - 1.0); + d3_add(pos, d3_add(pos, rect->position, x), y); +} + +void +htrdr_rectangle_ref_get(struct htrdr_rectangle* rect) +{ + ASSERT(rect); + ref_get(&rect->ref); +} + +void +htrdr_rectangle_ref_put(struct htrdr_rectangle* rect) +{ + ASSERT(rect); + ref_put(&rect->ref, rectangle_release); +} + +void +htrdr_rectangle_get_normal(const struct htrdr_rectangle* rect, double normal[3]) +{ + ASSERT(rect && normal); + d3_set(normal, rect->normal); +} + diff --git a/src/core/htrdr_rectangle.h b/src/core/htrdr_rectangle.h @@ -0,0 +1,61 @@ +/* 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_RECTANGLE_H +#define HTRDR_RECTANGLE_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +/* Forwar declarations */ +struct htrdr; +struct htrdr_rectangle; /* 2D rectangle transformed in 3D */ + +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_rectangle_create + (struct htrdr* htrdr, + const double sz[2], /* Size of the rectangle along its local X and Y axis */ + const double pos[3], /* World space position of the rectangle center */ + const double tgt[3], /* Vector orthognal to the rectangle plane */ + const double up[3], /* vector orthogonal to the rectangle X axis */ + struct htrdr_rectangle** rect); + +HTRDR_CORE_API void +htrdr_rectangle_ref_get + (struct htrdr_rectangle* rect); + +HTRDR_CORE_API void +htrdr_rectangle_ref_put + (struct htrdr_rectangle* rect); + +HTRDR_CORE_API void +htrdr_rectangle_sample_pos + (const struct htrdr_rectangle* rect, + const double sample[2], /* In [0, 1[ */ + double pos[3]); + +HTRDR_CORE_API void +htrdr_rectangle_get_normal + (const struct htrdr_rectangle* rect, + double normal[3]); + +END_DECLS + +#endif /* HTRDR_RECTANGLE_H */ + diff --git a/src/core/htrdr_sensor.h b/src/core/htrdr_sensor.h @@ -0,0 +1,39 @@ +/* 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_SENSOR_H +#define HTRDR_SENSOR_H + +/* Forward declarations */ +struct htrdr; +struct htrdr_camera; +struct htrdr_rectangle; +struct ssp_rng; + +enum htrdr_sensor_type { + HTRDR_SENSOR_CAMERA, + HTRDR_SENSOR_RECTANGLE +}; + +struct htrdr_sensor { + struct htrdr_camera* camera; + struct htrdr_rectangle* rectangle; + enum htrdr_sensor_type type; +}; + +#endif /* HTRDR_SENSOR_H */ + diff --git a/src/core/htrdr_slab.c b/src/core/htrdr_slab.c @@ -0,0 +1,136 @@ +/* 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 "core/htrdr.h" +#include "core/htrdr_log.h" +#include "core/htrdr_slab.h" + +#include <rsys/cstr.h> +#include <math.h> + +/******************************************************************************* + * Local function + ******************************************************************************/ +res_T +htrdr_slab_trace_ray + (struct htrdr* htrdr, + const double org[3], + const double dir[3], + const double range[2], + const double cell_low[2], + const double cell_upp[2], + htrdr_trace_cell_T trace_cell, + const size_t max_steps, + void* trace_cell_context) +{ + double pos[2]; + double org_cs[3]; /* Origin of the ray transformed in local cell space */ + double cell_low_ws[3]; /* Cell lower bound in world space */ + double cell_upp_ws[3]; /* Cell upper bound in world space */ + double cell_sz[3]; /* Size of a cell */ + double t_max[3], t_delta[2], t_min_z; + size_t istep; + int64_t xy[2]; /* 2D index of the repeated cell */ + int incr[2]; /* Index increment */ + res_T res = RES_OK; + ASSERT(htrdr && org && dir && range && cell_low && cell_upp && trace_cell); + ASSERT(range[0] < range[1]); + + /* Check that the ray intersects the slab */ + t_min_z = (cell_low[2] - org[2]) / dir[2]; + t_max[2] = (cell_upp[2] - org[2]) / dir[2]; + if(t_min_z > t_max[2]) SWAP(double, t_min_z, t_max[2]); + t_min_z = MMAX(t_min_z, range[0]); + t_max[2] = MMIN(t_max[2], range[1]); + if(t_min_z > t_max[2]) return RES_OK; + + /* Compute the size of a cell */ + cell_sz[0] = cell_upp[0] - cell_low[0]; + cell_sz[1] = cell_upp[1] - cell_low[1]; + cell_sz[2] = cell_upp[2] - cell_low[2]; + + /* Define the 2D index of the current cell. (0,0) is the index of the + * non duplicated cell */ + pos[0] = org[0] + t_min_z*dir[0]; + pos[1] = org[1] + t_min_z*dir[1]; + xy[0] = (int64_t)floor((pos[0] - cell_low[0]) / cell_sz[0]); + xy[1] = (int64_t)floor((pos[1] - cell_low[1]) / cell_sz[1]); + + /* Define the 2D index increment wrt dir sign */ + incr[0] = dir[0] < 0 ? -1 : 1; + incr[1] = dir[1] < 0 ? -1 : 1; + + /* Compute the world space AABB of the repeated cell currently hit */ + cell_low_ws[0] = cell_low[0] + (double)xy[0]*cell_sz[0]; + cell_low_ws[1] = cell_low[1] + (double)xy[1]*cell_sz[1]; + cell_low_ws[2] = cell_low[2]; + cell_upp_ws[0] = cell_low_ws[0] + cell_sz[0]; + cell_upp_ws[1] = cell_low_ws[1] + cell_sz[1]; + cell_upp_ws[2] = cell_upp[2]; + + /* Compute the max ray intersection with the current cell */ + t_max[0] = ((dir[0]<0 ? cell_low_ws[0] : cell_upp_ws[0]) - org[0]) / dir[0]; + t_max[1] = ((dir[1]<0 ? cell_low_ws[1] : cell_upp_ws[1]) - org[1]) / dir[1]; + /*t_max[2] = ((dir[2]<0 ? cell_low_ws[2] : cell_upp_ws[2]) - org[2]) / dir[2];*/ + ASSERT(t_max[0] >= 0 && t_max[1] >= 0 && t_max[2] >= 0); + + /* Compute the distance along the ray to traverse in order to move of a + * distance equal to the cloud size along the X and Y axis */ + t_delta[0] = (dir[0]<0 ? -cell_sz[0] : cell_sz[0]) / dir[0]; + t_delta[1] = (dir[1]<0 ? -cell_sz[1] : cell_sz[1]) / dir[1]; + ASSERT(t_delta[0] >= 0 && t_delta[1] >= 0); + + org_cs[2] = org[2]; + FOR_EACH(istep, 0, max_steps) { + int iaxis; + int hit; + + /* Transform the ray origin in the local cell space */ + org_cs[0] = org[0] - (double)xy[0]*cell_sz[0]; + org_cs[1] = org[1] - (double)xy[1]*cell_sz[1]; + + res = trace_cell(org_cs, dir, range, trace_cell_context, &hit); + if(res != RES_OK) { + htrdr_log_err(htrdr, + "%s: could not trace the ray in the repeated cells -- %s.\n", + FUNC_NAME, res_to_cstr(res)); + goto error; + } + if(hit) goto exit; + + /* Define the next axis to traverse */ + iaxis = t_max[0] < t_max[1] + ? (t_max[0] < t_max[2] ? 0 : 2) + : (t_max[1] < t_max[2] ? 1 : 2); + + if(iaxis == 2) break; /* The ray traverse the slab */ + + if(t_max[iaxis] >= range[1]) break; /* Out of bound */ + + t_max[iaxis] += t_delta[iaxis]; + + /* Define the 2D index of the next traversed cloud */ + xy[iaxis] += incr[iaxis]; + } + +exit: + return res; +error: + goto exit; +} + + diff --git a/src/core/htrdr_slab.h b/src/core/htrdr_slab.h @@ -0,0 +1,53 @@ +/* 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_SLAB_H +#define HTRDR_SLAB_H + +#include "core/htrdr.h" +#include <rsys/rsys.h> + +/* Forward declaration */ +struct htrdr; + +typedef res_T +(*htrdr_trace_cell_T) + (const double org[3], /* Ray origin */ + const double dir[3], /* Ray direction. Must be normalized */ + const double range[2], /* Ray range */ + void* ctx, /* User defined data */ + int* hit); /* Hit something ? */ + +BEGIN_DECLS + +/* Trace a ray into a slab composed of a cell infinitely repeated in X and Y */ +HTRDR_CORE_API res_T +htrdr_slab_trace_ray + (struct htrdr* htrdr, + const double org[3], + const double dir[3], + const double range[2], + const double cell_low[2], + const double cell_upp[2], + htrdr_trace_cell_T trace_cell, + const size_t max_steps, /* Max traversed cell */ + void* trace_cell_context); + +END_DECLS + +#endif /* HTRDR_SLAB_H */ + diff --git a/src/core/htrdr_spectral.c b/src/core/htrdr_spectral.c @@ -0,0 +1,116 @@ +/* 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 "core/htrdr.h" +#include "core/htrdr_log.h" +#include "core/htrdr_spectral.h" + +/******************************************************************************* + * Exported symbols + ******************************************************************************/ +res_T +htrdr_brightness_temperature + (struct htrdr* htrdr, + const double lambda_min, + const double lambda_max, + const double radiance, /* In W/m2/sr/m */ + double* temperature) +{ + const size_t MAX_ITER = 100; + const double epsilon_T = 1e-4; /* In K */ + const double epsilon_B = radiance * 1e-8; + double T, T0, T1, T2; + double B, B0; + size_t i; + res_T res = RES_OK; + ASSERT(temperature && lambda_min <= lambda_max); + + /* Search for a brightness temperature whose radiance is greater than or + * equal to the estimated radiance */ + T2 = 200; + FOR_EACH(i, 0, MAX_ITER) { + const double B2 = htrdr_planck(lambda_min, lambda_max, T2); + if(B2 >= radiance) break; + T2 *= 2; + } + if(i >= MAX_ITER) { res = RES_BAD_OP; goto error; } + + B0 = T0 = T1 = 0; + FOR_EACH(i, 0, MAX_ITER) { + T = (T1+T2)*0.5; + B = htrdr_planck(lambda_min, lambda_max, T); + + if(B < radiance) { + T1 = T; + } else { + T2 = T; + } + + if(fabs(T-T0) < epsilon_T || fabs(B-B0) < epsilon_B) + break; + + T0 = T; + B0 = B; + } + if(i >= MAX_ITER) { res = RES_BAD_OP; goto error; } + + *temperature = T; + +exit: + return res; +error: + htrdr_log_err(htrdr, + "Could not compute the brightness temperature for the estimated radiance %g " + "averaged over [%g, %g] nanometers.\n", + radiance, + lambda_min*1e9, + lambda_max*1e9); + goto exit; +} + +double +htrdr_radiance_temperature + (struct htrdr* htrdr, + const double lambda_min, /* In meters */ + const double lambda_max, /* In meters */ + const double radiance) /* In W/m^2/sr */ +{ + double temperature = 0; + double radiance_avg = radiance; + res_T res = RES_OK; + ASSERT(htrdr && radiance >= 0); + + /* From integrated radiance to average radiance in W/m^2/sr/m */ + if(lambda_min != lambda_max) { /* !monochromatic */ + radiance_avg /= (lambda_max - lambda_min); + } + + res = htrdr_brightness_temperature + (htrdr, + lambda_min, + lambda_max, + radiance_avg, + &temperature); + if(res != RES_OK) { + htrdr_log_warn(htrdr, + "Could not compute the brightness temperature for the radiance %g.\n", + radiance_avg); + temperature = 0; + } + return temperature; +} + diff --git a/src/core/htrdr_spectral.h b/src/core/htrdr_spectral.h @@ -0,0 +1,164 @@ +/* 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_SPECTRAL_H +#define HTRDR_SPECTRAL_H + +#include "core/htrdr.h" + +#include <rsys/rsys.h> +#include <rsys/math.h> /* PI support */ + +#define HTRDR_SUN_TEMPERATURE 5778 /* In K */ +#define HTRDR_DEFAULT_LW_REF_TEMPERATURE 290 /* Default longwave temperature in K */ + +enum htrdr_spectral_type { + HTRDR_SPECTRAL_LW, /* Longwave */ + HTRDR_SPECTRAL_SW, /* Shortwave */ + HTRDR_SPECTRAL_SW_CIE_XYZ /* Shortwave wrt the CIE XYZ tristimulus */ +}; + +/* Forwar declaration */ +struct htrdr; + +static FINLINE double /* In nanometer */ +htrdr_wavenumber_to_wavelength(const double nu/*In cm^-1*/) +{ + return 1.e7 / nu; +} + +static FINLINE double /* In cm^-1 */ +wavelength_to_wavenumber(const double lambda/*In nanometer*/) +{ + return htrdr_wavenumber_to_wavelength(lambda); +} + +static INLINE double +htrdr_wiebelt(const double v) +{ + int m; + double w, v2, v4; + /*.153989717364e+00;*/ + const double fifteen_over_pi_power_4 = 15.0/(PI*PI*PI*PI); + const double z0 = 1.0/3.0; + const double z1 = 1.0/8.0; + const double z2 = 1.0/60.0; + const double z4 = 1.0/5040.0; + const double z6 = 1.0/272160.0; + const double z8 = 1.0/13305600.0; + + if(v >= 2.) { + w = 0; + for(m=1; m<6 ;m++) + w+=exp(-m*v)/(m*m*m*m) * (((m*v+3)*m*v+6)*m*v+6); + w = w * fifteen_over_pi_power_4; + } else { + v2 = v*v; + v4 = v2*v2; + w = z0 - z1*v + z2*v2 - z4*v2*v2 + z6*v4*v2 - z8*v4*v4; + w = 1. - fifteen_over_pi_power_4*v2*v*w; + } + ASSERT(w >= 0.0 && w <= 1.0); + return w; +} + +static INLINE double +htrdr_blackbody_fraction + (const double lambda0, /* In meters */ + const double lambda1, /* In meters */ + const double temperature) /* In Kelvin */ +{ + const double C2 = 1.43877735e-2; /* m.K */ + double x0 = C2 / lambda0; + double x1 = C2 / lambda1; + double v0 = x0 / temperature; + double v1 = x1 / temperature; + double w0 = htrdr_wiebelt(v0); + double w1 = htrdr_wiebelt(v1); + return w1 - w0; +} + +/* Return the Planck value in W/m^2/sr/m at a given wavelength */ +static INLINE double +htrdr_planck_monochromatic + (const double lambda, /* In meters */ + const double temperature) /* In Kelvin */ +{ + const double c = 299792458; /* m/s */ + const double h = 6.62607015e-34; /* J.s */ + const double k = 1.380649e-23; /* J/K */ + const double lambda2 = lambda*lambda; + const double lambda5 = lambda2*lambda2*lambda; + const double B = ((2.0 * h * c*c) / lambda5) /* W/m^2/sr/m */ + / (exp(h*c/(lambda*k*temperature))-1.0); + ASSERT(temperature > 0); + return B; +} + +/* Return the average Planck value in W/m^2/sr/m over the + * [lambda_min, lambda_max] interval. */ +static INLINE double +htrdr_planck_interval + (const double lambda_min, /* In meters */ + const double lambda_max, /* In meters */ + const double temperature) /* In Kelvin */ +{ + const double T2 = temperature*temperature; + const double T4 = T2*T2; + const double BOLTZMANN_CONSTANT = 5.6696e-8; /* W/m^2/K^4 */ + ASSERT(lambda_min < lambda_max && temperature > 0); + return htrdr_blackbody_fraction(lambda_min, lambda_max, temperature) + * BOLTZMANN_CONSTANT * T4 / (PI * (lambda_max-lambda_min)); /* In W/m^2/sr/m */ +} + +/* Invoke planck_monochromatic or planck_interval whether the submitted + * interval is null or not, respectively. The returned value is in W/m^2/sr/m */ +static FINLINE double +htrdr_planck + (const double lambda_min, /* In meters */ + const double lambda_max, /* In meters */ + const double temperature) /* In Kelvin */ +{ + ASSERT(lambda_min <= lambda_max && temperature > 0); + if(lambda_min == lambda_max) { + return htrdr_planck_monochromatic(lambda_min, temperature); + } else { + return htrdr_planck_interval(lambda_min, lambda_max, temperature); + } +} + +BEGIN_DECLS + +HTRDR_CORE_API res_T +htrdr_brightness_temperature + (struct htrdr* htrdr, + const double lambda_min, /* In meters */ + const double lambda_max, /* In meters */ + /* Averaged over [lambda_min, lambda_max]. In W/m^2/sr/m */ + const double radiance, + double* temperature); + +HTRDR_CORE_API double +htrdr_radiance_temperature + (struct htrdr* htrdr, + const double lambda_min, /* In meters */ + const double lambda_max, /* In meters */ + const double radiance); /* In W/m^2/sr */ + +END_DECLS + +#endif /* HTRDR_SPECTRAL_H */ diff --git a/src/htrdr_version.h.in b/src/core/htrdr_version.h.in diff --git a/src/htrdr.c b/src/htrdr.c @@ -1,1034 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#define _POSIX_C_SOURCE 200809L /* stat.st_time support */ - -#include "htrdr.h" -#include "htrdr_c.h" -#include "htrdr_args.h" -#include "htrdr_buffer.h" -#include "htrdr_cie_xyz.h" -#include "htrdr_camera.h" -#include "htrdr_ground.h" -#include "htrdr_materials.h" -#include "htrdr_ran_wlen.h" -#include "htrdr_rectangle.h" -#include "htrdr_sun.h" -#include "htrdr_solve.h" -#include "htrdr_version.h" - -#include <rsys/cstr.h> -#include <rsys/mem_allocator.h> -#include <rsys/str.h> - -#include "high_tune/htsky.h" - -#include <star/s3d.h> -#include <star/ssf.h> - -#include <errno.h> -#include <fcntl.h> /* open */ -#include <libgen.h> /* basename */ -#include <stdarg.h> -#include <stdio.h> -#include <unistd.h> -#include <time.h> -#include <sys/time.h> /* timespec */ -#include <sys/stat.h> /* S_IRUSR & S_IWUSR */ - -#include <omp.h> -#include <mpi.h> - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static void -print_out(const char* msg, void* ctx) -{ - ASSERT(msg); - (void)ctx; -#ifdef OS_UNIX - fprintf(stderr, "\x1b[1m\x1b[32m>\x1b[0m %s", msg); -#else - fprintf(stderr, "> %s", msg); -#endif -} - -static void -print_err(const char* msg, void* ctx) -{ - ASSERT(msg); - (void)ctx; -#ifdef OS_UNIX - fprintf(stderr, "\x1b[31merror:\x1b[0m %s", msg); -#else - fprintf(stderr, "error: %s", msg); -#endif -} - -static void -print_warn(const char* msg, void* ctx) -{ - ASSERT(msg); - (void)ctx; -#ifdef OS_UNIX - fprintf(stderr, "\x1b[33mwarning:\x1b[0m %s", msg); -#else - fprintf(stderr,"warning: %s", msg); -#endif -} - -static void -log_msg - (struct htrdr* htrdr, - const enum log_type stream, - const char* msg, - va_list vargs) -{ - ASSERT(htrdr && msg); - if(htrdr->verbose) { - CHK(logger_vprint(&htrdr->logger, stream, msg, vargs) == RES_OK); - } -} - -static enum htsky_spectral_type -htrdr_to_sky_spectral_type(const enum htrdr_spectral_type type) -{ - enum htsky_spectral_type spectype; - switch(type) { - case HTRDR_SPECTRAL_LW: - spectype = HTSKY_SPECTRAL_LW; - break; - case HTRDR_SPECTRAL_SW: - case HTRDR_SPECTRAL_SW_CIE_XYZ: - spectype = HTSKY_SPECTRAL_SW; - break; - default: FATAL("Unreachable code.\n"); break; - } - return spectype; -} - -static res_T -dump_buffer - (struct htrdr* htrdr, - struct htrdr_buffer* buf, - struct htrdr_accum* time_acc, /* May be NULL */ - struct htrdr_accum* flux_acc, /* May be NULL */ - const char* stream_name, - FILE* stream) -{ - struct htrdr_buffer_layout layout; - size_t pixsz, pixal; - size_t x, y; - res_T res = RES_OK; - ASSERT(htrdr && buf && stream_name && stream); - (void)stream_name; - - pixsz = htrdr_spectral_type_get_pixsz - (htrdr->spectral_type, htrdr->sensor.type); - pixal = htrdr_spectral_type_get_pixal - (htrdr->spectral_type, htrdr->sensor.type); - - htrdr_buffer_get_layout(buf, &layout); - if(layout.elmt_size != pixsz || layout.alignment != pixal) { - htrdr_log_err(htrdr, "%s: invalid buffer layout. ", FUNC_NAME); - res = RES_BAD_ARG; - goto error; - } - - fprintf(stream, "%lu %lu\n", layout.width, layout.height); - - if(time_acc) *time_acc = HTRDR_ACCUM_NULL; - if(flux_acc) *flux_acc = HTRDR_ACCUM_NULL; - FOR_EACH(y, 0, layout.height) { - FOR_EACH(x, 0, layout.width) { - struct htrdr_estimate pix_time = HTRDR_ESTIMATE_NULL; - const struct htrdr_accum* pix_time_acc = NULL; - - if(htrdr->sensor.type == HTRDR_SENSOR_RECTANGLE) { - const struct htrdr_pixel_flux* pix = htrdr_buffer_at(buf, x, y); - struct htrdr_estimate flux = HTRDR_ESTIMATE_NULL; - - if(pix->flux.nweights == 0) { - fprintf(stream, "0 0 0 0 0 0 "); - } else { - htrdr_accum_get_estimation(&pix->flux, &flux); - fprintf(stream, "%g %g 0 0 0 0 ", flux.E, flux.SE); - - if(flux_acc) { - flux_acc->sum_weights += pix->flux.sum_weights; - flux_acc->sum_weights_sqr += pix->flux.sum_weights_sqr; - flux_acc->nweights += pix->flux.nweights; - } - } - pix_time_acc = &pix->time; - - } else { - if(htrdr->spectral_type != HTRDR_SPECTRAL_SW_CIE_XYZ){ - const struct htrdr_pixel_xwave* pix = htrdr_buffer_at(buf, x, y); - fprintf(stream, "%g %g ", - pix->radiance_temperature.E, pix->radiance_temperature.SE); - fprintf(stream, "%g %g ", pix->radiance.E, pix->radiance.SE); - fprintf(stream, "0 0 "); - pix_time_acc = &pix->time; - - } else { - const struct htrdr_pixel_image* pix = htrdr_buffer_at(buf, x, y); - fprintf(stream, "%g %g ", pix->X.E, pix->X.SE); - fprintf(stream, "%g %g ", pix->Y.E, pix->Y.SE); - fprintf(stream, "%g %g ", pix->Z.E, pix->Z.SE); - pix_time_acc = &pix->time; - } - } - - htrdr_accum_get_estimation(pix_time_acc, &pix_time); - fprintf(stream, "%g %g\n", pix_time.E, pix_time.SE); - - if(time_acc) { - time_acc->sum_weights += pix_time_acc->sum_weights; - time_acc->sum_weights_sqr += pix_time_acc->sum_weights_sqr; - time_acc->nweights += pix_time_acc->nweights; - } - } - fprintf(stream, "\n"); - } - -exit: - return res; -error: - goto exit; -} - -static INLINE void -spherical_to_cartesian_dir - (const double azimuth, /* In radians */ - const double elevation, /* In radians */ - double dir[3]) -{ - double cos_azimuth; - double sin_azimuth; - double cos_elevation; - double sin_elevation; - ASSERT(azimuth >= 0 && azimuth < 2*PI); - ASSERT(elevation >= 0 && elevation <= PI/2.0); - ASSERT(dir); - - cos_azimuth = cos(azimuth); - sin_azimuth = sin(azimuth); - cos_elevation = cos(elevation); - sin_elevation = sin(elevation); - - dir[0] = cos_elevation * cos_azimuth; - dir[1] = cos_elevation * sin_azimuth; - dir[2] = sin_elevation; -} - -static void -release_mpi(struct htrdr* htrdr) -{ - ASSERT(htrdr); - if(htrdr->mpi_working_procs) { - MEM_RM(htrdr->allocator, htrdr->mpi_working_procs); - htrdr->mpi_working_procs = NULL; - } - if(htrdr->mpi_progress_octree) { - MEM_RM(htrdr->allocator, htrdr->mpi_progress_octree); - htrdr->mpi_progress_octree = NULL; - } - if(htrdr->mpi_progress_render) { - MEM_RM(htrdr->allocator, htrdr->mpi_progress_render); - htrdr->mpi_progress_render = NULL; - } - if(htrdr->mpi_err_str) { - MEM_RM(htrdr->allocator, htrdr->mpi_err_str); - htrdr->mpi_err_str = NULL; - } - if(htrdr->mpi_mutex) { - mutex_destroy(htrdr->mpi_mutex); - htrdr->mpi_mutex = NULL; - } -} - -static res_T -mpi_print_proc_info(struct htrdr* htrdr) -{ - char proc_name[MPI_MAX_PROCESSOR_NAME]; - int proc_name_len; - char* proc_names = NULL; - uint32_t* proc_nthreads = NULL; - uint32_t nthreads = 0; - int iproc; - res_T res = RES_OK; - ASSERT(htrdr); - - if(htrdr->mpi_rank == 0) { - proc_names = MEM_CALLOC(htrdr->allocator, (size_t)htrdr->mpi_nprocs, - MPI_MAX_PROCESSOR_NAME*sizeof(*proc_names)); - if(!proc_names) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "could not allocate the temporary memory for MPI process names -- " - "%s.\n", res_to_cstr(res)); - goto error; - } - - proc_nthreads = MEM_CALLOC(htrdr->allocator, (size_t)htrdr->mpi_nprocs, - sizeof(*proc_nthreads)); - if(!proc_nthreads) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "could not allocate the temporary memory for the #threads of the MPI " - "processes -- %s.\n", res_to_cstr(res)); - goto error; - } - } - - /* Gather process name */ - MPI(Get_processor_name(proc_name, &proc_name_len)); - MPI(Gather(proc_name, MPI_MAX_PROCESSOR_NAME, MPI_CHAR, proc_names, - MPI_MAX_PROCESSOR_NAME, MPI_CHAR, 0, MPI_COMM_WORLD)); - - /* Gather process #threads */ - nthreads = (uint32_t)htrdr->nthreads; - MPI(Gather(&nthreads, 1, MPI_UINT32_T, proc_nthreads, 1, MPI_UINT32_T, 0, - MPI_COMM_WORLD)); - - if(htrdr->mpi_rank == 0) { - FOR_EACH(iproc, 0, htrdr->mpi_nprocs) { - htrdr_log(htrdr, "Process %d -- %s; #threads: %u\n", - iproc, proc_names + iproc*MPI_MAX_PROCESSOR_NAME, proc_nthreads[iproc]); - } - } - -exit: - if(proc_names) MEM_RM(htrdr->allocator, proc_names); - if(proc_nthreads) MEM_RM(htrdr->allocator, proc_nthreads); - return res; -error: - goto exit; -} - -static res_T -init_mpi(struct htrdr* htrdr) -{ - size_t n; - int err; - res_T res = RES_OK; - ASSERT(htrdr); - - htrdr->mpi_err_str = MEM_CALLOC - (htrdr->allocator, htrdr->nthreads, MPI_MAX_ERROR_STRING); - if(!htrdr->mpi_err_str) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "could not allocate the MPI error strings -- %s.\n", - res_to_cstr(res)); - goto error; - } - - err = MPI_Comm_rank(MPI_COMM_WORLD, &htrdr->mpi_rank); - if(err != MPI_SUCCESS) { - htrdr_log_err(htrdr, - "could not determine the MPI rank of the calling process -- %s.\n", - htrdr_mpi_error_string(htrdr, err)); - res = RES_UNKNOWN_ERR; - goto error; - } - - err = MPI_Comm_size(MPI_COMM_WORLD, &htrdr->mpi_nprocs); - if(err != MPI_SUCCESS) { - htrdr_log_err(htrdr, - "could retrieve the size of the MPI group -- %s.\n", - htrdr_mpi_error_string(htrdr, err)); - res = RES_UNKNOWN_ERR; - goto error; - } - - htrdr->mpi_working_procs = MEM_CALLOC(htrdr->allocator, - (size_t)htrdr->mpi_nprocs, sizeof(*htrdr->mpi_working_procs)); - if(!htrdr->mpi_working_procs) { - htrdr_log_err(htrdr, - "could not allocate the list of working processes.\n"); - res = RES_MEM_ERR; - goto error; - } - - /* Initialy, all the processes are working */ - htrdr->mpi_nworking_procs = (size_t)htrdr->mpi_nprocs; - memset(htrdr->mpi_working_procs, 0xFF, - htrdr->mpi_nworking_procs*sizeof(*htrdr->mpi_working_procs)); - - /* Allocate #processes progress statuses on the master process and only 1 - * progress status on the other ones: the master process will gather the - * status of the other processes to report their progression. */ - n = (size_t)(htrdr->mpi_rank == 0 ? htrdr->mpi_nprocs : 1); - - htrdr->mpi_progress_octree = MEM_CALLOC - (htrdr->allocator, n, sizeof(*htrdr->mpi_progress_octree)); - if(!htrdr->mpi_progress_octree) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "could not allocate the progress state of the octree building -- %s.\n", - res_to_cstr(res)); - goto error; - } - - htrdr->mpi_progress_render = MEM_CALLOC - (htrdr->allocator, n, sizeof(*htrdr->mpi_progress_render)); - if(!htrdr->mpi_progress_render) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "could not allocate the progress state of the scene rendering -- %s.\n", - res_to_cstr(res)); - goto error; - } - - htrdr->mpi_mutex = mutex_create(); - if(!htrdr->mpi_mutex) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "could not create the mutex to protect MPI calls from concurrent " - "threads -- %s.\n", res_to_cstr(res)); - goto error; - } - - mpi_print_proc_info(htrdr); - -exit: - return res; -error: - release_mpi(htrdr); - goto exit; -} - -static res_T -setup_sensor(struct htrdr* htrdr, const struct htrdr_args* args) -{ - double proj_ratio; - res_T res = RES_OK; - ASSERT(htrdr && args); - - htrdr->sensor.type = args->sensor_type; - - if(args->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ - && args->sensor_type != HTRDR_SENSOR_CAMERA) { - htrdr_log_err(htrdr, "the CIE 1931 XYZ spectral integration can be used " - "only with a camera sensor.\n"); - res = RES_BAD_ARG; - goto error; - } - - switch(args->sensor_type) { - case HTRDR_SENSOR_CAMERA: - proj_ratio = - (double)args->image.definition[0] - / (double)args->image.definition[1]; - res = htrdr_camera_create(htrdr, args->camera.pos, args->camera.tgt, - args->camera.up, proj_ratio, MDEG2RAD(args->camera.fov_y), - &htrdr->sensor.camera); - break; - case HTRDR_SENSOR_RECTANGLE: - res = htrdr_rectangle_create(htrdr, args->rectangle.sz, - args->rectangle.pos, args->rectangle.tgt, args->rectangle.up, - &htrdr->sensor.rectangle); - break; - default: FATAL("Unreachable code.\n"); break; - } - if(res != RES_OK) goto error; - -exit: - return res; -error: - goto exit; -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_init - (struct mem_allocator* mem_allocator, - const struct htrdr_args* args, - struct htrdr* htrdr) -{ - struct htsky_args htsky_args = HTSKY_ARGS_DEFAULT; - double sun_dir[3]; - double spectral_range[2]; - const char* output_name = NULL; - size_t ithread; - int nthreads_max; - res_T res = RES_OK; - ASSERT(args && htrdr); - - memset(htrdr, 0, sizeof(*htrdr)); - - htrdr->allocator = mem_allocator ? mem_allocator : &mem_default_allocator; - - logger_init(htrdr->allocator, &htrdr->logger); - logger_set_stream(&htrdr->logger, LOG_OUTPUT, print_out, NULL); - logger_set_stream(&htrdr->logger, LOG_ERROR, print_err, NULL); - logger_set_stream(&htrdr->logger, LOG_WARNING, print_warn, NULL); - str_init(htrdr->allocator, &htrdr->output_name); - nthreads_max = MMAX(omp_get_max_threads(), omp_get_num_procs()); - htrdr->dump_vtk = args->dump_vtk; - htrdr->verbose = args->verbose; - htrdr->nthreads = MMIN(args->nthreads, (unsigned)nthreads_max); - htrdr->spp = args->image.spp; - htrdr->width = args->image.definition[0]; - htrdr->height = args->image.definition[1]; - htrdr->grid_max_definition[0] = args->grid_max_definition[0]; - htrdr->grid_max_definition[1] = args->grid_max_definition[1]; - htrdr->grid_max_definition[2] = args->grid_max_definition[2]; - htrdr->spectral_type = args->spectral_type; - htrdr->ref_temperature = args->ref_temperature; - htrdr->sky_mtl_name = args->sky_mtl_name; - - res = init_mpi(htrdr); - if(res != RES_OK) goto error; - - if(!args->output) { - htrdr->output = stdout; - output_name = "<stdout>"; - } else if(htrdr->mpi_rank != 0) { - htrdr->output = NULL; - output_name = "<null>"; - } else { - res = open_output_stream - (htrdr, args->output, 0/*read*/, args->force_overwriting, &htrdr->output); - if(res != RES_OK) goto error; - output_name = args->output; - } - res = str_set(&htrdr->output_name, output_name); - if(res != RES_OK) { - htrdr_log_err(htrdr, - "%s: could not store the name of the output stream `%s' -- %s.\n", - FUNC_NAME, output_name, res_to_cstr(res)); - goto error; - } - - /* Disable the Star-3D verbosity since the Embree backend prints some messages - * on stdout rather than stderr. This is annoying since stdout may be used by - * htrdr to write output data */ - res = s3d_device_create - (&htrdr->logger, htrdr->allocator, 0, &htrdr->s3d); - if(res != RES_OK) { - htrdr_log_err(htrdr, - "%s: could not create the Star-3D device -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - - /* Materials are necessary only if a ground geometry is defined */ - if(args->filename_obj) { - res = htrdr_materials_create(htrdr, args->filename_mtl, &htrdr->mats); - if(res != RES_OK) goto error; - } - - res = htrdr_ground_create(htrdr, args->filename_obj, args->repeat_ground, - &htrdr->ground); - if(res != RES_OK) goto error; - - res = setup_sensor(htrdr, args); - if(res != RES_OK) goto error; - - res = htrdr_sun_create(htrdr, &htrdr->sun); - if(res != RES_OK) goto error; - spherical_to_cartesian_dir - (MDEG2RAD(args->sun_azimuth), MDEG2RAD(args->sun_elevation), sun_dir); - htrdr_sun_set_direction(htrdr->sun, sun_dir); - - htsky_args.htcp_filename = args->filename_les; - htsky_args.htgop_filename = args->filename_gas; - htsky_args.htmie_filename = args->filename_mie; - htsky_args.cache_filename = args->cache; - htsky_args.grid_max_definition[0] = args->grid_max_definition[0]; - htsky_args.grid_max_definition[1] = args->grid_max_definition[1]; - htsky_args.grid_max_definition[2] = args->grid_max_definition[2]; - htsky_args.optical_thickness = args->optical_thickness; - htsky_args.nthreads = htrdr->nthreads; - htsky_args.repeat_clouds = args->repeat_clouds; - htsky_args.verbose = htrdr->mpi_rank == 0 ? args->verbose : 0; - htsky_args.spectral_type = htrdr_to_sky_spectral_type(args->spectral_type); - htsky_args.wlen_range[0] = args->wlen_range[0]; - htsky_args.wlen_range[1] = args->wlen_range[1]; - res = htsky_create(&htrdr->logger, htrdr->allocator, &htsky_args, &htrdr->sky); - if(res != RES_OK) goto error; - - HTSKY(get_raw_spectral_bounds(htrdr->sky, spectral_range)); - - spectral_range[0] = MMAX(args->wlen_range[0], spectral_range[0]); - spectral_range[1] = MMIN(args->wlen_range[1], spectral_range[1]); - if(spectral_range[0] != args->wlen_range[0] - || spectral_range[1] != args->wlen_range[1]) { - htrdr_log_warn(htrdr, - "%s: the submitted spectral range overflowed the spectral data.\n", FUNC_NAME); - } - - htrdr->wlen_range_m[0] = spectral_range[0]*1e-9; /* Convert in meters */ - htrdr->wlen_range_m[1] = spectral_range[1]*1e-9; /* Convert in meters */ - - if(htrdr->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ) { - size_t n; - n = (size_t)(spectral_range[1] - spectral_range[0]); - res = htrdr_cie_xyz_create(htrdr, spectral_range, n, &htrdr->cie); - if(res != RES_OK) goto error; - - } else { - size_t n; - - if(htrdr->ref_temperature <= 0) { - htrdr_log_err(htrdr, "%s: invalid reference temperature %g K.\n", - FUNC_NAME, htrdr->ref_temperature); - res = RES_BAD_ARG; - goto error; - } - - ASSERT(htrdr->wlen_range_m[0] <= htrdr->wlen_range_m[1]); - n = (size_t)(spectral_range[1] - spectral_range[0]); - - res = htrdr_ran_wlen_create - (htrdr, spectral_range, n, htrdr->ref_temperature, &htrdr->ran_wlen); - if(res != RES_OK) goto error; - } - - htrdr->lifo_allocators = MEM_CALLOC - (htrdr->allocator, htrdr->nthreads, sizeof(*htrdr->lifo_allocators)); - if(!htrdr->lifo_allocators) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "%s: could not allocate the list of per thread LIFO allocator -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - - FOR_EACH(ithread, 0, htrdr->nthreads) { - res = mem_init_lifo_allocator - (&htrdr->lifo_allocators[ithread], htrdr->allocator, 16384); - if(res != RES_OK) { - htrdr_log_err(htrdr, - "%s: could not initialise the LIFO allocator of the thread %lu -- %s.\n", - FUNC_NAME, (unsigned long)ithread, res_to_cstr(res)); - goto error; - } - } - - /* Create the image buffer only on the master process; the image parts - * rendered by the processes are gathered onto the master process. */ - if(!htrdr->dump_vtk && htrdr->mpi_rank == 0) { - const size_t pixsz = htrdr_spectral_type_get_pixsz - (htrdr->spectral_type, htrdr->sensor.type); - const size_t pixal = htrdr_spectral_type_get_pixal - (htrdr->spectral_type, htrdr->sensor.type); - - res = htrdr_buffer_create(htrdr, - args->image.definition[0], /* Width */ - args->image.definition[1], /* Height */ - args->image.definition[0] * pixsz, /* Pitch */ - pixsz, /* Size of a pixel */ - pixal, /* Alignment of a pixel */ - &htrdr->buf); - if(res != RES_OK) goto error; - } - -exit: - return res; -error: - htrdr_release(htrdr); - goto exit; -} - -void -htrdr_release(struct htrdr* htrdr) -{ - ASSERT(htrdr); - release_mpi(htrdr); - if(htrdr->s3d) S3D(device_ref_put(htrdr->s3d)); - if(htrdr->ground) htrdr_ground_ref_put(htrdr->ground); - if(htrdr->sky) HTSKY(ref_put(htrdr->sky)); - if(htrdr->sun) htrdr_sun_ref_put(htrdr->sun); - if(htrdr->sensor.camera) htrdr_camera_ref_put(htrdr->sensor.camera); - if(htrdr->sensor.rectangle) htrdr_rectangle_ref_put(htrdr->sensor.rectangle); - if(htrdr->buf) htrdr_buffer_ref_put(htrdr->buf); - if(htrdr->mats) htrdr_materials_ref_put(htrdr->mats); - if(htrdr->cie) htrdr_cie_xyz_ref_put(htrdr->cie); - if(htrdr->ran_wlen) htrdr_ran_wlen_ref_put(htrdr->ran_wlen); - if(htrdr->output && htrdr->output != stdout) fclose(htrdr->output); - if(htrdr->lifo_allocators) { - size_t i; - FOR_EACH(i, 0, htrdr->nthreads) { - mem_shutdown_lifo_allocator(&htrdr->lifo_allocators[i]); - } - MEM_RM(htrdr->allocator, htrdr->lifo_allocators); - } - str_release(&htrdr->output_name); - logger_release(&htrdr->logger); -} - -res_T -htrdr_run(struct htrdr* htrdr) -{ - res_T res = RES_OK; - if(htrdr->dump_vtk) { - const size_t nbands = htsky_get_spectral_bands_count(htrdr->sky); - size_t i; - - /* Nothing to do */ - if(htrdr->mpi_rank != 0) goto exit; - - FOR_EACH(i, 0, nbands) { - const size_t iband = htsky_get_spectral_band_id(htrdr->sky, i); - const size_t nquads = htsky_get_spectral_band_quadrature_length - (htrdr->sky, iband); - size_t iquad; - FOR_EACH(iquad, 0, nquads) { - res = htsky_dump_cloud_vtk(htrdr->sky, iband, iquad, htrdr->output); - if(res != RES_OK) goto error; - fprintf(htrdr->output, "---\n"); - } - } - } else { - res = htrdr_draw_map(htrdr, &htrdr->sensor, htrdr->width, htrdr->height, - htrdr->spp, htrdr->buf); - if(res != RES_OK) goto error; - if(htrdr->mpi_rank == 0) { - struct htrdr_accum path_time_acc = HTRDR_ACCUM_NULL; - struct htrdr_accum flux_acc = HTRDR_ACCUM_NULL; - struct htrdr_estimate path_time; - struct htrdr_estimate flux; - - res = dump_buffer(htrdr, htrdr->buf, &path_time_acc, &flux_acc, - str_cget(&htrdr->output_name), htrdr->output); - if(res != RES_OK) goto error; - - htrdr_accum_get_estimation(&path_time_acc, &path_time); - htrdr_log(htrdr, - "Time per radiative path (in micro seconds): %g +/- %g\n", - path_time.E, - path_time.SE); - - if(htrdr->sensor.type == HTRDR_SENSOR_RECTANGLE) { - htrdr_accum_get_estimation(&flux_acc, &flux); - htrdr_log(htrdr, - "Radiative flux density (in W/(external m^2)): %g +/- %g\n", - flux.E, - flux.SE); - } - } - } -exit: - return res; -error: - goto exit; -} - -void -htrdr_log(struct htrdr* htrdr, const char* msg, ...) -{ - ASSERT(htrdr && msg); - /* Log standard message only on master process */ - if(htrdr->mpi_rank == 0) { - va_list vargs_list; - va_start(vargs_list, msg); - log_msg(htrdr, LOG_OUTPUT, msg, vargs_list); - va_end(vargs_list); - } -} - -void -htrdr_log_err(struct htrdr* htrdr, const char* msg, ...) -{ - va_list vargs_list; - ASSERT(htrdr && msg); - /* Log errors on all processes */ - va_start(vargs_list, msg); - log_msg(htrdr, LOG_ERROR, msg, vargs_list); - va_end(vargs_list); -} - -void -htrdr_log_warn(struct htrdr* htrdr, const char* msg, ...) -{ - ASSERT(htrdr && msg); - /* Log warnings only on master process */ - if(htrdr->mpi_rank == 0) { - va_list vargs_list; - va_start(vargs_list, msg); - log_msg(htrdr, LOG_WARNING, msg, vargs_list); - va_end(vargs_list); - } -} - -const char* -htrdr_mpi_error_string(struct htrdr* htrdr, const int mpi_err) -{ - const int ithread = omp_get_thread_num(); - char* str; - int strlen_err; - int err; - ASSERT(htrdr && (size_t)ithread < htrdr->nthreads); - str = htrdr->mpi_err_str + ithread*MPI_MAX_ERROR_STRING; - err = MPI_Error_string(mpi_err, str, &strlen_err); - return err == MPI_SUCCESS ? str : "Invalid MPI error"; -} - -void -htrdr_fprintf(struct htrdr* htrdr, FILE* stream, const char* msg, ...) -{ - ASSERT(htrdr && msg); - if(htrdr->mpi_rank == 0) { - va_list vargs_list; - va_start(vargs_list, msg); - vfprintf(stream, msg, vargs_list); - va_end(vargs_list); - } -} - -void -htrdr_fflush(struct htrdr* htrdr, FILE* stream) -{ - ASSERT(htrdr); - if(htrdr->mpi_rank == 0) { - fflush(stream); - } -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -double -compute_sky_min_band_len - (struct htsky* sky, - const double range[2]) -{ - double min_band_len = DBL_MAX; - size_t nbands; - ASSERT(sky && range && range[0] <= range[1]); - - nbands = htsky_get_spectral_bands_count(sky); - - if(eq_eps(range[0], range[1], 1.e-6)) { - ASSERT(nbands == 1); - min_band_len = 0; - } else { - size_t i = 0; - - /* Compute the length of the current band clamped to the submitted range */ - FOR_EACH(i, 0, nbands) { - const size_t iband = htsky_get_spectral_band_id(sky, i); - double wlens[2]; - HTSKY(get_spectral_band_bounds(sky, iband, wlens)); - - /* Adjust band boundaries to the submitted range */ - wlens[0] = MMAX(wlens[0], range[0]); - wlens[1] = MMIN(wlens[1], range[1]); - - min_band_len = MMIN(wlens[1] - wlens[0], min_band_len); - } - } - return min_band_len; -} - -res_T -open_output_stream - (struct htrdr* htrdr, - const char* filename, - const int read, - int force_overwrite, - FILE** out_fp) -{ - FILE* fp = NULL; - int fd = -1; - const char* mode; - res_T res = RES_OK; - ASSERT(htrdr && filename && out_fp); - - mode = read ? "w+" : "w"; - - if(force_overwrite) { - fp = fopen(filename, mode); - if(!fp) { - htrdr_log_err(htrdr, "could not open the output file `%s'.\n", filename); - goto error; - } - } else { - const int access_flags = read ? O_RDWR : O_WRONLY; - fd = open(filename, O_CREAT|O_EXCL|O_TRUNC|access_flags, S_IRUSR|S_IWUSR); - if(fd >= 0) { - fp = fdopen(fd, mode); - if(fp == NULL) { - htrdr_log_err(htrdr, "could not open the output file `%s'.\n", filename); - goto error; - } - } else if(errno == EEXIST) { - htrdr_log_err(htrdr, "the output file `%s' already exists. \n", - filename); - goto error; - } else { - htrdr_log_err(htrdr, - "unexpected error while opening the output file `%s'.\n", filename); - goto error; - } - } -exit: - *out_fp = fp; - return res; -error: - res = RES_IO_ERR; - if(fp) { - CHK(fclose(fp) == 0); - fp = NULL; - } else if(fd >= 0) { - CHK(close(fd) == 0); - } - goto exit; -} - -void -send_mpi_progress - (struct htrdr* htrdr, const enum htrdr_mpi_message msg, int32_t percent) -{ - ASSERT(htrdr); - ASSERT(msg == HTRDR_MPI_PROGRESS_RENDERING - || msg == HTRDR_MPI_PROGRESS_BUILD_OCTREE); - (void)htrdr; - mutex_lock(htrdr->mpi_mutex); - MPI(Send(&percent, 1, MPI_INT32_T, 0, msg, MPI_COMM_WORLD)); - mutex_unlock(htrdr->mpi_mutex); -} - -void -fetch_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg) -{ - struct timespec t; - int32_t* progress = NULL; - int iproc; - ASSERT(htrdr && htrdr->mpi_rank == 0); - - t.tv_sec = 0; - t.tv_nsec = 10000000; /* 10ms */ - - switch(msg) { - case HTRDR_MPI_PROGRESS_BUILD_OCTREE: - progress = htrdr->mpi_progress_octree; - break; - case HTRDR_MPI_PROGRESS_RENDERING: - progress = htrdr->mpi_progress_render; - break; - default: FATAL("Unreachable code.\n"); break; - } - - FOR_EACH(iproc, 1, htrdr->mpi_nprocs) { - /* Flush the last sent percentage of the process `iproc' */ - for(;;) { - MPI_Request req; - int flag; - int complete; - - mutex_lock(htrdr->mpi_mutex); - MPI(Iprobe(iproc, msg, MPI_COMM_WORLD, &flag, MPI_STATUS_IGNORE)); - mutex_unlock(htrdr->mpi_mutex); - - if(flag == 0) break; /* No more message */ - - mutex_lock(htrdr->mpi_mutex); - MPI(Irecv(&progress[iproc], 1, MPI_INT32_T, iproc, msg, MPI_COMM_WORLD, &req)); - mutex_unlock(htrdr->mpi_mutex); - for(;;) { - mutex_lock(htrdr->mpi_mutex); - MPI(Test(&req, &complete, MPI_STATUS_IGNORE)); - mutex_unlock(htrdr->mpi_mutex); - if(complete) break; - nanosleep(&t, NULL); - } - } - } -} - -void -print_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg) -{ - ASSERT(htrdr && htrdr->mpi_rank == 0); - - if(htrdr->mpi_nprocs == 1) { - switch(msg) { - case HTRDR_MPI_PROGRESS_BUILD_OCTREE: - htrdr_fprintf(htrdr, stderr, "\033[2K\rBuilding octree: %3d%%", - htrdr->mpi_progress_octree[0]); - break; - case HTRDR_MPI_PROGRESS_RENDERING: - htrdr_fprintf(htrdr, stderr, "\033[2K\rRendering: %3d%%", - htrdr->mpi_progress_render[0]); - break; - default: FATAL("Unreachable code.\n"); break; - } - htrdr_fflush(htrdr, stderr); - } else { - int iproc; - FOR_EACH(iproc, 0, htrdr->mpi_nprocs) { - switch(msg) { - case HTRDR_MPI_PROGRESS_BUILD_OCTREE: - htrdr_fprintf(htrdr, stderr, - "\033[2K\rProcess %d -- building octree: %3d%%%c", - iproc, htrdr->mpi_progress_octree[iproc], - iproc == htrdr->mpi_nprocs - 1 ? '\r' : '\n'); - break; - case HTRDR_MPI_PROGRESS_RENDERING: - htrdr_fprintf(htrdr, stderr, - "\033[2K\rProcess %d -- rendering: %3d%%%c", - iproc, htrdr->mpi_progress_render[iproc], - iproc == htrdr->mpi_nprocs - 1 ? '\r' : '\n'); - break; - default: FATAL("Unreachable code.\n"); break; - } - } - } -} - -void -clear_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message msg) -{ - ASSERT(htrdr); - (void)msg; - if(htrdr->mpi_nprocs > 1) { - htrdr_fprintf(htrdr, stderr, "\033[%dA", htrdr->mpi_nprocs-1); - } -} - -int -total_mpi_progress(const struct htrdr* htrdr, const enum htrdr_mpi_message msg) -{ - const int* progress = NULL; - int total = 0; - int iproc; - ASSERT(htrdr && htrdr->mpi_rank == 0); - - switch(msg) { - case HTRDR_MPI_PROGRESS_BUILD_OCTREE: - progress = htrdr->mpi_progress_octree; - break; - case HTRDR_MPI_PROGRESS_RENDERING: - progress = htrdr->mpi_progress_render; - break; - default: FATAL("Unreachable code.\n"); break; - } - - FOR_EACH(iproc, 0, htrdr->mpi_nprocs) { - total += progress[iproc]; - } - total = total / htrdr->mpi_nprocs; - return total; -} - diff --git a/src/htrdr.h b/src/htrdr.h @@ -1,163 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_H -#define HTRDR_H - -#include "htrdr_sensor.h" -#include "htrdr_spectral.h" - -#include <rsys/logger.h> -#include <rsys/ref_count.h> -#include <rsys/str.h> - -/* Helper macro that asserts if the invocation of the htrdr function `Func' - * returns an error. One should use this macro on htrdr function calls for - * which no explicit error checking is performed */ -#ifndef NDEBUG - #define HTRDR(Func) ASSERT(htrdr_ ## Func == RES_OK) -#else - #define HTRDR(Func) htrdr_ ## Func -#endif - -/* Forward declarations */ -struct htsky; -struct htrdr_args; -struct htrdr_buffer; -struct htrdr_cie_xyz; -struct htrdr_materials; -struct htrdr_rectangle; -struct mem_allocator; -struct mutext; -struct s3d_device; -struct s3d_scene; -struct ssf_bsdf; -struct ssf_phase; - -struct htrdr { - struct s3d_device* s3d; - - struct htrdr_ground* ground; - struct htrdr_materials* mats; - struct htrdr_sun* sun; - struct htrdr_cie_xyz* cie; - struct htrdr_ran_wlen* ran_wlen; - - struct htrdr_sensor sensor; - struct htrdr_buffer* buf; - - struct htsky* sky; - const char* sky_mtl_name; - enum htrdr_spectral_type spectral_type; - double wlen_range_m[2]; /* Integration range in *meters* */ - double ref_temperature; /* Reference temperature in Kelvin */ - - size_t spp; /* #samples per pixel */ - size_t width; /* Image width */ - size_t height; /* Image height */ - - FILE* output; - struct str output_name; - - unsigned grid_max_definition[3]; /* Max definition of the acceleration grids */ - unsigned nthreads; /* #threads of the process */ - int dump_vtk; /* Dump octree VTK */ - int verbose; /* Verbosity level */ - - int mpi_rank; /* Rank of the process in the MPI group */ - int mpi_nprocs; /* Overall #processes in the MPI group */ - char* mpi_err_str; /* Temp buffer used to store MPI error string */ - int8_t* mpi_working_procs; /* Define the rank of active processes */ - size_t mpi_nworking_procs; - - /* Process progress percentage */ - int32_t* mpi_progress_octree; - int32_t* mpi_progress_render; - - struct mutex* mpi_mutex; /* Protect MPI calls from concurrent threads */ - - struct logger logger; - struct mem_allocator* allocator; - struct mem_allocator* lifo_allocators; /* Per thread lifo allocator */ -}; - -extern LOCAL_SYM res_T -htrdr_init - (struct mem_allocator* allocator, - const struct htrdr_args* args, - struct htrdr* htrdr); - -extern LOCAL_SYM void -htrdr_release - (struct htrdr* htrdr); - -extern LOCAL_SYM res_T -htrdr_run - (struct htrdr* htrdr); - -extern LOCAL_SYM void -htrdr_log - (struct htrdr* htrdr, - const char* msg, - ...) -#ifdef COMPILER_GCC - __attribute((format(printf, 2, 3))) -#endif - ; - -extern LOCAL_SYM void -htrdr_log_err - (struct htrdr* htrdr, - const char* msg, - ...) -#ifdef COMPILER_GCC - __attribute((format(printf, 2, 3))) -#endif - ; - -extern LOCAL_SYM void -htrdr_log_warn - (struct htrdr* htrdr, - const char* msg, - ...) -#ifdef COMPILER_GCC - __attribute((format(printf, 2, 3))) -#endif - ; - -extern LOCAL_SYM const char* -htrdr_mpi_error_string - (struct htrdr* htrdr, - const int mpi_err); - -extern LOCAL_SYM void -htrdr_fprintf - (struct htrdr* htrdr, - FILE* stream, - const char* msg, - ...) -#ifdef COMPILER_GCC - __attribute((format(printf, 3, 4))) -#endif - ; - -extern LOCAL_SYM void -htrdr_fflush - (struct htrdr* htrdr, - FILE* stream); - -#endif /* HTRDR_H */ - diff --git a/src/htrdr_args.c b/src/htrdr_args.c @@ -1,644 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#define _POSIX_C_SOURCE 2 /* strtok_r support */ - -#include "htrdr_args.h" -#include "htrdr_version.h" - -#include <rsys/cstr.h> -#include <rsys/double3.h> - -#include <getopt.h> -#include <string.h> - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static void -print_help(const char* cmd) -{ - ASSERT(cmd); - printf("Usage: %s [OPION]... -a ATMOSPHERE\n", cmd); - printf( -"Render an image or compute a flux map for scenes composed of an\n" -"atmospheric gas mixture, clouds and a ground.\n\n"); - printf( -" -a ATMOSPHERE gas optical properties of the atmosphere.\n"); - printf( -" -c CLOUDS properties of the clouds.\n"); - printf( -" -C <camera> define the rendering point of view. Refer to the\n" -" htrdr man page for the list of camera options.\n"); - printf( -" -D AZIMUTH,ELEVATION\n" -" direction in degrees toward the sun center. By default\n" -" AZIMUTH is %g and ELEVATION is %g.\n", - HTRDR_ARGS_DEFAULT.sun_azimuth, - HTRDR_ARGS_DEFAULT.sun_elevation); - printf( -" -d dump octrees data to OUTPUT and exit.\n"); - printf( -" -f overwrite the OUTPUT file if it already exists.\n"); - printf( -" -g GROUND ground geometry.\n"); - printf( -" -h display this help and exit.\n"); - printf( -" -i <image> define the image to compute. Refer to the htrdr man\n" -" page for the list of image options\n"); - printf( -" -M MATERIALS file listing the ground materials.\n"); - printf( -" -m MIE file of Mie's data.\n"); - printf( -" -n SKY-NAME name used to identify the sky in the MATERIALS file.\n" -" Its default value is `%s'.\n", - HTRDR_ARGS_DEFAULT.sky_mtl_name); - printf( -" -O CACHE name of the cache file used to store/restore the sky\n" -" data.\n"); - printf( -" -o OUTPUT file where data are written. If not defined, data are\n" -" written to standard output.\n"); - printf( -" -p <rectangle> switch in flux computation by defining the rectangular\n" -" sensor onto wich the flux is computed. Refer to the\n" -" htrdr man page for the list of rectangle options.\n"); - printf( -" -R infinitely repeat the ground along the X and Y axis.\n"); - printf( -" -r infinitely repeat the clouds along the X and Y axis.\n"); - printf( -" -s <spectral> define the type and range of the spectral\n" -" integration. Refer to the htrdr man page for the list\n" -" of spectral options\n"); - printf( -" -T THRESHOLD optical thickness used as threshold during the octree\n" -" building. By default its value is `%g'.\n", - HTRDR_ARGS_DEFAULT.optical_thickness); - printf( -" -t THREADS hint on the number of threads to use. By default use\n" -" as many threads as CPU cores.\n"); - printf( -" -V X,Y,Z maximum definition of the cloud acceleration grids\n" -" along the 3 axis. By default use the definition of\n" -" the clouds\n"); - printf( -" -v make the program verbose.\n"); - printf( -" --version display version information and exit.\n"); - printf("\n"); - printf( -"Copyright (C) 2018, 2019, 2020 |Meso|Star> <contact@meso-star.com>.\n" -"Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier. htrdr is free\n" -"software released under the GNU GPL license, version 3 or later. You\n" -"are free to change or redistribute it under certain conditions\n" -"<http://gnu.org/licenses/gpl.html>.\n"); -} - -static INLINE res_T -parse_doubleX(const char* str, double* val, const size_t sz) -{ - size_t len; - res_T res = RES_OK; - ASSERT(str && val); - res = cstr_to_list_double(str, ',', val, &len, sz); - if(res == RES_OK && len != sz) res = RES_BAD_ARG; - return res; -} - -static INLINE res_T -parse_definition(const char* str, unsigned val[2]) -{ - size_t len; - res_T res = RES_OK; - ASSERT(str && val); - res = cstr_to_list_uint(str, 'x', val, &len, 2); - if(res != RES_OK) return res; - if(len != 2) return RES_BAD_ARG; - if(val[0] > 16384 || val[1] > 16384) return RES_BAD_ARG; - return RES_OK; -} - -static res_T -parse_fov(const char* str, double* out_fov) -{ - double fov; - res_T res = RES_OK; - ASSERT(str && out_fov); - - res = cstr_to_double(str, &fov); - if(res != RES_OK) { - fprintf(stderr, "Invalid field of view `%s'.\n", str); - return RES_BAD_ARG; - } - if(fov <= 0 || fov >= 180) { - fprintf(stderr, "The field of view %g is not in [30, 120].\n", fov); - return RES_BAD_ARG; - } - *out_fov = fov; - return RES_OK; -} - -static res_T -parse_image_parameter(struct htrdr_args* args, const char* str) -{ - char buf[128]; - char* key; - char* val; - char* ctx; - res_T res = RES_OK; - ASSERT(str && args); - - if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { - fprintf(stderr, - "Could not duplicate the image option string `%s'.\n", str); - res = RES_MEM_ERR; - goto error; - } - strncpy(buf, str, sizeof(buf)); - - key = strtok_r(buf, "=", &ctx); - val = strtok_r(NULL, "", &ctx); - - if(!val) { - fprintf(stderr, "Missing a value to the image option `%s'.\n", key); - res = RES_BAD_ARG; - goto error; - } - - #define PARSE(Name, Func) \ - res = Func; \ - if(res != RES_OK) { \ - fprintf(stderr, "Invalid image "Name" `%s'.\n", val); \ - goto error; \ - } (void)0 - if(!strcmp(key, "def")) { - PARSE("definition", parse_definition(val, args->image.definition)); - } else if(!strcmp(key, "spp")) { - PARSE("#samples per pixel", cstr_to_uint(val, &args->image.spp)); - } else { - fprintf(stderr, "Invalid image parameter `%s'.\n", key); - res = RES_BAD_ARG; - goto error; - } - #undef PARSE - - if(!args->image.definition[0] || !args->image.definition[1]) { - fprintf(stderr, "The image definition cannot be null.n"); - res = RES_BAD_ARG; - goto error; - } - if(!args->image.spp) { - fprintf(stderr, "The number of samples per pixel cannot be null.\n"); - res = RES_BAD_ARG; - goto error; - } - -exit: - return res; -error: - goto exit; -} - -static res_T -parse_camera_parameter(struct htrdr_args* args, const char* str) -{ - char buf[128]; - char* key; - char* val; - char* ctx; - res_T res = RES_OK; - ASSERT(args); - - if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { - fprintf(stderr, - "Could not duplicate the camera option string `%s'.\n", str); - res = RES_MEM_ERR; - goto error; - } - strncpy(buf, str, sizeof(buf)); - - key = strtok_r(buf, "=", &ctx); - val = strtok_r(NULL, "", &ctx); - - if(!val) { - fprintf(stderr, "Missing value to the camera option `%s'.\n", key); - res = RES_BAD_ARG; - goto error; - } - - #define PARSE(Name, Func) { \ - if(RES_OK != (res = Func)) { \ - fprintf(stderr, "Invalid camera "Name" `%s'.\n", val); \ - goto error; \ - } \ - } (void)0 - if(!strcmp(key, "pos")) { - PARSE("position", parse_doubleX(val, args->camera.pos, 3)); - } else if(!strcmp(key, "tgt")) { - PARSE("target", parse_doubleX(val, args->camera.tgt, 3)); - } else if(!strcmp(key, "up")) { - PARSE("up vector", parse_doubleX(val, args->camera.up, 3)); - } else if(!strcmp(key, "fov")) { - PARSE("field-of-view", parse_fov(val, &args->camera.fov_y)); - } else { - fprintf(stderr, "Invalid camera parameter `%s'.\n", key); - res = RES_BAD_ARG; - goto error; - } - #undef PARSE -exit: - return res; -error: - goto exit; -} - -static res_T -parse_rectangle_parameter(struct htrdr_args* args, const char* str) -{ - char buf[128]; - char* key; - char* val; - char* ctx; - res_T res = RES_OK; - ASSERT(args); - - if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { - fprintf(stderr, - "Could not duplicate the rectangle option string `%s'.\n", str); - res = RES_MEM_ERR; - goto error; - } - strncpy(buf, str, sizeof(buf)); - - /* pos=0,0,10.1; key <- pos, val <- 0,0,10 */ - key = strtok_r(buf, "=", &ctx); - val = strtok_r(NULL, "", &ctx); - - if(!val) { - fprintf(stderr, "Missing value to the rectangle option `%s'.\n", key); - res = RES_BAD_ARG; - goto error; - } - - #define PARSE(Name, Func) { \ - if(RES_OK != (res = Func)) { \ - fprintf(stderr, "Invalid rectangle "Name" `%s'.\n", val); \ - goto error; \ - } \ - } (void)0 - if(!strcmp(key, "pos")) { - PARSE("position", parse_doubleX(val, args->rectangle.pos, 3)); - } else if(!strcmp(key, "tgt")) { - PARSE("target", parse_doubleX(val, args->rectangle.tgt, 3)); - } else if(!strcmp(key, "up")) { - PARSE("up vector", parse_doubleX(val, args->rectangle.up, 3)); - } else if(!strcmp(key, "sz")) { - PARSE("size", parse_doubleX(val, args->rectangle.sz, 2)); - } else { - fprintf(stderr, "Invalid rectangle parameter `%s'.\n", key); - res = RES_BAD_ARG; - goto error; - } - #undef PARSE -exit: - return res; -error: - goto exit; -} - - - -static res_T -parse_spectral_range(const char* str, double wlen_range[2]) -{ - double range[2]; - size_t len; - res_T res = RES_OK; - ASSERT(wlen_range && str); - - res = cstr_to_list_double(str, ',', range, &len, 2); - if(res == RES_OK && len != 2) res = RES_BAD_ARG; - if(res == RES_OK && range[0] > range[1]) res = RES_BAD_ARG; - if(res != RES_OK) { - fprintf(stderr, "Invalid spectral range `%s'.\n", str); - goto error; - } - wlen_range[0] = range[0]; - wlen_range[1] = range[1]; - -exit: - return res; -error: - goto exit; -} - -static res_T -parse_spectral_parameter(struct htrdr_args* args, const char* str) -{ - char buf[128]; - char* key; - char* val; - char* ctx; - res_T res = RES_OK; - ASSERT(args); - - if(strlen(str) >= sizeof(buf) -1/*NULL char*/) { - fprintf(stderr, - "Could not duplicate the spectral option string `%s'.\n", str); - res = RES_MEM_ERR; - goto error; - } - strncpy(buf, str, sizeof(buf)); - - key = strtok_r(buf, "=", &ctx); - val = strtok_r(NULL, "", &ctx); - - if(!strcmp(key, "cie_xyz")) { - args->spectral_type = HTRDR_SPECTRAL_SW_CIE_XYZ; - args->wlen_range[0] = HTRDR_CIE_XYZ_RANGE_DEFAULT[0]; - args->wlen_range[1] = HTRDR_CIE_XYZ_RANGE_DEFAULT[1]; - } else { - if(!val) { - fprintf(stderr, "Missing value to the spectral option `%s'.\n", key); - res = RES_BAD_ARG; - goto error; - } - if(!strcmp(key, "sw")) { - args->spectral_type = HTRDR_SPECTRAL_SW; - res = parse_spectral_range(val, args->wlen_range); - if(res != RES_OK) goto error; - } else if(!strcmp(key, "lw")) { - args->spectral_type = HTRDR_SPECTRAL_LW; - res = parse_spectral_range(val, args->wlen_range); - if(res != RES_OK) goto error; - } else if(!strcmp(key, "Tref")) { - res = cstr_to_double(val, &args->ref_temperature); - if(res == RES_OK && args->ref_temperature < 0) res = RES_BAD_ARG; - if(res != RES_OK) { - fprintf(stderr, "Invalid reference temperature Tref=%s.\n", val); - goto error; - } - } else { - fprintf(stderr, "Invalid spectral parameter `%s'.\n", key); - res = RES_BAD_ARG; - goto error; - } - } - -exit: - return res; -error: - goto exit; -} - -static res_T -parse_multiple_parameters - (struct htrdr_args* args, - const char* str, - res_T (*parse_parameter)(struct htrdr_args* args, const char* str)) -{ - char buf[512]; - char* tk; - char* ctx; - res_T res = RES_OK; - ASSERT(args && str); - - if(strlen(str) >= sizeof(buf) - 1/*NULL char*/) { - fprintf(stderr, "Could not duplicate the option string `%s'.\n", str); - res = RES_MEM_ERR; - goto error; - } - strncpy(buf, str, sizeof(buf)); - - tk = strtok_r(buf, ":", &ctx); - do { - res = parse_parameter(args, tk); - if(res != RES_OK) goto error; - tk = strtok_r(NULL, ":", &ctx); - } while(tk); - -exit: - return res; -error: - goto exit; -} - -static res_T -parse_sun_dir(struct htrdr_args* args, const char* str) -{ - double angles[2]; - size_t len; - res_T res = RES_OK; - ASSERT(args && str); - - res = cstr_to_list_double(str, ',', angles, &len, 2); - if(res == RES_OK && len != 2) res = RES_BAD_ARG; - if(res != RES_OK) { - fprintf(stderr, "Invalid direction `%s'.\n", str); - goto error; - } - - if(angles[0] < 0 || angles[0] >= 360) { - fprintf(stderr, - "Invalid azimuth angle `%g'. Azimuth must be in [0, 360[ degrees.\n", - angles[0]); - res = RES_BAD_ARG; - goto error; - } - - if(angles[1] < 0 || angles[1] > 90) { - fprintf(stderr, - "Invalid elevation angle `%g'. Elevation must be in [0, 90] degrees.\n", - angles[1]); - res = RES_BAD_ARG; - goto error; - } - - args->sun_azimuth = angles[0]; - args->sun_elevation = angles[1]; - -exit: - return res; -error: - goto exit; -} - -static res_T -parse_grid_definition(struct htrdr_args* args, const char* str) -{ - unsigned def[3]; - size_t len; - res_T res = RES_OK; - ASSERT(args && str); - - res = cstr_to_list_uint(str, ',', def, &len, 3); - if(res == RES_OK && len != 3) res = RES_BAD_ARG; - if(res != RES_OK) { - fprintf(stderr, "Invalid grid definition `%s'.\n", str); - goto error; - } - - if(!def[0] || !def[1] || !def[2]) { - fprintf(stderr, - "Invalid null grid definition {%u, %u, %u}.\n", SPLIT3(def)); - res = RES_BAD_ARG; - goto error; - } - - args->grid_max_definition[0] = def[0]; - args->grid_max_definition[1] = def[1]; - args->grid_max_definition[2] = def[2]; - -exit: - return res; -error: - goto exit; -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_args_init(struct htrdr_args* args, int argc, char** argv) -{ - int opt; - int i; - res_T res = RES_OK; - ASSERT(args && argc && argv); - - *args = HTRDR_ARGS_DEFAULT; - - FOR_EACH(i, 1, argc) { - if(!strcmp(argv[i], "--version")) { - printf("High-Tune: RenDeRer %d.%d.%d\n", - HTRDR_VERSION_MAJOR, - HTRDR_VERSION_MINOR, - HTRDR_VERSION_PATCH); - args->quit = 1; - goto exit; - } - } - - while((opt = getopt(argc, argv, "a:C:c:D:dfg:hi:M:m:n:O:o:p:Rrs:T:t:V:v")) != -1) { - switch(opt) { - case 'a': args->filename_gas = optarg; break; - case 'C': - args->sensor_type = HTRDR_SENSOR_CAMERA; - res = parse_multiple_parameters - (args, optarg, parse_camera_parameter); - break; - case 'c': args->filename_les = optarg; break; - case 'D': res = parse_sun_dir(args, optarg); break; - case 'd': args->dump_vtk = 1; break; - case 'f': args->force_overwriting = 1; break; - case 'g': args->filename_obj = optarg; break; - case 'h': - print_help(argv[0]); - htrdr_args_release(args); - args->quit = 1; - goto exit; - case 'i': - res = parse_multiple_parameters - (args, optarg, parse_image_parameter); - break; - case 'M': args->filename_mtl = optarg; break; - case 'm': args->filename_mie = optarg; break; - case 'n': args->sky_mtl_name = optarg; break; - case 'O': args->cache = optarg; break; - case 'o': args->output = optarg; break; - case 'p': - args->sensor_type = HTRDR_SENSOR_RECTANGLE; - res = parse_multiple_parameters - (args, optarg, parse_rectangle_parameter); - break; - case 'r': args->repeat_clouds = 1; break; - case 'R': args->repeat_ground = 1; break; - case 's': - res = parse_multiple_parameters - (args, optarg, parse_spectral_parameter); - break; - case 'T': - res = cstr_to_double(optarg, &args->optical_thickness); - if(res == RES_OK && args->optical_thickness < 0) res = RES_BAD_ARG; - break; - case 't': /* Submit an hint on the number of threads to use */ - res = cstr_to_uint(optarg, &args->nthreads); - if(res == RES_OK && !args->nthreads) res = RES_BAD_ARG; - break; - case 'V': res = parse_grid_definition(args, optarg); break; - case 'v': args->verbose = 1; break; - default: res = RES_BAD_ARG; break; - } - if(res != RES_OK) { - if(optarg) { - fprintf(stderr, "%s: invalid option argument '%s' -- '%c'\n", - argv[0], optarg, opt); - } - goto error; - } - } - if(!args->filename_gas) { - fprintf(stderr, - "Missing the path of the gas optical properties file -- option '-a'\n"); - res = RES_BAD_ARG; - goto error; - } - if(args->filename_obj && !args->filename_mtl) { - fprintf(stderr, - "Missing the path of the file listing the ground materials -- option '-M'\n"); - res = RES_BAD_ARG; - goto error; - } - if(args->filename_les && !args->filename_mie) { - fprintf(stderr, - "Missing the path toward the file of the Mie's data -- option '-m'\n"); - res = RES_BAD_ARG; - goto error; - } - - /* Setup default ref temperature if necessary */ - if(args->ref_temperature <= 0) { - switch(args->spectral_type) { - case HTRDR_SPECTRAL_LW: - args->ref_temperature = HTRDR_DEFAULT_LW_REF_TEMPERATURE; - break; - case HTRDR_SPECTRAL_SW: - args->ref_temperature = HTRDR_SUN_TEMPERATURE; - break; - case HTRDR_SPECTRAL_SW_CIE_XYZ: - args->ref_temperature = -1; /* Unused */ - break; - default: FATAL("Unreachable code.\n"); break; - } - } - -exit: - return res; -error: - htrdr_args_release(args); - goto exit; -} - -void -htrdr_args_release(struct htrdr_args* args) -{ - ASSERT(args); - *args = HTRDR_ARGS_DEFAULT; -} - diff --git a/src/htrdr_args.h.in b/src/htrdr_args.h.in @@ -1,127 +0,0 @@ -/* Copyright (C) 2018-2019 CNRS, Université Paul Sabatier, |Meso|Star> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_ARGS_H -#define HTRDR_ARGS_H - -#include "htrdr_sensor.h" -#include "htrdr_spectral.h" -#include "htrdr_cie_xyz.h" - -#include <float.h> -#include <limits.h> -#include <rsys/rsys.h> - -struct htrdr_args { - const char* filename_gas; /* Path of the gas file */ - const char* filename_les; /* Path of the HTCP file */ - const char* filename_mie; /* Path of the Mie properties */ - const char* filename_obj; /* Path of the 3D geometry */ - const char* filename_mtl; /* Path of the materials */ - const char* cache; - const char* output; - const char* sky_mtl_name; - - struct { - double pos[3]; /* Center of the renctangle */ - double tgt[3]; /* Target */ - double up[3]; /* Up vector */ - double sz[2]; /* Plane size in world space */ - } rectangle; - - struct { - double pos[3]; - double tgt[3]; - double up[3]; - double fov_y; /* In degrees */ - } camera; - - struct { - unsigned definition[2]; /* #pixels in X and Y */ - unsigned spp; /* #samples per pixel */ - } image; - - double sun_azimuth; /* In degrees */ - double sun_elevation; /* In degrees */ - double optical_thickness; /* Threshold used during octree building */ - unsigned grid_max_definition[3]; /* Maximum definition of the grid */ - - enum htrdr_spectral_type spectral_type; - double wlen_range[2]; /* Spectral range of integration in nm */ - double ref_temperature; /* Planck reference temperature in Kelvin */ - - enum htrdr_sensor_type sensor_type; - unsigned nthreads; /* Hint on the number of threads to use */ - int force_overwriting; - int dump_vtk; /* Dump the loaded cloud properties in a VTK file */ - int verbose; /* Verbosity level */ - int repeat_clouds; /* Make the clouds infinite in X and Y */ - int repeat_ground; /* Make the ground infinite in X and Y */ - int quit; /* Quit the application */ -}; - -#define HTRDR_ARGS_DEFAULT__ { \ - NULL, /* Gas filename */ \ - NULL, /* LES filename */ \ - NULL, /* Mie filename */ \ - NULL, /* Obj filename */ \ - NULL, /* Mtl filename */ \ - NULL, /* Cache filename */ \ - NULL, /* Output filename */ \ - @HTRDR_ARGS_DEFAULT_SKY_MTL_NAME@, /* Sky mtl name */ \ - { \ - {@HTRDR_ARGS_DEFAULT_RECTANGLE_POS@}, /* Rectangle center */ \ - {@HTRDR_ARGS_DEFAULT_RECTANGLE_TGT@}, /* Rectangle target */ \ - {@HTRDR_ARGS_DEFAULT_RECTANGLE_UP@}, /* Rectangle up */ \ - {@HTRDR_ARGS_DEFAULT_RECTANGLE_SZ@}, /* Rectangle size */ \ - }, { \ - {@HTRDR_ARGS_DEFAULT_CAMERA_POS@}, /* Camera position */ \ - {@HTRDR_ARGS_DEFAULT_CAMERA_TGT@}, /* Camera target */ \ - {@HTRDR_ARGS_DEFAULT_CAMERA_UP@}, /* Camera up */ \ - @HTRDR_ARGS_DEFAULT_CAMERA_FOV@, /* Horizontal field of view */ \ - }, { \ - {@HTRDR_ARGS_DEFAULT_IMG_WIDTH@, @HTRDR_ARGS_DEFAULT_IMG_HEIGHT@}, \ - @HTRDR_ARGS_DEFAULT_IMG_SPP@ \ - }, \ - 0, /* Sun azimuth */ \ - 90, /* Sun elevation */ \ - @HTRDR_ARGS_DEFAULT_OPTICAL_THICKNESS_THRESHOLD@, /* Optical thickness */ \ - {UINT_MAX, UINT_MAX, UINT_MAX}, /* Maximum definition of the grid */ \ - HTRDR_SPECTRAL_SW_CIE_XYZ, /* Spectral type */ \ - HTRDR_CIE_XYZ_RANGE_DEFAULT__, /* Spectral range */ \ - -1, /* Reference temperature */ \ - HTRDR_SENSOR_CAMERA, /* sensor type */ \ - (unsigned)~0, /* #threads */ \ - 0, /* Force overwriting */ \ - 0, /* dump VTK */ \ - 0, /* Verbose flag */ \ - 0, /* Repeat clouds */ \ - 0, /* Repeat ground */ \ - 0 /* Quit the application */ \ -} -static const struct htrdr_args HTRDR_ARGS_DEFAULT = HTRDR_ARGS_DEFAULT__; - -extern LOCAL_SYM res_T -htrdr_args_init - (struct htrdr_args* args, - int argc, - char** argv); - -extern LOCAL_SYM void -htrdr_args_release - (struct htrdr_args* args); - -#endif /* HTRDR_ARGS_H */ - diff --git a/src/htrdr_buffer.c b/src/htrdr_buffer.c @@ -1,167 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "htrdr.h" -#include "htrdr_buffer.h" - -#include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> - -struct htrdr_buffer { - char* mem; - - size_t width; - size_t height; - size_t pitch; - size_t elmtsz; - size_t align; - - struct htrdr* htrdr; - ref_T ref; -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static void -buffer_release(ref_T* ref) -{ - struct htrdr_buffer* buf = NULL; - ASSERT(ref); - buf = CONTAINER_OF(ref, struct htrdr_buffer, ref); - if(buf->mem) MEM_RM(buf->htrdr->allocator, buf->mem); - MEM_RM(buf->htrdr->allocator, buf); -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_buffer_create - (struct htrdr* htrdr, - const size_t width, - const size_t height, - const size_t pitch, - const size_t elmtsz, - const size_t align, - struct htrdr_buffer** out_buf) -{ - struct htrdr_buffer* buf = NULL; - size_t memsz = 0; - res_T res = RES_OK; - ASSERT(htrdr && out_buf); - - if(!width || !height) { - htrdr_log_err(htrdr, "invalid buffer definition %lux%lu.\n", - (unsigned long)width, (unsigned long)height); - res = RES_BAD_ARG; - goto error; - } - if(pitch < width*elmtsz) { - htrdr_log_err(htrdr, - "invalid buffer pitch `%lu' wrt the buffer width `%lu'. " - "The buffer pitch cannot be less than the buffer width.\n", - (unsigned long)pitch, (unsigned long)width); - res = RES_BAD_ARG; - goto error; - } - if(!elmtsz) { - htrdr_log_err(htrdr, - "the size of the buffer's elements cannot be null.\n"); - res = RES_BAD_ARG; - goto error; - } - if(!IS_POW2(align)) { - htrdr_log_err(htrdr, - "invalid buffer alignment `%lu'. It must be a power of 2.\n", - (unsigned long)align); - res = RES_BAD_ARG; - goto error; - } - - buf = MEM_CALLOC(htrdr->allocator, 1, sizeof(*buf)); - if(!buf) { - res = RES_MEM_ERR; - goto error; - } - ref_init(&buf->ref); - buf->htrdr = htrdr; - buf->width = width; - buf->height = height; - buf->pitch = pitch; - buf->elmtsz = elmtsz; - buf->align = align; - buf->htrdr = htrdr; - - memsz = buf->pitch * buf->height; - buf->mem = MEM_ALLOC_ALIGNED(htrdr->allocator, memsz, align); - if(!buf->mem) { - res = RES_MEM_ERR; - goto error; - } - -exit: - *out_buf = buf; - return res; -error: - if(buf) { - htrdr_buffer_ref_put(buf); - buf = NULL; - } - goto exit; -} - -void -htrdr_buffer_ref_get(struct htrdr_buffer* buf) -{ - ASSERT(buf); - ref_get(&buf->ref); -} - -void -htrdr_buffer_ref_put(struct htrdr_buffer* buf) -{ - ASSERT(buf); - ref_put(&buf->ref, buffer_release); -} - -void -htrdr_buffer_get_layout - (const struct htrdr_buffer* buf, - struct htrdr_buffer_layout* layout) -{ - ASSERT(buf && layout); - layout->width = buf->width; - layout->height = buf->height; - layout->pitch = buf->pitch; - layout->elmt_size = buf->elmtsz; - layout->alignment = buf->align; -} - -void* -htrdr_buffer_get_data(struct htrdr_buffer* buf) -{ - ASSERT(buf); - return buf->mem; -} - -void* -htrdr_buffer_at(struct htrdr_buffer* buf, const size_t x, const size_t y) -{ - ASSERT(buf && x < buf->width && y < buf->height); - return buf->mem + y*buf->pitch + x*buf->elmtsz; -} - diff --git a/src/htrdr_buffer.h b/src/htrdr_buffer.h @@ -1,74 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_BUFFER_H -#define HTRDR_BUFFER_H - -#include <rsys/rsys.h> - -/* - * Row major ordered 2D buffer - */ - -struct htrdr_buffer_layout { - size_t width; /* #elements in X */ - size_t height; /* #elements in Y */ - size_t pitch; /* #Bytes between 2 consecutive line */ - size_t elmt_size; /* Size of an element in the buffer */ - size_t alignment; /* Alignement of the memory */ -}; -#define HTRDR_BUFFER_LAYOUT_NULL__ {0,0,0,0,0} -static const struct htrdr_buffer_layout HTRDR_BUFFER_LAYOUT_NULL = - HTRDR_BUFFER_LAYOUT_NULL__; - -/* Forward declarations */ -struct htrdr; -struct htrdr_buffer; - -extern LOCAL_SYM res_T -htrdr_buffer_create - (struct htrdr* htrdr, - const size_t width, - const size_t height, - const size_t pitch, /* #Bytes between 2 consecutive line */ - const size_t elmt_size, /* Size of an element in the buffer */ - const size_t alignment, /* Alignement of the buffer */ - struct htrdr_buffer** buf); - -extern LOCAL_SYM void -htrdr_buffer_ref_get - (struct htrdr_buffer* buf); - -extern LOCAL_SYM void -htrdr_buffer_ref_put - (struct htrdr_buffer* buf); - -extern LOCAL_SYM void -htrdr_buffer_get_layout - (const struct htrdr_buffer* buf, - struct htrdr_buffer_layout* layout); - -extern LOCAL_SYM void* -htrdr_buffer_get_data - (struct htrdr_buffer* buf); - -extern LOCAL_SYM void* -htrdr_buffer_at - (struct htrdr_buffer* buf, - const size_t x, - const size_t y); - -#endif /* HTRDR_BUFFER_H */ diff --git a/src/htrdr_c.h b/src/htrdr_c.h @@ -1,154 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_C_H -#define HTRDR_C_H - -#include <rsys/rsys.h> - -#ifndef NDEBUG - #define MPI(Func) ASSERT(MPI_##Func == MPI_SUCCESS) -#else - #define MPI(Func) MPI_##Func -#endif - -enum htrdr_mpi_message { - HTRDR_MPI_PROGRESS_BUILD_OCTREE, - HTRDR_MPI_PROGRESS_RENDERING, - HTRDR_MPI_STEAL_REQUEST, - HTRDR_MPI_WORK_STEALING, - HTRDR_MPI_TILE_DATA -}; - -struct htrdr; - -/* In nanometer */ -static FINLINE double -wavenumber_to_wavelength(const double nu/*In cm^-1*/) -{ - return 1.e7 / nu; -} - -/* In cm^-1 */ -static FINLINE double -wavelength_to_wavenumber(const double lambda/*In nanometer*/) -{ - return wavenumber_to_wavelength(lambda); -} - -static INLINE uint64_t -morton3D_encode_u21(const uint32_t u21) -{ - uint64_t u64 = u21 & ((1<<21) - 1); - ASSERT(u21 <= ((1 << 21) - 1)); - u64 = (u64 | (u64 << 32)) & 0xFFFF00000000FFFF; - u64 = (u64 | (u64 << 16)) & 0x00FF0000FF0000FF; - u64 = (u64 | (u64 << 8)) & 0xF00F00F00F00F00F; - u64 = (u64 | (u64 << 4)) & 0x30C30C30C30C30C3; - u64 = (u64 | (u64 << 2)) & 0x9249249249249249; - return u64; -} - -static INLINE uint32_t -morton3D_decode_u21(const uint64_t u64) -{ - uint64_t tmp = (u64 & 0x9249249249249249); - tmp = (tmp | (tmp >> 2)) & 0x30C30C30C30C30C3; - tmp = (tmp | (tmp >> 4)) & 0xF00F00F00F00F00F; - tmp = (tmp | (tmp >> 8)) & 0x00FF0000FF0000FF; - tmp = (tmp | (tmp >> 16)) & 0xFFFF00000000FFFF; - tmp = (tmp | (tmp >> 32)) & 0x00000000FFFFFFFF; - ASSERT(tmp <= ((1<<21)-1)); - return (uint32_t)tmp; -} - -static INLINE uint64_t -morton_xyz_encode_u21(const uint32_t xyz[3]) -{ - return (morton3D_encode_u21(xyz[0]) << 2) - | (morton3D_encode_u21(xyz[1]) << 1) - | (morton3D_encode_u21(xyz[2]) << 0); -} - -static INLINE void -morton_xyz_decode_u21(const uint64_t code, uint32_t xyz[3]) -{ - ASSERT(xyz && code < ((1ull << 63)-1)); - xyz[0] = (uint32_t)morton3D_decode_u21(code >> 2); - xyz[1] = (uint32_t)morton3D_decode_u21(code >> 1); - xyz[2] = (uint32_t)morton3D_decode_u21(code >> 0); -} - -/* Return the minimum length in nanometer of the sky spectral bands - * clamped to in [range[0], range[1]]. */ -extern LOCAL_SYM double -compute_sky_min_band_len - (struct htsky* sky, - const double range[2]); - -extern LOCAL_SYM res_T -open_output_stream - (struct htrdr* htrdr, - const char* filename, - const int read, /* Enable read access */ - int force_overwrite, - FILE** out_fp); - -extern LOCAL_SYM void -send_mpi_progress - (struct htrdr* htrdr, - const enum htrdr_mpi_message progress, - const int32_t percent); - -extern LOCAL_SYM void -fetch_mpi_progress - (struct htrdr* htrdr, - const enum htrdr_mpi_message progress); - -extern LOCAL_SYM void -print_mpi_progress - (struct htrdr* htrdr, - const enum htrdr_mpi_message progress); - -extern LOCAL_SYM void -clear_mpi_progress - (struct htrdr* htrdr, - const enum htrdr_mpi_message progress); - -extern int32_t -total_mpi_progress - (const struct htrdr* htrdr, - const enum htrdr_mpi_message progress); - -static INLINE void -update_mpi_progress(struct htrdr* htrdr, const enum htrdr_mpi_message progress) -{ - ASSERT(htrdr); - fetch_mpi_progress(htrdr, progress); - clear_mpi_progress(htrdr, progress); - print_mpi_progress(htrdr, progress); -} - -static FINLINE int -cmp_dbl(const void* a, const void* b) -{ - const double d0 = *((const double*)a); - const double d1 = *((const double*)b); - return d0 < d1 ? -1 : (d0 > d1 ? 1 : 0); -} - -#endif /* HTRDR_C_H */ - diff --git a/src/htrdr_camera.c b/src/htrdr_camera.c @@ -1,151 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "htrdr.h" -#include "htrdr_camera.h" - -#include <rsys/double3.h> -#include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> - -struct htrdr_camera { - /* Orthogonal basis of the camera */ - double axis_x[3]; - double axis_y[3]; - double axis_z[3]; - - double position[3]; - - ref_T ref; - struct htrdr* htrdr; -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static void -camera_release(ref_T* ref) -{ - struct htrdr_camera* cam; - ASSERT(ref); - cam = CONTAINER_OF(ref, struct htrdr_camera, ref); - MEM_RM(cam->htrdr->allocator, cam); -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_camera_create - (struct htrdr* htrdr, - const double position[3], - const double target[3], - const double up[3], - const double proj_ratio, - const double fov, /* In radian */ - struct htrdr_camera** out_cam) -{ - double x[3], y[3], z[3]; - double img_plane_depth; - struct htrdr_camera* cam = NULL; - res_T res = RES_OK; - ASSERT(htrdr && position && target && up && out_cam); - - cam = MEM_CALLOC(htrdr->allocator, 1, sizeof(*cam)); - if(!cam) { - htrdr_log_err(htrdr, "could not allocate the camera data structure.\n"); - res = RES_MEM_ERR; - goto error; - } - ref_init(&cam->ref); - cam->htrdr = htrdr; - - if(fov <= 0 || fov >= PI) { - htrdr_log_err(htrdr, "invalid horizontal camera field of view `%g'\n", fov); - res = RES_BAD_ARG; - goto error; - } - - if(proj_ratio <= 0) { - htrdr_log_err(htrdr, "invalid projection ratio `%g'\n", proj_ratio); - res = RES_BAD_ARG; - goto error; - } - - if(d3_normalize(z, d3_sub(z, target, position)) <= 0 - || d3_normalize(x, d3_cross(x, z, up)) <= 0 - || d3_normalize(y, d3_cross(y, z, x)) <= 0) { - htrdr_log_err(htrdr, - "invalid camera point of view:\n" - " position = %g %g %g\n" - " target = %g %g %g\n" - " up = %g %g %g\n", - SPLIT3(position), SPLIT3(target), SPLIT3(up)); - res = RES_BAD_ARG; - goto error; - } - - img_plane_depth = 1.0/tan(fov*0.5); - d3_muld(cam->axis_x, x, proj_ratio); - d3_set(cam->axis_y, y); - d3_muld(cam->axis_z, z, img_plane_depth); - d3_set(cam->position, position); - -exit: - *out_cam = cam; - return res; -error: - if(cam) { - htrdr_camera_ref_put(cam); - cam = NULL; - } - goto exit; -} - -void -htrdr_camera_ref_get(struct htrdr_camera* cam) -{ - ASSERT(cam); - ref_get(&cam->ref); -} - -void -htrdr_camera_ref_put(struct htrdr_camera* cam) -{ - ASSERT(cam); - ref_put(&cam->ref, camera_release); -} - -void -htrdr_camera_ray - (const struct htrdr_camera* cam, - const double sample[2], - double ray_org[3], - double ray_dir[3]) -{ - double x[3], y[3], len; - (void)len; - ASSERT(cam && sample && ray_org && ray_dir); - ASSERT(sample[0] >= 0 || sample[0] < 1); - ASSERT(sample[1] >= 0 || sample[1] < 1); - d3_muld(x, cam->axis_x, sample[0]*2-1); - d3_muld(y, cam->axis_y, sample[1]*2-1); - d3_add(ray_dir, d3_add(ray_dir, x, y), cam->axis_z); - len = d3_normalize(ray_dir, ray_dir); - ASSERT(len >= 1.e-6); - d3_set(ray_org, cam->position); -} - diff --git a/src/htrdr_camera.h b/src/htrdr_camera.h @@ -1,52 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_CAMERA_H -#define HTRDR_CAMERA_H - -#include <rsys/rsys.h> - -/* Forward declarations */ -struct htrdr; -struct htrdr_camera; - -extern LOCAL_SYM res_T -htrdr_camera_create - (struct htrdr* htrdr, - const double position[3], - const double target[3], - const double up[3], - const double proj_ratio, /* Width / Height */ - const double fov, /* In radian */ - struct htrdr_camera** cam); - -extern LOCAL_SYM void -htrdr_camera_ref_get - (struct htrdr_camera* cam); - -extern LOCAL_SYM void -htrdr_camera_ref_put - (struct htrdr_camera* cam); - -extern LOCAL_SYM void -htrdr_camera_ray - (const struct htrdr_camera* cam, - const double sample[2], /* In [0, 1[ */ - double ray_org[3], - double ray_dir[3]); - -#endif /* HTRDR_CAMERA_H */ - diff --git a/src/htrdr_cie_xyz.c b/src/htrdr_cie_xyz.c @@ -1,396 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#define _POSIX_C_SOURCE 200112L /* nextafter */ - -#include "htrdr.h" -#include "htrdr_c.h" -#include "htrdr_cie_xyz.h" - -#include <high_tune/htsky.h> - -#include <rsys/algorithm.h> -#include <rsys/cstr.h> -#include <rsys/dynamic_array_double.h> -#include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> - -#include <math.h> /* nextafter */ - -struct htrdr_cie_xyz { - struct darray_double cdf_X; - struct darray_double cdf_Y; - struct darray_double cdf_Z; - double rcp_integral_X; - double rcp_integral_Y; - double rcp_integral_Z; - double range[2]; /* Boundaries of the handled CIE XYZ color space */ - double band_len; /* Length in nanometers of a band */ - - struct htrdr* htrdr; - ref_T ref; -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static INLINE double -trapezoidal_integration - (const double lambda_lo, /* Integral lower bound. In nanometer */ - const double lambda_hi, /* Integral upper bound. In nanometer */ - double (*f_bar)(const double lambda)) /* Function to integrate */ -{ - double dlambda; - size_t i, n; - double integral = 0; - ASSERT(lambda_lo <= lambda_hi); - ASSERT(lambda_lo > 0); - - n = (size_t)(lambda_hi - lambda_lo) + 1; - dlambda = (lambda_hi - lambda_lo) / (double)n; - - FOR_EACH(i, 0, n) { - const double lambda1 = lambda_lo + dlambda*(double)(i+0); - const double lambda2 = lambda_lo + dlambda*(double)(i+1); - const double f1 = f_bar(lambda1); - const double f2 = f_bar(lambda2); - integral += (f1 + f2)*dlambda*0.5; - } - return integral; -} - -/* The following 3 functions are used to fit the CIE Xbar, Ybar and Zbar curved - * has defined by the 1931 standard. These analytical fits are propsed by C. - * Wyman, P. P. Sloan & P. Shirley in "Simple Analytic Approximations to the - * CIE XYZ Color Matching Functions" - JCGT 2013. */ -static INLINE double -fit_x_bar_1931(const double lambda) -{ - const double a = (lambda - 442.0) * (lambda < 442.0 ? 0.0624 : 0.0374); - const double b = (lambda - 599.8) * (lambda < 599.8 ? 0.0264 : 0.0323); - const double c = (lambda - 501.1) * (lambda < 501.1 ? 0.0490 : 0.0382); - return 0.362*exp(-0.5*a*a) + 1.056*exp(-0.5f*b*b) - 0.065*exp(-0.5*c*c); -} - -static FINLINE double -fit_y_bar_1931(const double lambda) -{ - const double a = (lambda - 568.8) * (lambda < 568.8 ? 0.0213 : 0.0247); - const double b = (lambda - 530.9) * (lambda < 530.9 ? 0.0613 : 0.0322); - return 0.821*exp(-0.5*a*a) + 0.286*exp(-0.5*b*b); -} - -static FINLINE double -fit_z_bar_1931(const double lambda) -{ - const double a = (lambda - 437.0) * (lambda < 437.0 ? 0.0845 : 0.0278); - const double b = (lambda - 459.0) * (lambda < 459.0 ? 0.0385 : 0.0725); - return 1.217*exp(-0.5*a*a) + 0.681*exp(-0.5*b*b); -} - -static INLINE double -sample_cie_xyz - (const struct htrdr_cie_xyz* cie, - const double* cdf, - const size_t cdf_length, - double (*f_bar)(const double lambda), /* Function to integrate */ - const double r0, /* Canonical number in [0, 1[ */ - const double r1) /* Canonical number in [0, 1[ */ -{ - double r0_next = nextafter(r0, DBL_MAX); - double* find; - double f_min, f_max; /* CIE 1931 value for the band boundaries */ - double lambda; /* Sampled wavelength */ - double lambda_min, lambda_max; /* Boundaries of the sampled band */ - double lambda_1, lambda_2; /* Solutions if the equation to solve */ - double a, b, c, d; /* Equation parameters */ - double delta, sqrt_delta; - size_t iband; /* Index of the sampled band */ - ASSERT(cie && cdf && cdf_length); - ASSERT(0 <= r0 && r0 < 1); - ASSERT(0 <= r1 && r1 < 1); - - /* Use r_next rather than r in order to find the first entry that is not less - * than *or equal* to r */ - find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl); - ASSERT(find); - - /* Define and check the sampled band */ - iband = (size_t)(find - cdf); - ASSERT(iband < cdf_length); - ASSERT(cdf[iband] > r0 && (!iband || cdf[iband-1] <= r0)); - - /* Define the boundaries of the sampled band */ - lambda_min = cie->range[0] + cie->band_len * (double)iband; - lambda_max = lambda_min + cie->band_len; - - /* Define the value of the CIE 1931 function for the boudaries of the sampled - * band */ - f_min = f_bar(lambda_min); - f_max = f_bar(lambda_max); - - /* Compute the equation constants */ - a = 0.5 * (f_max - f_min) / cie->band_len; - b = (lambda_max * f_min - lambda_min * f_max) / cie->band_len; - c = -lambda_min * f_min + lambda_min*lambda_min * a; - d = 0.5 * (f_max + f_min) * cie->band_len; - - delta = b*b - 4*a*(c-d*r1); - if(delta < 0 && eq_eps(delta, 0, 1.e-6)) { - delta = 0; - } - ASSERT(delta > 0); - sqrt_delta = sqrt(delta); - - /* Compute the roots that solve the equation */ - lambda_1 = (-b - sqrt_delta) / (2*a); - lambda_2 = (-b + sqrt_delta) / (2*a); - - /* Select the solution */ - if(lambda_min <= lambda_1 && lambda_1 < lambda_max) { - lambda = lambda_1; - } else if(lambda_min <= lambda_2 && lambda_2 < lambda_max) { - lambda = lambda_2; - } else { - htrdr_log_warn(cie->htrdr, - "%s: cannot sample a wavelength in [%g, %g[. The possible wavelengths" - "were %g and %g.\n", - FUNC_NAME, lambda_min, lambda_max, lambda_1, lambda_2); - /* Arbitrarly choose the wavelength at the center of the sampled band */ - lambda = (lambda_min + lambda_max)*0.5; - } - - return lambda; -} - -static res_T -setup_cie_xyz - (struct htrdr_cie_xyz* cie, - const char* func_name, - const size_t nbands) -{ - enum { X, Y, Z }; /* Helper constant */ - double* pdf[3] = {NULL, NULL, NULL}; - double* cdf[3] = {NULL, NULL, NULL}; - double sum[3] = {0, 0, 0}; - size_t i; - res_T res = RES_OK; - - ASSERT(cie && func_name && nbands); - ASSERT(cie->range[0] >= HTRDR_CIE_XYZ_RANGE_DEFAULT[0]); - ASSERT(cie->range[1] <= HTRDR_CIE_XYZ_RANGE_DEFAULT[1]); - ASSERT(cie->range[0] < cie->range[1]); - - /* Allocate and reset the memory space for the tristimulus CDF */ - #define SETUP_STIMULUS(Stimulus) { \ - res = darray_double_resize(&cie->cdf_ ## Stimulus, nbands); \ - if(res != RES_OK) { \ - htrdr_log_err(cie->htrdr, \ - "%s: Could not reserve the memory space for the CDF " \ - "of the "STR(X)" stimulus -- %s.\n", func_name, res_to_cstr(res)); \ - goto error; \ - } \ - cdf[Stimulus] = darray_double_data_get(&cie->cdf_ ## Stimulus); \ - pdf[Stimulus] = cdf[Stimulus]; \ - memset(cdf[Stimulus], 0, nbands*sizeof(double)); \ - } (void)0 - SETUP_STIMULUS(X); - SETUP_STIMULUS(Y); - SETUP_STIMULUS(Z); - #undef SETUP_STIMULUS - - /* Compute the *unormalized* pdf of the tristimulus */ - FOR_EACH(i, 0, nbands) { - const double lambda_lo = cie->range[0] + (double)i * cie->band_len; - const double lambda_hi = MMIN(lambda_lo + cie->band_len, cie->range[1]); - ASSERT(lambda_lo <= lambda_hi); - ASSERT(lambda_lo >= cie->range[0]); - ASSERT(lambda_hi <= cie->range[1]); - pdf[X][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_x_bar_1931); - pdf[Y][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_y_bar_1931); - pdf[Z][i] = trapezoidal_integration(lambda_lo, lambda_hi, fit_z_bar_1931); - sum[X] += pdf[X][i]; - sum[Y] += pdf[Y][i]; - sum[Z] += pdf[Z][i]; - } - #define CHK_SUM(Sum, Range, Fit) \ - ASSERT(eq_eps(Sum, trapezoidal_integration(Range[0], Range[1], Fit), 1.e-3)) - CHK_SUM(sum[X], cie->range, fit_x_bar_1931); - CHK_SUM(sum[Y], cie->range, fit_y_bar_1931); - CHK_SUM(sum[Z], cie->range, fit_z_bar_1931); - #undef CHK_SUM - cie->rcp_integral_X = 1.0 / sum[X]; - cie->rcp_integral_Y = 1.0 / sum[Y]; - cie->rcp_integral_Z = 1.0 / sum[Z]; - - FOR_EACH(i, 0, nbands) { - /* Normalize the pdf */ - pdf[X][i] /= sum[X]; - pdf[Y][i] /= sum[Y]; - pdf[Z][i] /= sum[Z]; - /* Setup the cumulative */ - if(i == 0) { - cdf[X][i] = pdf[X][i]; - cdf[Y][i] = pdf[Y][i]; - cdf[Z][i] = pdf[Z][i]; - } else { - cdf[X][i] = pdf[X][i] + cdf[X][i-1]; - cdf[Y][i] = pdf[Y][i] + cdf[Y][i-1]; - cdf[Z][i] = pdf[Z][i] + cdf[Z][i-1]; - ASSERT(cdf[X][i] >= cdf[X][i-1]); - ASSERT(cdf[Y][i] >= cdf[Y][i-1]); - ASSERT(cdf[Z][i] >= cdf[Z][i-1]); - } - } - ASSERT(eq_eps(cdf[X][nbands-1], 1, 1.e-6)); - ASSERT(eq_eps(cdf[Y][nbands-1], 1, 1.e-6)); - ASSERT(eq_eps(cdf[Z][nbands-1], 1, 1.e-6)); - - /* Handle numerical issue */ - cdf[X][nbands-1] = 1.0; - cdf[Y][nbands-1] = 1.0; - cdf[Z][nbands-1] = 1.0; - -exit: - return res; -error: - darray_double_clear(&cie->cdf_X); - darray_double_clear(&cie->cdf_Y); - darray_double_clear(&cie->cdf_Z); - goto exit; -} - -static void -release_cie_xyz(ref_T* ref) -{ - struct htrdr_cie_xyz* cie = NULL; - ASSERT(ref); - cie = CONTAINER_OF(ref, struct htrdr_cie_xyz, ref); - darray_double_release(&cie->cdf_X); - darray_double_release(&cie->cdf_Y); - darray_double_release(&cie->cdf_Z); - MEM_RM(cie->htrdr->allocator, cie); -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_cie_xyz_create - (struct htrdr* htrdr, - const double range[2], /* Must be included in [380, 780] nanometers */ - const size_t bands_count, /* # bands used to discretisze the CIE tristimulus */ - struct htrdr_cie_xyz** out_cie) -{ - struct htrdr_cie_xyz* cie = NULL; - double min_band_len = 0; - size_t nbands = bands_count; - res_T res = RES_OK; - ASSERT(htrdr && range && nbands && out_cie); - - cie = MEM_CALLOC(htrdr->allocator, 1, sizeof(*cie)); - if(!cie) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "%s: could not allocate the CIE XYZ data structure -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - ref_init(&cie->ref); - cie->htrdr = htrdr; - darray_double_init(htrdr->allocator, &cie->cdf_X); - darray_double_init(htrdr->allocator, &cie->cdf_Y); - darray_double_init(htrdr->allocator, &cie->cdf_Z); - cie->range[0] = range[0]; - cie->range[1] = range[1]; - - min_band_len = compute_sky_min_band_len(cie->htrdr->sky, range); - cie->band_len = (range[1] - range[0]) / (double)nbands; - - /* Adjust the band length to ensure that each sky spectral interval is - * overlapped by at least one band */ - if(cie->band_len > min_band_len) { - cie->band_len = min_band_len; - nbands = (size_t)ceil((range[1] - range[0]) / cie->band_len); - printf("%lu\n", nbands); - } - - res = setup_cie_xyz(cie, FUNC_NAME, nbands); - if(res != RES_OK) goto error; - - htrdr_log(htrdr, "CIE XYZ spectral interval defined on [%g, %g] nanometers.\n", - range[0], range[1]); - -exit: - *out_cie = cie; - return res; -error: - if(cie) htrdr_cie_xyz_ref_put(cie); - goto exit; -} - -void -htrdr_cie_xyz_ref_get(struct htrdr_cie_xyz* cie) -{ - ASSERT(cie); - ref_get(&cie->ref); -} - -void -htrdr_cie_xyz_ref_put(struct htrdr_cie_xyz* cie) -{ - ASSERT(cie); - ref_put(&cie->ref, release_cie_xyz); -} - -double -htrdr_cie_xyz_sample_X - (struct htrdr_cie_xyz* cie, - const double r0, - const double r1, - double* pdf) -{ - const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_X), - darray_double_size_get(&cie->cdf_X), fit_x_bar_1931, r0, r1); - if(pdf) *pdf = cie->rcp_integral_X; - return wlen; -} - -double -htrdr_cie_xyz_sample_Y - (struct htrdr_cie_xyz* cie, - const double r0, - const double r1, - double* pdf) -{ - const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Y), - darray_double_size_get(&cie->cdf_Y), fit_y_bar_1931, r0, r1); - if(pdf) *pdf = cie->rcp_integral_Y; - return wlen; -} - -double -htrdr_cie_xyz_sample_Z - (struct htrdr_cie_xyz* cie, - const double r0, - const double r1, - double* pdf) -{ - const double wlen = sample_cie_xyz(cie, darray_double_cdata_get(&cie->cdf_Z), - darray_double_size_get(&cie->cdf_Z), fit_z_bar_1931, r0, r1); - if(pdf) *pdf = cie->rcp_integral_Z; - return wlen; -} - diff --git a/src/htrdr_cie_xyz.h b/src/htrdr_cie_xyz.h @@ -1,66 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_CIE_XYZ_H -#define HTRDR_CIE_XYZ_H - -#include <rsys/rsys.h> - -struct htrdr; -struct htrdr_cie_xyz; - -/* Wavelength boundaries of the CIE XYZ color space in nanometers */ -#define HTRDR_CIE_XYZ_RANGE_DEFAULT__ {380, 780} -static const double HTRDR_CIE_XYZ_RANGE_DEFAULT[2] = - HTRDR_CIE_XYZ_RANGE_DEFAULT__; - -extern LOCAL_SYM res_T -htrdr_cie_xyz_create - (struct htrdr* htrdr, - const double range[2], /* Must be included in [380, 780] nanometers */ - const size_t nbands, /* # bands used to discretisze the CIE tristimulus s*/ - struct htrdr_cie_xyz** cie); - -extern LOCAL_SYM void -htrdr_cie_xyz_ref_get - (struct htrdr_cie_xyz* cie); - -extern LOCAL_SYM void -htrdr_cie_xyz_ref_put - (struct htrdr_cie_xyz* cie); - -/* Return a wavelength in nanometer */ -extern LOCAL_SYM double -htrdr_cie_xyz_sample_X - (struct htrdr_cie_xyz* cie, - const double r0, const double r1, /* Canonical numbers in [0, 1[ */ - double* pdf); /* In nm^-1. May be NULL */ - -/* Return a wavelength in nanometer */ -extern LOCAL_SYM double -htrdr_cie_xyz_sample_Y - (struct htrdr_cie_xyz* cie, - const double r0, const double r1, /* Canonical number in [0, 1[ */ - double* pdf); /* In nm^-1. May be NULL */ - -/* Return a wavelength in nanometer */ -extern LOCAL_SYM double -htrdr_cie_xyz_sample_Z - (struct htrdr_cie_xyz* cie, - const double r0, const double r1, /* Canonical number in [0, 1[ */ - double* pdf); /* In nm^-1. May be NULL */ - -#endif /* HTRDR_cie_xyz_H */ - diff --git a/src/htrdr_compute_radiance_lw.c b/src/htrdr_compute_radiance_lw.c @@ -1,270 +0,0 @@ -/*Spectralt (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "htrdr.h" -#include "htrdr_c.h" -#include "htrdr_interface.h" -#include "htrdr_ground.h" -#include "htrdr_solve.h" - -#include <high_tune/htsky.h> - -#include <star/s3d.h> -#include <star/ssf.h> -#include <star/ssp.h> -#include <star/svx.h> - -#include <rsys/double2.h> -#include <rsys/double3.h> - -enum event { - EVENT_ABSORPTION, - EVENT_SCATTERING, - EVENT_NONE -}; - -struct filter_context { - struct ssp_rng* rng; - const struct htsky* htsky; - size_t iband; /* Index of the spectral band */ - size_t iquad; /* Index of the quadrature point into the band */ - - double Ts; /* Sampled optical thickness */ - double traversal_dst; /* Distance traversed along the ray */ - - enum event event_type; -}; -static const struct filter_context FILTER_CONTEXT_NULL = { - NULL, NULL, 0, 0, 0.0, 0.0, EVENT_NONE -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static int -hit_filter - (const struct svx_hit* hit, - const double org[3], - const double dir[3], - const double range[2], - void* context) -{ - struct filter_context* ctx = context; - double kext_max; - int pursue_traversal = 1; - ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range); - (void)range; - - kext_max = htsky_fetch_svx_voxel_property(ctx->htsky, HTSKY_Kext, - HTSKY_SVX_MAX, HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, &hit->voxel); - - ctx->traversal_dst = hit->distance[0]; - - for(;;) { - double vox_dst = hit->distance[1] - ctx->traversal_dst; - const double T = vox_dst * kext_max; - - /* A collision occurs behind `vox_dst' */ - if(ctx->Ts > T) { - ctx->Ts -= T; - ctx->traversal_dst = hit->distance[1]; - pursue_traversal = 1; - break; - - /* A real/null collision occurs before `vox_dst' */ - } else { - const double collision_dst = ctx->Ts / kext_max; - double pos[3]; - double ks, ka; - double r; - double proba_abs; - double proba_sca; - - /* Compute the traversed distance up to the challenged collision */ - ctx->traversal_dst += collision_dst; - ASSERT(ctx->traversal_dst >= hit->distance[0]); - ASSERT(ctx->traversal_dst <= hit->distance[1]); - - /* Compute the world space position where a collision may occur */ - pos[0] = org[0] + ctx->traversal_dst * dir[0]; - pos[1] = org[1] + ctx->traversal_dst * dir[1]; - pos[2] = org[2] + ctx->traversal_dst * dir[2]; - - ka = htsky_fetch_raw_property(ctx->htsky, HTSKY_Ka, HTSKY_CPNT_MASK_ALL, - ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX); - ks = htsky_fetch_raw_property(ctx->htsky, HTSKY_Ks, HTSKY_CPNT_MASK_ALL, - ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX); - - r = ssp_rng_canonical(ctx->rng); - proba_abs = ka / kext_max; - proba_sca = ks / kext_max; - if(r < proba_abs) { /* Absorption event */ - pursue_traversal = 0; - ctx->event_type = EVENT_ABSORPTION; - break; - } else if(r < proba_abs + proba_sca) { /* Scattering event */ - pursue_traversal = 0; - ctx->event_type = EVENT_SCATTERING; - break; - } else { /* Null collision */ - ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */ - } - } - } - return pursue_traversal; -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -double -htrdr_compute_radiance_lw - (struct htrdr* htrdr, - const size_t ithread, - struct ssp_rng* rng, - const double pos_in[3], - const double dir_in[3], - const double wlen, /* In nanometer */ - const size_t iband, - const size_t iquad) -{ - struct s3d_hit s3d_hit = S3D_HIT_NULL; - struct s3d_hit s3d_hit_prev = S3D_HIT_NULL; - struct svx_hit svx_hit = SVX_HIT_NULL; - struct ssf_phase* phase_hg = NULL; - - double wo[3]; - double pos[3]; - double dir[3]; - double range[2]; - double pos_next[3]; - double dir_next[3]; - double temperature; - double wlen_m = wlen * 1.e-9; - double g; - double w = 0; /* Weight */ - - ASSERT(htrdr && rng && pos_in && dir_in && ithread < htrdr->nthreads); - - /* Setup the phase function for this spectral band & quadrature point */ - CHK(RES_OK == ssf_phase_create - (&htrdr->lifo_allocators[ithread], &ssf_phase_hg, &phase_hg)); - g = htsky_fetch_per_wavelength_particle_phase_function_asymmetry_parameter - (htrdr->sky, wlen); - SSF(phase_hg_setup(phase_hg, g)); - - /* Initialise the random walk */ - d3_set(pos, pos_in); - d3_set(dir, dir_in); - d2(range, 0, INF); - - for(;;) { - struct filter_context ctx = FILTER_CONTEXT_NULL; - - /* Sample an optical thickness */ - ctx.Ts = ssp_ran_exp(rng, 1); - - /* Setup the remaining fields of the hit filter context */ - ctx.rng = rng; - ctx.htsky = htrdr->sky; - ctx.iband = iband; - ctx.iquad = iquad; - - /* Found the first intersection with the surface geometry */ - HTRDR(ground_trace_ray - (htrdr->ground, pos, dir, range, &s3d_hit_prev, &s3d_hit)); - - /* Fit the ray range to the surface distance along the ray */ - range[0] = 0; - range[1] = s3d_hit.distance; - - /* Trace a ray into the participating media */ - HTSKY(trace_ray(htrdr->sky, pos, dir, range, NULL, - hit_filter, &ctx, iband, iquad, &svx_hit)); - - /* No scattering and no surface reflection. - * Congratulation !! You are in space. */ - if(S3D_HIT_NONE(&s3d_hit) && SVX_HIT_NONE(&svx_hit)) { - w = 0; - break; - } - - /* Compute the next position */ - pos_next[0] = pos[0] + dir[0]*ctx.traversal_dst; - pos_next[1] = pos[1] + dir[1]*ctx.traversal_dst; - pos_next[2] = pos[2] + dir[2]*ctx.traversal_dst; - - /* Absorption event. Stop the realisation */ - if(ctx.event_type == EVENT_ABSORPTION) { - ASSERT(!SVX_HIT_NONE(&svx_hit)); - temperature = htsky_fetch_temperature(htrdr->sky, pos_next); - /* weight is planck integrated over the spectral sub-interval */ - w = planck_monochromatic(wlen_m, temperature); - break; - } - - /* Negate the incoming dir to match the convention of the SSF library */ - d3_minus(wo, dir); - - /* Scattering in the volume */ - if(ctx.event_type == EVENT_SCATTERING) { - ASSERT(!SVX_HIT_NONE(&svx_hit)); - ssf_phase_sample(phase_hg, rng, wo, dir_next, NULL); - s3d_hit_prev = S3D_HIT_NULL; - - /* Scattering at a surface */ - } else { - struct htrdr_interface interf = HTRDR_INTERFACE_NULL; - const struct htrdr_mtl* mtl = NULL; - struct ssf_bsdf* bsdf = NULL; - double bounce_reflectivity = 0; - double N[3]; - int type; - ASSERT(ctx.event_type == EVENT_NONE); - ASSERT(!S3D_HIT_NONE(&s3d_hit)); - - /* Fetch the hit interface materal and build its BSDF */ - htrdr_ground_get_interface(htrdr->ground, &s3d_hit, &interf); - mtl = htrdr_interface_fetch_hit_mtl(&interf, dir, &s3d_hit); - HTRDR(mtl_create_bsdf(htrdr, mtl, ithread, wlen, rng, &bsdf)); - - d3_normalize(N, d3_set_f3(N, s3d_hit.normal)); - if(d3_dot(N, wo) < 0) d3_minus(N, N); - - bounce_reflectivity = ssf_bsdf_sample - (bsdf, rng, wo, N, dir_next, &type, NULL); - if(!(type & SSF_REFLECTION)) { /* Handle only reflections */ - bounce_reflectivity = 0; - } - - /* Release the BSDF */ - SSF(bsdf_ref_put(bsdf)); - - if(ssp_rng_canonical(rng) >= bounce_reflectivity) { /* Absorbed at boundary */ - temperature = mtl->temperature; /* Fetch mtl temperature */ - /* Weight is planck integrated over the spectral sub-interval */ - w = temperature > 0 ? planck_monochromatic(wlen_m, temperature) : 0; - break; - } - s3d_hit_prev = s3d_hit; - } - d3_set(pos, pos_next); - d3_set(dir, dir_next); - } - SSF(phase_ref_put(phase_hg)); - return w; -} - diff --git a/src/htrdr_compute_radiance_sw.c b/src/htrdr_compute_radiance_sw.c @@ -1,497 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "htrdr.h" -#include "htrdr_c.h" -#include "htrdr_interface.h" -#include "htrdr_ground.h" -#include "htrdr_solve.h" -#include "htrdr_sun.h" - -#include <high_tune/htsky.h> - -#include <star/s3d.h> -#include <star/ssf.h> -#include <star/ssp.h> -#include <star/svx.h> - -#include <rsys/double2.h> -#include <rsys/float2.h> -#include <rsys/float3.h> - -struct scattering_context { - struct ssp_rng* rng; - const struct htsky* sky; - size_t iband; /* Index of the spectral band */ - size_t iquad; /* Index of the quadrature point into the band */ - - double Ts; /* Sampled optical thickness */ - double traversal_dst; /* Distance traversed along the ray */ -}; -static const struct scattering_context SCATTERING_CONTEXT_NULL = { - NULL, NULL, 0, 0, 0, 0 -}; - -struct transmissivity_context { - struct ssp_rng* rng; - const struct htsky* sky; - size_t iband; /* Index of the spectral */ - size_t iquad; /* Index of the quadrature point into the band */ - - double Ts; /* Sampled optical thickness */ - double Tmin; /* Minimal optical thickness */ - double traversal_dst; /* Distance traversed along the ray */ - - enum htsky_property prop; -}; -static const struct transmissivity_context TRANSMISSION_CONTEXT_NULL = { - NULL, NULL, 0, 0, 0, 0, 0, 0 -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static int -scattering_hit_filter - (const struct svx_hit* hit, - const double org[3], - const double dir[3], - const double range[2], - void* context) -{ - struct scattering_context* ctx = context; - double ks_max; - int pursue_traversal = 1; - ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range); - (void)range; - - ks_max = htsky_fetch_svx_voxel_property(ctx->sky, HTSKY_Ks, - HTSKY_SVX_MAX, HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, &hit->voxel); - - ctx->traversal_dst = hit->distance[0]; - - /* Iterate until a collision occurs into the voxel or until the ray - * does not collide the voxel */ - for(;;) { - /* Compute tau for the current leaf */ - const double vox_dst = hit->distance[1] - ctx->traversal_dst; - const double T = vox_dst * ks_max; - - /* A collision occurs behind `vox_dst' */ - if(ctx->Ts > T) { - ctx->Ts -= T; - ctx->traversal_dst = hit->distance[1]; - pursue_traversal = 1; - break; - - /* A real/null collision occurs before `vox_dst' */ - } else { - double pos[3]; - double proba; - double ks; - const double collision_dst = ctx->Ts / ks_max; - - /* Compute the traversed distance up to the challenged collision */ - ctx->traversal_dst += collision_dst; - ASSERT(ctx->traversal_dst >= hit->distance[0]); - ASSERT(ctx->traversal_dst <= hit->distance[1]); - - /* Stop the ray whenever the traversal distance without any scattering - * event is too high. It means the maximum scattering coefficient has a - * very small value, and the returned radiance is null. This can only - * happen when the voxel has a [quasi] infinite length in the propagation - * direction. */ - if(ctx->traversal_dst > 1e9) break; - - /* Compute the world space position where a collision may occur */ - pos[0] = org[0] + ctx->traversal_dst * dir[0]; - pos[1] = org[1] + ctx->traversal_dst * dir[1]; - pos[2] = org[2] + ctx->traversal_dst * dir[2]; - - ks = htsky_fetch_raw_property(ctx->sky, HTSKY_Ks, - HTSKY_CPNT_MASK_ALL, ctx->iband, ctx->iquad, pos, -DBL_MAX, DBL_MAX); - - /* Handle the case that ks_max is not *really* the max */ - proba = ks / ks_max; - - if(ssp_rng_canonical(ctx->rng) < proba) {/* Collide <=> real scattering */ - pursue_traversal = 0; - break; - } else { /* Null collision */ - ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */ - } - } - } - return pursue_traversal; -} - -static int -transmissivity_hit_filter - (const struct svx_hit* hit, - const double org[3], - const double dir[3], - const double range[2], - void* context) -{ - struct transmissivity_context* ctx = context; - int comp_mask = HTSKY_CPNT_MASK_ALL; - double k_max; - double k_min; - int pursue_traversal = 1; - ASSERT(hit && ctx && !SVX_HIT_NONE(hit) && org && dir && range); - (void)range; - - k_min = htsky_fetch_svx_voxel_property(ctx->sky, ctx->prop, - HTSKY_SVX_MIN, comp_mask, ctx->iband, ctx->iquad, &hit->voxel); - k_max = htsky_fetch_svx_voxel_property(ctx->sky, ctx->prop, - HTSKY_SVX_MAX, comp_mask, ctx->iband, ctx->iquad, &hit->voxel); - ASSERT(k_min <= k_max); - - ctx->Tmin += (hit->distance[1] - hit->distance[0]) * k_min; - ctx->traversal_dst = hit->distance[0]; - - /* Iterate until a collision occurs into the voxel or until the ray - * does not collide the voxel */ - for(;;) { - const double vox_dst = hit->distance[1] - ctx->traversal_dst; - const double Tdif = vox_dst * (k_max-k_min); - - /* A collision occurs behind `vox_dst' */ - if(ctx->Ts > Tdif) { - ctx->Ts -= Tdif; - ctx->traversal_dst = hit->distance[1]; - pursue_traversal = 1; - break; - - /* A real/null collision occurs before `vox_dst' */ - } else { - double x[3]; - double k; - double proba; - double collision_dst = ctx->Ts / (k_max - k_min); - - /* Compute the traversed distance up to the challenged collision */ - ctx->traversal_dst += collision_dst; - ASSERT(ctx->traversal_dst >= hit->distance[0]); - ASSERT(ctx->traversal_dst <= hit->distance[1]); - - /* Compute the world space position where a collision may occur */ - x[0] = org[0] + ctx->traversal_dst * dir[0]; - x[1] = org[1] + ctx->traversal_dst * dir[1]; - x[2] = org[2] + ctx->traversal_dst * dir[2]; - - k = htsky_fetch_raw_property(ctx->sky, ctx->prop, - comp_mask, ctx->iband, ctx->iquad, x, k_min, k_max); - ASSERT(k >= k_min && k <= k_max); - - proba = (k - k_min) / (k_max - k_min); - - if(ssp_rng_canonical(ctx->rng) < proba) { /* Collide */ - pursue_traversal = 0; - break; - } else { /* Null collision */ - ctx->Ts = ssp_ran_exp(ctx->rng, 1); /* Sample a new optical thickness */ - } - } - } - return pursue_traversal; -} - -static double -transmissivity - (struct htrdr* htrdr, - struct ssp_rng* rng, - const enum htsky_property prop, - const size_t iband, - const size_t iquad, - const double pos[3], - const double dir[3], - const double range[2]) -{ - struct svx_hit svx_hit; - struct transmissivity_context transmissivity_ctx = TRANSMISSION_CONTEXT_NULL; - - ASSERT(htrdr && rng && pos && dir && range); - - transmissivity_ctx.rng = rng; - transmissivity_ctx.sky = htrdr->sky; - transmissivity_ctx.iband = iband; - transmissivity_ctx.iquad = iquad; - transmissivity_ctx.Ts = ssp_ran_exp(rng, 1); /* Sample an optical thickness */ - transmissivity_ctx.prop = prop; - - /* Compute the transmissivity */ - HTSKY(trace_ray(htrdr->sky, pos, dir, range, NULL, - transmissivity_hit_filter, &transmissivity_ctx, iband, iquad, &svx_hit)); - - if(SVX_HIT_NONE(&svx_hit)) { - return transmissivity_ctx.Tmin ? exp(-transmissivity_ctx.Tmin) : 1.0; - } else { - return 0; - } -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -double -htrdr_compute_radiance_sw - (struct htrdr* htrdr, - const size_t ithread, - struct ssp_rng* rng, - const int cpnt_mask, /* Combination of enum htrdr_radiance_cpnt_flag */ - const double pos_in[3], - const double dir_in[3], - const double wlen, /* In nanometer */ - const size_t iband, - const size_t iquad) -{ - struct s3d_hit s3d_hit = S3D_HIT_NULL; - struct s3d_hit s3d_hit_tmp = S3D_HIT_NULL; - struct s3d_hit s3d_hit_prev = S3D_HIT_NULL; - struct svx_hit svx_hit = SVX_HIT_NULL; - struct ssf_phase* phase_hg = NULL; - struct ssf_phase* phase_rayleigh = NULL; - - double pos[3]; - double dir[3]; - double range[2]; - double pos_next[3]; - double dir_next[3]; - double band_bounds[2]; /* In nanometers */ - - double R; - double r; /* Random number */ - double wo[3]; /* -dir */ - double pdf; - double Tr; /* Overall transmissivity */ - double Tr_abs; /* Absorption transmissivity */ - double L_sun; /* Sun radiance in W.m^-2.sr^-1 */ - double sun_dir[3]; - double ksi = 1; /* Throughput */ - double w = 0; /* MC weight */ - double g = 0; /* Asymmetry parameter of the HG phase function */ - - ASSERT(htrdr && rng && pos_in && dir_in && ithread < htrdr->nthreads); - ASSERT(htrdr->spectral_type == HTRDR_SPECTRAL_SW - || htrdr->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ); - - CHK(RES_OK == ssf_phase_create - (&htrdr->lifo_allocators[ithread], &ssf_phase_hg, &phase_hg)); - CHK(RES_OK == ssf_phase_create - (&htrdr->lifo_allocators[ithread], &ssf_phase_rayleigh, &phase_rayleigh)); - - /* Setup the phase function for this wavelength */ - g = htsky_fetch_per_wavelength_particle_phase_function_asymmetry_parameter - (htrdr->sky, wlen); - SSF(phase_hg_setup(phase_hg, g)); - - /* Fetch sun properties. Note that the sun spectral data are defined by bands - * that, actually are the same of the SW spectral bands defined in the - * default "ecrad_opt_prot.txt" file provided by the HTGOP project. */ - htsky_get_spectral_band_bounds(htrdr->sky, iband, band_bounds); - ASSERT(band_bounds[0] <= wlen && wlen <= band_bounds[1]); - L_sun = htrdr_sun_get_radiance(htrdr->sun, wlen); - d3_set(pos, pos_in); - d3_set(dir, dir_in); - - if((cpnt_mask & HTRDR_RADIANCE_DIRECT) /* Handle direct contribation */ - && htrdr_sun_is_dir_in_solar_cone(htrdr->sun, dir)) { - /* Check that the ray is not occluded along the submitted range */ - d2(range, 0, FLT_MAX); - HTRDR(ground_trace_ray(htrdr->ground, pos, dir, range, NULL, &s3d_hit_tmp)); - if(!S3D_HIT_NONE(&s3d_hit_tmp)) { - Tr = 0; - } else { - Tr = transmissivity - (htrdr, rng, HTSKY_Kext, iband, iquad , pos, dir, range); - w = L_sun * Tr; - } - } - - if((cpnt_mask & HTRDR_RADIANCE_DIFFUSE) == 0) - goto exit; /* Discard diffuse contribution */ - - /* Radiative random walk */ - for(;;) { - struct scattering_context scattering_ctx = SCATTERING_CONTEXT_NULL; - struct ssf_bsdf* bsdf = NULL; - struct ssf_phase* phase; - double N[3]; - double bounce_reflectivity = 1; - double sun_dir_pdf; - int surface_scattering = 0; /* Define if hit a surface */ - int bsdf_type = 0; - - /* Find the first intersection with a surface */ - d2(range, 0, DBL_MAX); - HTRDR(ground_trace_ray - (htrdr->ground, pos, dir, range, &s3d_hit_prev, &s3d_hit)); - - /* Sample an optical thickness */ - scattering_ctx.Ts = ssp_ran_exp(rng, 1); - - /* Setup the remaining scattering context fields */ - scattering_ctx.rng = rng; - scattering_ctx.sky = htrdr->sky; - scattering_ctx.iband = iband; - scattering_ctx.iquad = iquad; - - /* Define if a scattering event occurs */ - d2(range, 0, s3d_hit.distance); - HTSKY(trace_ray(htrdr->sky, pos, dir, range, NULL, - scattering_hit_filter, &scattering_ctx, iband, iquad, &svx_hit)); - - /* No scattering and no surface reflection. Stop the radiative random walk */ - if(S3D_HIT_NONE(&s3d_hit) && SVX_HIT_NONE(&svx_hit)) { - break; - } - ASSERT(SVX_HIT_NONE(&svx_hit) - || ( svx_hit.distance[0] <= scattering_ctx.traversal_dst - && svx_hit.distance[1] >= scattering_ctx.traversal_dst)); - - /* Negate the incoming dir to match the convention of the SSF library */ - d3_minus(wo, dir); - - /* Define if the scattering occurs at a surface */ - surface_scattering = SVX_HIT_NONE(&svx_hit); - - /* Compute the new position */ - pos_next[0] = pos[0] + dir[0]*scattering_ctx.traversal_dst; - pos_next[1] = pos[1] + dir[1]*scattering_ctx.traversal_dst; - pos_next[2] = pos[2] + dir[2]*scattering_ctx.traversal_dst; - - /* Define the previous hit surface used to avoid self hit */ - s3d_hit_prev = surface_scattering ? s3d_hit : S3D_HIT_NULL; - - /* Define the absorption transmissivity from the current position to the - * next position */ - d2(range, 0, scattering_ctx.traversal_dst); - Tr_abs = transmissivity - (htrdr, rng, HTSKY_Ka, iband, iquad, pos, dir, range); - if(Tr_abs <= 0) break; - - /* Sample the scattering direction */ - if(surface_scattering) { /* Scattering at a surface */ - struct htrdr_interface interf = HTRDR_INTERFACE_NULL; - const struct htrdr_mtl* mtl = NULL; - - /* Fetch the hit interface materal and build its BSDF */ - htrdr_ground_get_interface(htrdr->ground, &s3d_hit, &interf); - mtl = htrdr_interface_fetch_hit_mtl(&interf, dir, &s3d_hit); - HTRDR(mtl_create_bsdf(htrdr, mtl, ithread, wlen, rng, &bsdf)); - - /* Revert the normal if necessary to match the SSF convention */ - d3_normalize(N, d3_set_f3(N, s3d_hit.normal)); - if(d3_dot(N, wo) < 0) d3_minus(N, N); - - /* Sample scattering direction */ - bounce_reflectivity = ssf_bsdf_sample - (bsdf, rng, wo, N, dir_next, &bsdf_type, &pdf); - if(!(bsdf_type & SSF_REFLECTION)) { /* Handle only reflections */ - bounce_reflectivity = 0; - } - - } else { /* Scattering in a volume */ - double ks_particle; /* Scattering coefficient of the particles */ - double ks_gas; /* Scattering coefficient of the gaz */ - double ks; /* Overall scattering coefficient */ - - ks_gas = htsky_fetch_raw_property(htrdr->sky, HTSKY_Ks, - HTSKY_CPNT_FLAG_GAS, iband, iquad, pos_next, -DBL_MAX, DBL_MAX); - ks_particle = htsky_fetch_raw_property(htrdr->sky, HTSKY_Ks, - HTSKY_CPNT_FLAG_PARTICLES, iband, iquad, pos_next, -DBL_MAX, DBL_MAX); - ks = ks_particle + ks_gas; - - r = ssp_rng_canonical(rng); - if(r < ks_gas / ks) { /* Gas scattering */ - phase = phase_rayleigh; - } else { /* Cloud scattering */ - phase = phase_hg; - } - - /* Sample scattering direction */ - ssf_phase_sample(phase, rng, wo, dir_next, NULL); - ssf_phase_ref_get(phase); - } - - /* Sample the direction of the direct contribution */ - if(surface_scattering && (bsdf_type & SSF_SPECULAR)) { - if(!htrdr_sun_is_dir_in_solar_cone(htrdr->sun, dir_next)) { - R = 0; /* No direct lightning */ - } else { - sun_dir[0] = dir_next[0]; - sun_dir[1] = dir_next[1]; - sun_dir[2] = dir_next[2]; - R = d3_dot(N, sun_dir)<0/* Below the ground*/ ? 0 : bounce_reflectivity; - } - sun_dir_pdf = 1.0; - } else { - /* Sample a sun direction */ - sun_dir_pdf = htrdr_sun_sample_direction(htrdr->sun, rng, sun_dir); - if(surface_scattering) { - R = d3_dot(N, sun_dir) < 0/* Below the ground */ - ? 0 : ssf_bsdf_eval(bsdf, wo, N, sun_dir) * d3_dot(N, sun_dir); - } else { - R = ssf_phase_eval(phase, wo, sun_dir); - } - } - - /* The direct contribution to the scattering point is not null so we need - * to compute the transmissivity from sun to scatt pt */ - if(R <= 0) { - Tr = 0; - } else { - /* Check that the sun is visible from the new position */ - d2(range, 0, FLT_MAX); - HTRDR(ground_trace_ray - (htrdr->ground, pos_next, sun_dir, range, &s3d_hit_prev, &s3d_hit_tmp)); - - /* Compute the sun transmissivity */ - if(!S3D_HIT_NONE(&s3d_hit_tmp)) { - Tr = 0; - } else { - Tr = transmissivity - (htrdr, rng, HTSKY_Kext, iband, iquad, pos_next, sun_dir, range); - } - } - - /* Release the scattering function */ - if(surface_scattering) { - SSF(bsdf_ref_put(bsdf)); - } else { - SSF(phase_ref_put(phase)); - } - - /* Update the MC weight */ - ksi *= Tr_abs; - w += ksi * L_sun * Tr * R / sun_dir_pdf; - - /* Russian roulette wrt surface scattering */ - if(surface_scattering && ssp_rng_canonical(rng) >= bounce_reflectivity) - break; - - /* Setup the next random walk state */ - d3_set(pos, pos_next); - d3_set(dir, dir_next); - } - -exit: - SSF(phase_ref_put(phase_hg)); - SSF(phase_ref_put(phase_rayleigh)); - return w; -} - diff --git a/src/htrdr_draw_map.c b/src/htrdr_draw_map.c @@ -1,1207 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#define _POSIX_C_SOURCE 200112L /* nanosleep && nextafter */ - -#include "htrdr.h" -#include "htrdr_c.h" -#include "htrdr_buffer.h" -#include "htrdr_camera.h" -#include "htrdr_cie_xyz.h" -#include "htrdr_ran_wlen.h" -#include "htrdr_rectangle.h" -#include "htrdr_solve.h" -#include "htrdr_sun.h" - -#include <high_tune/htsky.h> - -#include <rsys/algorithm.h> -#include <rsys/clock_time.h> -#include <rsys/cstr.h> -#include <rsys/dynamic_array_u32.h> -#include <rsys/math.h> -#include <rsys/mutex.h> -#include <star/ssp.h> - -#include <omp.h> -#include <mpi.h> -#include <time.h> -#include <unistd.h> - -#define RNG_SEQUENCE_SIZE 10000 - -#define TILE_MCODE_NULL UINT32_MAX -#define TILE_SIZE 32 /* Definition in X & Y of a tile */ -STATIC_ASSERT(IS_POW2(TILE_SIZE), TILE_SIZE_must_be_a_power_of_2); - -enum pixel_format { - PIXEL_XWAVE, - PIXEL_IMAGE -}; - -union pixel { - struct htrdr_pixel_flux flux; - struct htrdr_pixel_xwave xwave; - struct htrdr_pixel_image image; -}; - -/* Tile of row ordered image pixels */ -struct tile { - struct list_node node; - struct mem_allocator* allocator; - ref_T ref; - - struct tile_data { - uint16_t x, y; /* 2D coordinates of the tile in tile space */ - enum pixel_format format; - /* Simulate the flexible array member of the C99 standard. */ - union pixel pixels[1/*Dummy element*/]; - } data; -}; - -/* List of tile to compute onto the MPI process. */ -struct proc_work { - struct mutex* mutex; - struct darray_u32 tiles; /* #tiles to render */ - size_t itile; /* Next tile to render in the above list of tiles */ -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static FINLINE uint16_t -morton2D_decode(const uint32_t u32) -{ - uint32_t x = u32 & 0x55555555; - x = (x | (x >> 1)) & 0x33333333; - x = (x | (x >> 2)) & 0x0F0F0F0F; - x = (x | (x >> 4)) & 0x00FF00FF; - x = (x | (x >> 8)) & 0x0000FFFF; - return (uint16_t)x; -} - -static FINLINE uint32_t -morton2D_encode(const uint16_t u16) -{ - uint32_t u32 = u16; - u32 = (u32 | (u32 << 8)) & 0x00FF00FF; - u32 = (u32 | (u32 << 4)) & 0X0F0F0F0F; - u32 = (u32 | (u32 << 2)) & 0x33333333; - u32 = (u32 | (u32 << 1)) & 0x55555555; - return u32; -} - -static INLINE enum pixel_format -spectral_type_to_pixfmt(const enum htrdr_spectral_type spectral_type) -{ - enum pixel_format pixfmt; - switch(spectral_type) { - case HTRDR_SPECTRAL_LW: pixfmt = PIXEL_XWAVE; break; - case HTRDR_SPECTRAL_SW: pixfmt = PIXEL_XWAVE; break; - case HTRDR_SPECTRAL_SW_CIE_XYZ: pixfmt = PIXEL_IMAGE; break; - default: FATAL("Unreachable code.\n"); break; - } - return pixfmt; -} - -static FINLINE struct tile* -tile_create(struct mem_allocator* allocator, const enum pixel_format fmt) -{ - struct tile* tile; - const size_t tile_sz = - sizeof(struct tile) - sizeof(union pixel)/*rm dummy pixel*/; - const size_t buf_sz = /* Flexiblbe array element */ - TILE_SIZE*TILE_SIZE*sizeof(union pixel); - ASSERT(allocator); - - tile = MEM_ALLOC(allocator, tile_sz+buf_sz); - if(!tile) return NULL; - - tile->data.format = fmt; - - ref_init(&tile->ref); - list_init(&tile->node); - tile->allocator = allocator; - ASSERT(IS_ALIGNED(&tile->data.pixels, ALIGNOF(union pixel))); - - return tile; -} - -static INLINE void -tile_ref_get(struct tile* tile) -{ - ASSERT(tile); - tile_ref_get(tile); -} - -static INLINE void -release_tile(ref_T* ref) -{ - struct tile* tile = CONTAINER_OF(ref, struct tile, ref); - ASSERT(ref); - MEM_RM(tile->allocator, tile); -} - -static INLINE void -tile_ref_put(struct tile* tile) -{ - ASSERT(tile); - ref_put(&tile->ref, release_tile); -} - -static FINLINE union pixel* -tile_at - (struct tile* tile, - const size_t x, /* In tile space */ - const size_t y) /* In tile space */ -{ - ASSERT(tile && x < TILE_SIZE && y < TILE_SIZE); - return tile->data.pixels + (y*TILE_SIZE + x); -} - -static void -write_tile_data - (struct htrdr* htrdr, - const struct htrdr_sensor* sensor, - struct htrdr_buffer* buf, - const struct tile_data* tile_data) -{ - struct htrdr_buffer_layout layout = HTRDR_BUFFER_LAYOUT_NULL; - size_t icol, irow; - size_t irow_tile; - size_t ncols_tile, nrows_tile; - char* buf_mem; - ASSERT(htrdr && sensor && buf && tile_data); - (void)htrdr, (void)sensor; - - htrdr_buffer_get_layout(buf, &layout); - buf_mem = htrdr_buffer_get_data(buf); - ASSERT(layout.elmt_size - == htrdr_spectral_type_get_pixsz(htrdr->spectral_type, sensor->type)); - - /* Compute the row/column of the tile origin into the buffer */ - icol = tile_data->x * (size_t)TILE_SIZE; - irow = tile_data->y * (size_t)TILE_SIZE; - - /* Define the number of tile row/columns to write into the buffer */ - ncols_tile = MMIN(icol + TILE_SIZE, layout.width) - icol; - nrows_tile = MMIN(irow + TILE_SIZE, layout.height) - irow; - - /* Copy the row ordered tile data */ - FOR_EACH(irow_tile, 0, nrows_tile) { - char* buf_row = buf_mem + (irow + irow_tile) * layout.pitch; - char* buf_col = buf_row + icol * layout.elmt_size; - const union pixel* tile_row = tile_data->pixels + irow_tile*TILE_SIZE; - size_t x; - - FOR_EACH(x, 0, ncols_tile) { - switch(tile_data->format) { - case PIXEL_XWAVE: - ((struct htrdr_pixel_xwave*)buf_col)[x] = tile_row[x].xwave; - break; - case PIXEL_IMAGE: - ((struct htrdr_pixel_image*)buf_col)[x] = tile_row[x].image; - break; - default: FATAL("Unreachable code.\n"); break; - } - } - } -} - -static INLINE void -proc_work_init(struct mem_allocator* allocator, struct proc_work* work) -{ - ASSERT(work); - darray_u32_init(allocator, &work->tiles); - work->itile = 0; - CHK(work->mutex = mutex_create()); -} - -static INLINE void -proc_work_release(struct proc_work* work) -{ - darray_u32_release(&work->tiles); - mutex_destroy(work->mutex); -} - -static INLINE void -proc_work_reset(struct proc_work* work) -{ - ASSERT(work); - mutex_lock(work->mutex); - darray_u32_clear(&work->tiles); - work->itile = 0; - mutex_unlock(work->mutex); -} - -static INLINE void -proc_work_add_tile(struct proc_work* work, const uint32_t mcode) -{ - mutex_lock(work->mutex); - CHK(darray_u32_push_back(&work->tiles, &mcode) == RES_OK); - mutex_unlock(work->mutex); -} - -static INLINE uint32_t -proc_work_get_tile(struct proc_work* work) -{ - uint32_t mcode; - ASSERT(work); - mutex_lock(work->mutex); - if(work->itile >= darray_u32_size_get(&work->tiles)) { - mcode = TILE_MCODE_NULL; - } else { - mcode = darray_u32_cdata_get(&work->tiles)[work->itile]; - ++work->itile; - } - mutex_unlock(work->mutex); - return mcode; -} - -static INLINE size_t -proc_work_get_ntiles(struct proc_work* work) -{ - size_t sz = 0; - ASSERT(work); - mutex_lock(work->mutex); - sz = darray_u32_size_get(&work->tiles); - mutex_unlock(work->mutex); - return sz; -} - -static void -mpi_wait_for_request(struct htrdr* htrdr, MPI_Request* req) -{ - ASSERT(htrdr && req); - - /* Wait for process synchronisation */ - for(;;) { - struct timespec t; - int complete; - t.tv_sec = 0; - t.tv_nsec = 10000000; /* 10ms */ - - mutex_lock(htrdr->mpi_mutex); - MPI(Test(req, &complete, MPI_STATUS_IGNORE)); - mutex_unlock(htrdr->mpi_mutex); - if(complete) break; - - nanosleep(&t, NULL); - } -} - -static void -mpi_probe_thieves - (struct htrdr* htrdr, - struct proc_work* work, - ATOMIC* probe_thieves) -{ - uint32_t tiles[UINT8_MAX]; - struct timespec t; - ASSERT(htrdr && work && probe_thieves); - - if(htrdr->mpi_nprocs == 1) /* The process is alone. No thief is possible */ - return; - - t.tv_sec = 0; - - /* Protect MPI calls of multiple invocations from concurrent threads */ - #define P_MPI(Func) { \ - mutex_lock(htrdr->mpi_mutex); \ - MPI(Func); \ - mutex_unlock(htrdr->mpi_mutex); \ - } (void)0 - - while(ATOMIC_GET(probe_thieves)) { - MPI_Status status; - size_t itile; - int msg; - - /* Probe if a steal request was submitted by any processes */ - P_MPI(Iprobe(MPI_ANY_SOURCE, HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD, &msg, - &status)); - - if(msg) { /* A steal request was posted */ - MPI_Request req; - uint8_t ntiles_to_steal; - - /* Asynchronously receive the steal request */ - P_MPI(Irecv(&ntiles_to_steal, 1, MPI_UINT8_T, status.MPI_SOURCE, - HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD, &req)); - - /* Wait for the completion of the steal request */ - mpi_wait_for_request(htrdr, &req); - - /* Thief some tiles */ - FOR_EACH(itile, 0, ntiles_to_steal) { - tiles[itile] = proc_work_get_tile(work); - } - P_MPI(Send(&tiles, ntiles_to_steal, MPI_UINT32_T, status.MPI_SOURCE, - HTRDR_MPI_WORK_STEALING, MPI_COMM_WORLD)); - } - t.tv_nsec = 500000000; /* 500ms */ - nanosleep(&t, NULL); - } - #undef P_MPI -} - -static int -mpi_sample_working_process(struct htrdr* htrdr, struct ssp_rng* rng) -{ - int iproc, i; - int dst_rank; - ASSERT(htrdr && rng && htrdr->mpi_nworking_procs); - - /* Sample the index of the 1st active process */ - iproc = (int)(ssp_rng_canonical(rng) * (double)htrdr->mpi_nworking_procs); - - /* Find the rank of the sampled active process. Use a simple linear search - * since the overall number of processes should be quite low; at most few - * dozens. */ - i = 0; - FOR_EACH(dst_rank, 0, htrdr->mpi_nprocs) { - if(htrdr->mpi_working_procs[dst_rank] == 0) continue; /* Inactive process */ - if(i == iproc) break; /* The rank of the sampled process is found */ - ++i; - } - ASSERT(dst_rank < htrdr->mpi_nprocs); - return dst_rank; -} - -/* Return the number of stolen tiles */ -static size_t -mpi_steal_work - (struct htrdr* htrdr, - struct ssp_rng* rng, - struct proc_work* work) -{ - MPI_Request req; - size_t itile; - size_t nthieves = 0; - uint32_t tiles[UINT8_MAX]; /* Morton code of the stolen tile */ - int proc_to_steal; /* Process to steal */ - uint8_t ntiles_to_steal = MMIN((uint8_t)(htrdr->nthreads*2), 16); - ASSERT(htrdr && rng && work && htrdr->nthreads < UINT8_MAX); - - /* Protect MPI calls of multiple invocations from concurrent threads */ - #define P_MPI(Func) { \ - mutex_lock(htrdr->mpi_mutex); \ - MPI(Func); \ - mutex_unlock(htrdr->mpi_mutex); \ - } (void)0 - - /* No more working process => nohting to steal */ - if(!htrdr->mpi_nworking_procs) return 0; - - /* Sample a process to steal */ - proc_to_steal = mpi_sample_working_process(htrdr, rng); - - /* Send a steal request to the sampled process and wait for a response */ - P_MPI(Send(&ntiles_to_steal, 1, MPI_UINT8_T, proc_to_steal, - HTRDR_MPI_STEAL_REQUEST, MPI_COMM_WORLD)); - - /* Receive the stolen tile from the sampled process */ - P_MPI(Irecv(tiles, ntiles_to_steal, MPI_UINT32_T, proc_to_steal, - HTRDR_MPI_WORK_STEALING, MPI_COMM_WORLD, &req)); - - mpi_wait_for_request(htrdr, &req); - - FOR_EACH(itile, 0, ntiles_to_steal) { - if(tiles[itile] == TILE_MCODE_NULL) { - ASSERT(htrdr->mpi_working_procs[proc_to_steal] != 0); - htrdr->mpi_working_procs[proc_to_steal] = 0; - htrdr->mpi_nworking_procs--; - break; - } - proc_work_add_tile(work, tiles[itile]); - ++nthieves; - } - #undef P_MPI - return nthieves; -} - -static res_T -mpi_gather_tiles - (struct htrdr* htrdr, - const struct htrdr_sensor* sensor, - struct htrdr_buffer* buf, - const size_t ntiles, - struct list_node* tiles) -{ - /* Compute the size of the tile_data */ - const size_t msg_sz = - sizeof(struct tile_data) - sizeof(union pixel)/*dummy*/ - + TILE_SIZE*TILE_SIZE*sizeof(union pixel); - - struct list_node* node = NULL; - struct tile* tile = NULL; - res_T res = RES_OK; - ASSERT(htrdr && tiles); - ASSERT(htrdr->mpi_rank != 0 || buf); - (void)ntiles; - - if(htrdr->mpi_rank != 0) { /* Non master process */ - /* Send the computed tile to the master process */ - LIST_FOR_EACH(node, tiles) { - struct tile* t = CONTAINER_OF(node, struct tile, node); - MPI(Send(&t->data, (int)msg_sz, MPI_CHAR, 0, - HTRDR_MPI_TILE_DATA, MPI_COMM_WORLD)); - } - } else { /* Master process */ - size_t itile = 0; - - LIST_FOR_EACH(node, tiles) { - struct tile* t = CONTAINER_OF(node, struct tile, node); - write_tile_data(htrdr, sensor, buf, &t->data); - ++itile; - } - - if(itile != ntiles) { - enum pixel_format pixfmt; - ASSERT(htrdr->mpi_nprocs > 1); - - /* Create a temporary tile to receive the tile data computed by the - * concurrent MPI processes */ - pixfmt = spectral_type_to_pixfmt(htrdr->spectral_type); - tile = tile_create(htrdr->allocator, pixfmt); - if(!tile) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "could not allocate the temporary tile used to gather the process " - "output data -- %s.\n", res_to_cstr(res)); - goto error; - } - - /* Receive the tile data of the concurrent MPI processes */ - FOR_EACH(itile, itile, ntiles) { - MPI(Recv(&tile->data, (int)msg_sz, MPI_CHAR, MPI_ANY_SOURCE, - HTRDR_MPI_TILE_DATA, MPI_COMM_WORLD, MPI_STATUS_IGNORE)); - write_tile_data(htrdr, sensor, buf, &tile->data); - } - } - } - -exit: - if(tile) tile_ref_put(tile); - return res; -error: - goto exit; -} - -static void -draw_pixel_image - (struct htrdr* htrdr, - const size_t ithread, - const size_t ipix[2], - const double pix_sz[2], /* Size of a pixel in the normalized image plane */ - const struct htrdr_camera* cam, - const size_t spp, - struct ssp_rng* rng, - struct htrdr_pixel_image* pixel) -{ - struct htrdr_accum XYZ[3]; /* X, Y, and Z */ - struct htrdr_accum time; - size_t ichannel; - ASSERT(ipix && ipix && pix_sz && cam && rng && pixel); - ASSERT(htrdr->spectral_type == HTRDR_SPECTRAL_SW_CIE_XYZ); - - /* Reset accumulators */ - XYZ[0] = HTRDR_ACCUM_NULL; - XYZ[1] = HTRDR_ACCUM_NULL; - XYZ[2] = HTRDR_ACCUM_NULL; - time = HTRDR_ACCUM_NULL; - - FOR_EACH(ichannel, 0, 3) { - size_t isamp; - - FOR_EACH(isamp, 0, spp) { - struct time t0, t1; - double pix_samp[2]; - double ray_org[3]; - double ray_dir[3]; - double weight; - double r0, r1, r2; - double wlen; /* Sampled wavelength into the spectral band */ - double pdf; - size_t iband; /* Sampled spectral band */ - size_t iquad; /* Sampled quadrature point into the spectral band */ - double usec; - - /* Begin the registration of the time spent to in the realisation */ - time_current(&t0); - - /* Sample a position into the pixel, in the normalized image plane */ - pix_samp[0] = ((double)ipix[0] + ssp_rng_canonical(rng)) * pix_sz[0]; - pix_samp[1] = ((double)ipix[1] + ssp_rng_canonical(rng)) * pix_sz[1]; - - /* Generate a ray starting from the pinhole camera and passing through the - * pixel sample */ - htrdr_camera_ray(cam, pix_samp, ray_org, ray_dir); - - r0 = ssp_rng_canonical(rng); - r1 = ssp_rng_canonical(rng); - r2 = ssp_rng_canonical(rng); - - /* Sample a spectral band and a quadrature point */ - switch(ichannel) { - case 0: wlen = htrdr_cie_xyz_sample_X(htrdr->cie, r0, r1, &pdf); break; - case 1: wlen = htrdr_cie_xyz_sample_Y(htrdr->cie, r0, r1, &pdf); break; - case 2: wlen = htrdr_cie_xyz_sample_Z(htrdr->cie, r0, r1, &pdf); break; - default: FATAL("Unreachable code.\n"); break; - } - - iband = htsky_find_spectral_band(htrdr->sky, wlen); - iquad = htsky_spectral_band_sample_quadrature(htrdr->sky, r2, iband); - - /* Compute the radiance in W/m^2/sr/m */ - weight = htrdr_compute_radiance_sw(htrdr, ithread, rng, - HTRDR_RADIANCE_ALL, ray_org, ray_dir, wlen, iband, iquad); - ASSERT(weight >= 0); - - pdf *= 1.e9; /* Transform the pdf from nm^-1 to m^-1 */ - weight /= pdf; /* In W/m^2/sr */ - - /* End the registration of the per realisation time */ - time_sub(&t0, time_current(&t1), &t0); - usec = (double)time_val(&t0, TIME_NSEC) * 0.001; - - /* Update the pixel accumulator of the current channel */ - XYZ[ichannel].sum_weights += weight; - XYZ[ichannel].sum_weights_sqr += weight*weight; - XYZ[ichannel].nweights += 1; - - /* Update the pixel accumulator of per realisation time */ - time.sum_weights += usec; - time.sum_weights_sqr += usec*usec; - time.nweights += 1; - } - } - - /* Flush pixel data */ - htrdr_accum_get_estimation(XYZ+0, &pixel->X); - htrdr_accum_get_estimation(XYZ+1, &pixel->Y); - htrdr_accum_get_estimation(XYZ+2, &pixel->Z); - pixel->time = time; -} - -static void -draw_pixel_flux - (struct htrdr* htrdr, - const size_t ithread, - const size_t ipix[2], - const double pix_sz[2], /* Size of a pixel in the normalized image plane */ - const struct htrdr_sensor* sensor, - const size_t spp, - struct ssp_rng* rng, - struct htrdr_pixel_flux* pixel) -{ - struct htrdr_accum flux; - struct htrdr_accum time; - size_t isamp; - ASSERT(ipix && ipix && pix_sz && sensor && rng && pixel); - ASSERT(sensor->type == HTRDR_SENSOR_RECTANGLE); - ASSERT(htrdr->spectral_type == HTRDR_SPECTRAL_LW - || htrdr->spectral_type == HTRDR_SPECTRAL_SW); - - /* Reset the pixel accumulators */ - flux = HTRDR_ACCUM_NULL; - time = HTRDR_ACCUM_NULL; - - FOR_EACH(isamp, 0, spp) { - struct time t0, t1; - double ray_org[3]; - double ray_dir[3]; - double weight; - double r0, r1, r2; - double wlen; - size_t iband; - size_t iquad; - double usec; - double band_pdf; - res_T res = RES_OK; - - /* Begin the registration of the time spent in the realisation */ - time_current(&t0); - - res = htrdr_sensor_sample_primary_ray(&htrdr->sensor, htrdr, ipix, - pix_sz, rng, ray_org, ray_dir); - if(res != RES_OK) continue; /* Reject the current sample */ - - r0 = ssp_rng_canonical(rng); - r1 = ssp_rng_canonical(rng); - r2 = ssp_rng_canonical(rng); - - /* Sample a wavelength */ - wlen = htrdr_ran_wlen_sample(htrdr->ran_wlen, r0, r1, &band_pdf); - - /* Select the associated band and sample a quadrature point */ - iband = htsky_find_spectral_band(htrdr->sky, wlen); - iquad = htsky_spectral_band_sample_quadrature(htrdr->sky, r2, iband); - - if(htrdr->spectral_type == HTRDR_SPECTRAL_LW) { - weight = htrdr_compute_radiance_lw(htrdr, ithread, rng, ray_org, - ray_dir, wlen, iband, iquad); - weight *= PI / band_pdf; /* Transform weight from W/m^2/sr/m to W/m^2 */ - } else { - double sun_dir[3]; - double N[3]; - double L_direct; - double L_diffuse; - double cos_N_sun_dir; - double sun_solid_angle; - ASSERT(htrdr->spectral_type == HTRDR_SPECTRAL_SW); - - /* Compute direct contribution if necessary */ - htrdr_sun_sample_direction(htrdr->sun, rng, sun_dir); - htrdr_rectangle_get_normal(sensor->rectangle, N); - cos_N_sun_dir = d3_dot(N, sun_dir); - - if(cos_N_sun_dir <= 0) { - L_direct = 0; - } else { - L_direct = htrdr_compute_radiance_sw(htrdr, ithread, rng, - HTRDR_RADIANCE_DIRECT, ray_org, sun_dir, wlen, iband, iquad); - } - - /* Compute diffuse contribution */ - L_diffuse = htrdr_compute_radiance_sw(htrdr, ithread, rng, - HTRDR_RADIANCE_DIFFUSE, ray_org, ray_dir, wlen, iband, iquad); - - sun_solid_angle = htrdr_sun_get_solid_angle(htrdr->sun); - - /* Compute the weight in W/m^2/m */ - weight = cos_N_sun_dir * sun_solid_angle * L_direct + PI * L_diffuse; - - /* Importance sampling: correct weight with pdf */ - weight /= band_pdf; /* In W/m^2 */ - } - - /* End the registration of the per realisation time */ - time_sub(&t0, time_current(&t1), &t0); - usec = (double)time_val(&t0, TIME_NSEC) * 0.001; - - /* Update the pixel accumulator of the flux */ - flux.sum_weights += weight; - flux.sum_weights_sqr += weight*weight; - flux.nweights += 1; - - /* Update the pixel accumulator of per realisation time */ - time.sum_weights += usec; - time.sum_weights_sqr += usec*usec; - time.nweights += 1; - } - - /* Save the per realisation integration time */ - pixel->flux = flux; - pixel->time = time; -} - -static INLINE double -radiance_temperature - (struct htrdr* htrdr, - const double radiance) /* In W/m^2/sr */ -{ - double temperature = 0; - double radiance_avg = radiance; - res_T res = RES_OK; - ASSERT(htrdr && radiance >= 0); - - /* From integrated radiance to average radiance in W/m^2/sr/m */ - if(htrdr->wlen_range_m[0] != htrdr->wlen_range_m[1]) { /* !monochromatic */ - radiance_avg /= (htrdr->wlen_range_m[1] - htrdr->wlen_range_m[0]); - } - - res = brightness_temperature - (htrdr, - htrdr->wlen_range_m[0], - htrdr->wlen_range_m[1], - radiance_avg, - &temperature); - if(res != RES_OK) { - htrdr_log_warn(htrdr, - "Could not compute the brightness temperature for the radiance %g.\n", - radiance_avg); - temperature = 0; - } - return temperature; -} - -static void -draw_pixel_xwave - (struct htrdr* htrdr, - const size_t ithread, - const size_t ipix[2], - const double pix_sz[2], /* Size of a pixel in the normalized image plane */ - const struct htrdr_sensor* sensor, - const size_t spp, - struct ssp_rng* rng, - struct htrdr_pixel_xwave* pixel) -{ - struct htrdr_accum radiance; - struct htrdr_accum time; - size_t isamp; - double temp_min, temp_max; - ASSERT(ipix && ipix && pix_sz && sensor && rng && pixel); - ASSERT(sensor->type == HTRDR_SENSOR_CAMERA); - ASSERT(htrdr->spectral_type == HTRDR_SPECTRAL_LW - || htrdr->spectral_type == HTRDR_SPECTRAL_SW); - - /* Reset the pixel accumulators */ - radiance = HTRDR_ACCUM_NULL; - time = HTRDR_ACCUM_NULL; - - FOR_EACH(isamp, 0, spp) { - struct time t0, t1; - double ray_org[3]; - double ray_dir[3]; - double weight; - double r0, r1, r2; - double wlen; - size_t iband; - size_t iquad; - double usec; - double band_pdf; - res_T res = RES_OK; - - /* Begin the registration of the time spent in the realisation */ - time_current(&t0); - - res = htrdr_sensor_sample_primary_ray(sensor, htrdr, ipix, - pix_sz, rng, ray_org, ray_dir); - if(res != RES_OK) continue; /* Reject the current sample */ - - r0 = ssp_rng_canonical(rng); - r1 = ssp_rng_canonical(rng); - r2 = ssp_rng_canonical(rng); - - /* Sample a wavelength */ - wlen = htrdr_ran_wlen_sample(htrdr->ran_wlen, r0, r1, &band_pdf); - - /* Select the associated band and sample a quadrature point */ - iband = htsky_find_spectral_band(htrdr->sky, wlen); - iquad = htsky_spectral_band_sample_quadrature(htrdr->sky, r2, iband); - - /* Compute the spectral radiance in W/m^2/sr/m */ - switch(htrdr->spectral_type) { - case HTRDR_SPECTRAL_LW: - weight = htrdr_compute_radiance_lw(htrdr, ithread, rng, ray_org, - ray_dir, wlen, iband, iquad); - break; - case HTRDR_SPECTRAL_SW: - weight = htrdr_compute_radiance_sw(htrdr, ithread, rng, - HTRDR_RADIANCE_ALL, ray_org, ray_dir, wlen, iband, iquad); - break; - default: FATAL("Unreachable code.\n"); break; - } - ASSERT(weight >= 0); - /* Importance sampling: correct weight with pdf */ - weight /= band_pdf; /* In W/m^2/sr */ - - /* End the registration of the per realisation time */ - time_sub(&t0, time_current(&t1), &t0); - usec = (double)time_val(&t0, TIME_NSEC) * 0.001; - - /* Update the pixel accumulator of the current channel */ - radiance.sum_weights += weight; - radiance.sum_weights_sqr += weight*weight; - radiance.nweights += 1; - - /* Update the pixel accumulator of per realisation time */ - time.sum_weights += usec; - time.sum_weights_sqr += usec*usec; - time.nweights += 1; - } - - /* Compute the estimation of the pixel radiance */ - htrdr_accum_get_estimation(&radiance, &pixel->radiance); - - /* Save the per realisation integration time */ - pixel->time = time; - - /* Compute the brightness_temperature of the pixel and estimate its standard - * error if the sources were in the medium (<=> longwave) */ - if(htrdr->spectral_type == HTRDR_SPECTRAL_LW) { - pixel->radiance_temperature.E = radiance_temperature(htrdr, pixel->radiance.E); - temp_min = radiance_temperature(htrdr, pixel->radiance.E - pixel->radiance.SE); - temp_max = radiance_temperature(htrdr, pixel->radiance.E + pixel->radiance.SE); - pixel->radiance_temperature.SE = temp_max - temp_min; - } -} - -static res_T -draw_tile - (struct htrdr* htrdr, - const size_t ithread, - const int64_t tile_mcode, /* For debug only */ - const size_t tile_org[2], /* Origin of the tile in pixel space */ - const size_t tile_sz[2], /* Definition of the tile */ - const double pix_sz[2], /* Size of a pixel in the normalized image plane */ - const struct htrdr_sensor* sensor, - const size_t spp, /* #samples per pixel */ - struct ssp_rng* rng, - struct tile* tile) -{ - size_t npixels; - size_t mcode; /* Morton code of tile pixel */ - ASSERT(htrdr && tile_org && tile_sz && pix_sz && sensor && spp && tile); - (void)tile_mcode; - /* Adjust the #pixels to process them wrt a morton order */ - npixels = round_up_pow2(MMAX(tile_sz[0], tile_sz[1])); - npixels *= npixels; - - FOR_EACH(mcode, 0, npixels) { - union pixel* pixel; - size_t ipix_tile[2]; /* Pixel coord in the tile */ - size_t ipix[2]; /* Pixel coord in the buffer */ - - ipix_tile[0] = morton2D_decode((uint32_t)(mcode>>0)); - if(ipix_tile[0] >= tile_sz[0]) continue; /* Pixel is out of tile */ - ipix_tile[1] = morton2D_decode((uint32_t)(mcode>>1)); - if(ipix_tile[1] >= tile_sz[1]) continue; /* Pixel is out of tile */ - - /* Fetch and reset the pixel accumulator */ - pixel = tile_at(tile, ipix_tile[0], ipix_tile[1]); - - /* Compute the pixel coordinate */ - ipix[0] = tile_org[0] + ipix_tile[0]; - ipix[1] = tile_org[1] + ipix_tile[1]; - - /* Draw the pixel */ - switch(sensor->type) { - case HTRDR_SENSOR_RECTANGLE: - draw_pixel_flux - (htrdr, ithread, ipix, pix_sz, sensor, spp, rng, &pixel->flux); - break; - case HTRDR_SENSOR_CAMERA: - switch(htrdr->spectral_type) { - case HTRDR_SPECTRAL_LW: - case HTRDR_SPECTRAL_SW: - draw_pixel_xwave - (htrdr, ithread, ipix, pix_sz, sensor, spp, rng, &pixel->xwave); - break; - case HTRDR_SPECTRAL_SW_CIE_XYZ: - draw_pixel_image - (htrdr, ithread, ipix, pix_sz, sensor->camera, spp, rng, &pixel->image); - break; - default: FATAL("Unreachable code.\n"); break; - } - break; - default: FATAL("Unreachable code.\n"); break; - } - } - return RES_OK; -} - -static res_T -draw_image - (struct htrdr* htrdr, - const struct htrdr_sensor* sensor, - const size_t width, /* Image width */ - const size_t height, /* Image height */ - const size_t spp, - const size_t ntiles_x, - const size_t ntiles_y, - const size_t ntiles_adjusted, - const double pix_sz[2], /* Pixel size in the normalized image plane */ - struct proc_work* work, - struct list_node* tiles) -{ - struct ssp_rng* rng_proc = NULL; - size_t nthreads = 0; - size_t nthieves = 0; - size_t proc_ntiles = 0; - enum pixel_format pixfmt; - ATOMIC nsolved_tiles = 0; - ATOMIC res = RES_OK; - ASSERT(htrdr && sensor && spp && ntiles_adjusted && work && tiles); - ASSERT(pix_sz && pix_sz[0] > 0 && pix_sz[1] > 0); - ASSERT(width && height); - (void)ntiles_x, (void)ntiles_y; - - res = ssp_rng_create(htrdr->allocator, &ssp_rng_mt19937_64, &rng_proc); - if(res != RES_OK) { - htrdr_log_err(htrdr, "could not create the RNG used to sample a process " - "to steal -- %s.\n", res_to_cstr((res_T)res)); - goto error; - } - - proc_ntiles = proc_work_get_ntiles(work); - nthreads = MMIN(htrdr->nthreads, proc_ntiles); - - /* The process is not considered as a working process for himself */ - htrdr->mpi_working_procs[htrdr->mpi_rank] = 0; - --htrdr->mpi_nworking_procs; - - pixfmt = spectral_type_to_pixfmt(htrdr->spectral_type); - - omp_set_num_threads((int)nthreads); - #pragma omp parallel - for(;;) { - const int ithread = omp_get_thread_num(); - struct ssp_rng_proxy* rng_proxy = NULL; - struct ssp_rng* rng; - struct tile* tile; - uint32_t mcode = TILE_MCODE_NULL; - size_t tile_org[2]; - size_t tile_sz[2]; - size_t n; - res_T res_local = RES_OK; - int32_t pcent; - - /* Get a tile to draw */ - #pragma omp critical - { - mcode = proc_work_get_tile(work); - if(mcode == TILE_MCODE_NULL) { /* No more work on this process */ - /* Try to steal works to concurrent processes */ - proc_work_reset(work); - nthieves = mpi_steal_work(htrdr, rng_proc, work); - if(nthieves != 0) { - mcode = proc_work_get_tile(work); - } - } - } - if(mcode == TILE_MCODE_NULL) break; /* No more work */ - - /* Decode the morton code to retrieve the tile index */ - tile_org[0] = morton2D_decode((uint32_t)(mcode>>0)); - tile_org[1] = morton2D_decode((uint32_t)(mcode>>1)); - ASSERT(tile_org[0] < ntiles_x && tile_org[1] < ntiles_y); - - /* Create the tile */ - tile = tile_create(htrdr->allocator, pixfmt); - if(!tile) { - ATOMIC_SET(&res, RES_MEM_ERR); - htrdr_log_err(htrdr, - "could not allocate the memory space of the tile (%lu, %lu) -- %s.\n", - (unsigned long)tile_org[0], (unsigned long)tile_org[1], - res_to_cstr((res_T)ATOMIC_GET(&res))); - break; - } - - /* Register the tile */ - #pragma omp critical - list_add_tail(tiles, &tile->node); - - tile->data.x = (uint16_t)tile_org[0]; - tile->data.y = (uint16_t)tile_org[1]; - - /* Define the tile origin in pixel space */ - tile_org[0] *= TILE_SIZE; - tile_org[1] *= TILE_SIZE; - - /* Compute the size of the tile clamped by the borders of the buffer */ - tile_sz[0] = MMIN(TILE_SIZE, width - tile_org[0]); - tile_sz[1] = MMIN(TILE_SIZE, height - tile_org[1]); - - /* Create a proxy RNG for the current tile. This proxy is used for the - * current thread only and thus it has to manage only one RNG. This proxy - * is initialised in order to ensure that an unique and predictable set of - * random numbers is used for the current tile. */ - SSP(rng_proxy_create2 - (&htrdr->lifo_allocators[ithread], - &ssp_rng_threefry, - RNG_SEQUENCE_SIZE * (size_t)mcode, /* Offset */ - RNG_SEQUENCE_SIZE, /* Size */ - RNG_SEQUENCE_SIZE * (size_t)ntiles_adjusted, /* Pitch */ - 1, &rng_proxy)); - SSP(rng_proxy_create_rng(rng_proxy, 0, &rng)); - - /* Launch the tile rendering */ - res_local = draw_tile(htrdr, (size_t)ithread, mcode, tile_org, tile_sz, - pix_sz, sensor, spp, rng, tile); - - SSP(rng_proxy_ref_put(rng_proxy)); - SSP(rng_ref_put(rng)); - - if(res_local != RES_OK) { - ATOMIC_SET(&res, res_local); - break; - } - - /* Update the progress status */ - n = (size_t)ATOMIC_INCR(&nsolved_tiles); - pcent = (int32_t)((double)n * 100.0 / (double)proc_ntiles + 0.5/*round*/); - - #pragma omp critical - if(pcent > htrdr->mpi_progress_render[0]) { - htrdr->mpi_progress_render[0] = pcent; - if(htrdr->mpi_rank == 0) { - update_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING); - } else { /* Send the progress percentage to the master process */ - send_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING, pcent); - } - } - } - - if(ATOMIC_GET(&res) != RES_OK) goto error; - - /* Asynchronously wait for processes completion. Use an asynchronous barrier to - * avoid a dead lock with the `mpi_probe_thieves' thread that requires also - * the `mpi_mutex'. */ - { - MPI_Request req; - - mutex_lock(htrdr->mpi_mutex); - MPI(Ibarrier(MPI_COMM_WORLD, &req)); - mutex_unlock(htrdr->mpi_mutex); - - mpi_wait_for_request(htrdr, &req); - } - -exit: - if(rng_proc) SSP(rng_ref_put(rng_proc)); - return (res_T)res; -error: - goto exit; -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_draw_map - (struct htrdr* htrdr, - const struct htrdr_sensor* sensor, - const size_t width, - const size_t height, - const size_t spp, - struct htrdr_buffer* buf) -{ - char strbuf[128]; - struct time t0, t1; - struct list_node tiles; - size_t ntiles_x, ntiles_y, ntiles, ntiles_adjusted; - size_t itile; - struct proc_work work; - struct htrdr_buffer_layout layout = HTRDR_BUFFER_LAYOUT_NULL; - size_t proc_ntiles_adjusted; - double pix_sz[2]; - ATOMIC probe_thieves = 1; - ATOMIC res = RES_OK; - ASSERT(htrdr && sensor && width && height); - ASSERT(htrdr->mpi_rank != 0 || buf); - - list_init(&tiles); - proc_work_init(htrdr->allocator, &work); - - if(htrdr->mpi_rank == 0) { - const size_t pixsz = htrdr_spectral_type_get_pixsz - (htrdr->spectral_type, sensor->type); - const size_t pixal = htrdr_spectral_type_get_pixal - (htrdr->spectral_type, sensor->type); - - htrdr_buffer_get_layout(buf, &layout); - ASSERT(layout.width || layout.height || layout.elmt_size); - ASSERT(layout.width == width && layout.height == height); - - if(layout.elmt_size != pixsz || layout.alignment < pixal) { - htrdr_log_err(htrdr, "%s: invalid buffer layout.\n", FUNC_NAME); - res = RES_BAD_ARG; - goto error; - } - } - - /* Compute the overall number of tiles */ - ntiles_x = (width + (TILE_SIZE-1)/*ceil*/)/TILE_SIZE; - ntiles_y = (height+ (TILE_SIZE-1)/*ceil*/)/TILE_SIZE; - ntiles = ntiles_x * ntiles_y; - - /* Compute the pixel size in the normalized image plane */ - pix_sz[0] = 1.0 / (double)width; - pix_sz[1] = 1.0 / (double)height; - - /* Adjust the #tiles for the morton-encoding procedure */ - ntiles_adjusted = round_up_pow2(MMAX(ntiles_x, ntiles_y)); - ntiles_adjusted *= ntiles_adjusted; - - /* Define the initial number of tiles of the current process */ - proc_ntiles_adjusted = ntiles_adjusted / (size_t)htrdr->mpi_nprocs; - if(htrdr->mpi_rank == 0) { /* Affect the remaining tiles to the master proc */ - proc_ntiles_adjusted += - ntiles_adjusted - proc_ntiles_adjusted*(size_t)htrdr->mpi_nprocs; - } - - /* Define the initial list of tiles of the process */ - FOR_EACH(itile, 0, proc_ntiles_adjusted) { - uint32_t mcode; - uint16_t tile_org[2]; - - mcode = (uint32_t)itile*(uint32_t)htrdr->mpi_nprocs - + (uint32_t)htrdr->mpi_rank; - - tile_org[0] = morton2D_decode(mcode>>0); - if(tile_org[0] >= ntiles_x) continue; - tile_org[1] = morton2D_decode(mcode>>1); - if(tile_org[1] >= ntiles_y) continue; - proc_work_add_tile(&work, mcode); - } - - if(htrdr->mpi_rank == 0) { - fetch_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING); - print_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING); - } - - time_current(&t0); - - omp_set_nested(1); /* Enable nested threads for draw_image */ - #pragma omp parallel sections num_threads(2) - { - #pragma omp section - mpi_probe_thieves(htrdr, &work, &probe_thieves); - - #pragma omp section - { - draw_image(htrdr, sensor, width, height, spp, ntiles_x, ntiles_y, - ntiles_adjusted, pix_sz, &work, &tiles); - /* The processes have no more work to do. Stop probing for thieves */ - ATOMIC_SET(&probe_thieves, 0); - } - } - - if(htrdr->mpi_rank == 0) { - update_mpi_progress(htrdr, HTRDR_MPI_PROGRESS_RENDERING); - fprintf(stderr, "\n"); /* Add a new line after the progress statuses */ - } - - time_sub(&t0, time_current(&t1), &t0); - time_dump(&t0, TIME_ALL, NULL, strbuf, sizeof(strbuf)); - htrdr_log(htrdr, "Rendering time: %s\n", strbuf); - - /* Gather accum buffers from the group of processes */ - time_current(&t0); - res = mpi_gather_tiles(htrdr, sensor, buf, ntiles, &tiles); - if(res != RES_OK) goto error; - time_sub(&t0, time_current(&t1), &t0); - time_dump(&t0, TIME_ALL, NULL, strbuf, sizeof(strbuf)); - htrdr_log(htrdr, "Image gathering time: %s\n", strbuf); - -exit: - { /* Free allocated tiles */ - struct list_node* node; - struct list_node* tmp; - LIST_FOR_EACH_SAFE(node, tmp, &tiles) { - struct tile* tile = CONTAINER_OF(node, struct tile, node); - list_del(node); - tile_ref_put(tile); - } - } - proc_work_release(&work); - return (res_T)res; -error: - goto exit; -} - diff --git a/src/htrdr_grid.c b/src/htrdr_grid.c @@ -1,366 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#define _POSIX_C_SOURCE 200809L /* mmap support */ -#define _DEFAULT_SOURCE 1 /* MAP_POPULATE support */ -#define _BSD_SOURCE 1 /* MAP_POPULATE for glibc < 2.19 */ - -#include "htrdr.h" -#include "htrdr_c.h" -#include "htrdr_grid.h" - -#include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> - -#include <errno.h> -#include <sys/mman.h> /* mmap/munmap */ -#include <fcntl.h> -#include <unistd.h> /* sysconf */ - -const int32_t GRID_VERSION = 0; -const int32_t GRID_VERSION_NONE = -1; - -struct htrdr_grid { - FILE* fp; - char* data; /* Mapped data */ - size_t definition[3]; /* Submitted definition */ - size_t def_adjusted; /* Adjusted definition along the 3 dimensions */ - size_t cell_sz; /* Size in bytes of a grid cell */ - size_t pagesize; /* Page size in bytes */ - size_t data_sz; /* Size in bytes of the overall grid data + padding */ - - ref_T ref; - struct htrdr* htrdr; -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static void -grid_release(ref_T* ref) -{ - struct htrdr_grid* grid; - ASSERT(ref); - grid = CONTAINER_OF(ref, struct htrdr_grid, ref); - if(grid->fp) { - rewind(grid->fp); - CHK(fwrite(&GRID_VERSION, sizeof(int), 1, grid->fp) == 1); - fclose(grid->fp); - } - if(grid->data) { - if(munmap(grid->data, grid->data_sz)) { - htrdr_log_err(grid->htrdr, "error unmapping the grid data -- %s.\n", - strerror(errno)); - ASSERT(0); - } - } - MEM_RM(grid->htrdr->allocator, grid); -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_grid_create - (struct htrdr* htrdr, - const size_t definition[3], - const size_t sizeof_cell, /* Size of an cell in Bytes */ - const char* filename, - const int force_overwrite, - struct htrdr_grid** out_grid) -{ - const char byte = 0; - struct htrdr_grid* grid = NULL; - size_t mcode_max; - long grid_offset; - int n; - res_T res = RES_OK; - ASSERT(htrdr && out_grid && filename && definition); - - if(!definition[0] || !definition[1] || !definition[2]) { - htrdr_log_err(htrdr, "%s: invalid definition [%lu, %lu, %lu].\n", FUNC_NAME, - (unsigned long)definition[0], - (unsigned long)definition[1], - (unsigned long)definition[2]); - res = RES_BAD_ARG; - goto error; - } - - if(!sizeof_cell) { - htrdr_log_err(htrdr, "%s: invalid cell size `%lu'.\n", FUNC_NAME, - (unsigned long)sizeof_cell); - res = RES_BAD_ARG; - goto error; - } - - grid = MEM_CALLOC(htrdr->allocator, 1, sizeof(*grid)); - if(!grid) { - res = RES_MEM_ERR; - goto error; - } - ref_init(&grid->ref); - grid->definition[0] = definition[0]; - grid->definition[1] = definition[1]; - grid->definition[2] = definition[2]; - grid->cell_sz = sizeof_cell; - grid->pagesize = (size_t)sysconf(_SC_PAGESIZE); - grid->htrdr = htrdr; - - res = open_output_stream(htrdr, filename, 1, force_overwrite, &grid->fp); - if(res != RES_OK) goto error; - - #define WRITE(Var, N, Name) { \ - if(fwrite((Var), sizeof(*(Var)), (N), grid->fp) != (N)) { \ - htrdr_log_err(htrdr, "%s:%s: could not write `%s' -- %s.\n", \ - FUNC_NAME, filename, (Name), strerror(errno)); \ - res = RES_IO_ERR; \ - goto error; \ - } \ - } (void)0 - WRITE(&GRID_VERSION_NONE, 1, "version"); - WRITE(&grid->pagesize, 1, "pagesize"); - WRITE(&grid->cell_sz, 1, "cell_sz"); - WRITE(grid->definition, 3, "definition"); - WRITE(grid->definition, 3, "definition"); - - /* Align the grid data on pagesize */ - n = fseek - (grid->fp, ALIGN_SIZE(ftell(grid->fp),(off_t)grid->pagesize), SEEK_SET); - if(n < 0) { - htrdr_log_err(htrdr, - "%s:%s: could not align the grid data on page size -- %s.\n", - FUNC_NAME, filename, strerror(errno)); - res = RES_IO_ERR; - goto error; - } - - /* Adjust the grid definition in order to sort its data wrt the morton code - * of its voxel */ - grid->def_adjusted = MMAX(MMAX(definition[0], definition[1]), definition[2]); - grid->def_adjusted = round_up_pow2(grid->def_adjusted); - mcode_max = grid->def_adjusted*grid->def_adjusted*grid->def_adjusted; - - /* Define the grid size */ - grid->data_sz = mcode_max * sizeof_cell; - grid->data_sz = ALIGN_SIZE(grid->data_sz, grid->pagesize); - - /* Save the position of the grid data into the file */ - grid_offset = ftell(grid->fp); - - /* Reserve the space for the grid data */ - n = fseek(grid->fp, (long)grid->data_sz, SEEK_CUR); - if(n < 0) { - htrdr_log_err(htrdr, - "%s:%s: could reserve the space to store the grid -- %s.\n", FUNC_NAME, - filename, strerror(errno)); - res = RES_IO_ERR; - goto error; - } - - /* Write one char at the end of the file to position the EOF indicator */ - CHK(fseek(grid->fp, -1, SEEK_CUR) != -1); - WRITE(&byte, 1, "Dummy Byte"); - #undef WRITE - - /* Avoid to be positionned on the EOF */ - rewind(grid->fp); - - /* Map the grid data */ - grid->data = mmap(NULL, grid->data_sz, PROT_READ|PROT_WRITE, - MAP_SHARED|MAP_POPULATE, fileno(grid->fp), grid_offset); - - if(grid->data == MAP_FAILED) { - htrdr_log_err(htrdr, "%s:%s: could not map the grid data -- %s.\n", - FUNC_NAME, filename, strerror(errno)); - grid->data = NULL; - res = RES_IO_ERR; - goto error; - } - -exit: - *out_grid = grid; - return res; -error: - if(grid) { - htrdr_grid_ref_put(grid); - grid = NULL; - } - goto exit; -} - -res_T -htrdr_grid_open - (struct htrdr* htrdr, - const char* filename, - struct htrdr_grid** out_grid) -{ - struct htrdr_grid* grid = NULL; - size_t grid_offset; - size_t pagesize; - size_t mcode_max; - int32_t version; - int fd = -1; - res_T res = RES_OK; - ASSERT(htrdr && filename && out_grid); - - grid = MEM_CALLOC(htrdr->allocator, 1, sizeof(*grid)); - if(!grid) { - res = RES_MEM_ERR; - goto error; - } - ref_init(&grid->ref); - grid->pagesize = (size_t)sysconf(_SC_PAGESIZE); - grid->htrdr = htrdr; - - fd = open(filename, O_RDWR, 0); - if(fd < 0) { - htrdr_log_err(htrdr, "%s: could not open `%s' -- %s.\n", FUNC_NAME, - filename, strerror(errno)); - res = RES_IO_ERR; - goto error; - } - CHK(grid->fp = fdopen(fd, "w+")); - - #define READ(Var, N, Name) { \ - if(fread((Var), sizeof(*(Var)), (N), grid->fp) != (N)) { \ - htrdr_log_err(htrdr, "%s:%s: could not read `%s'.\n", \ - FUNC_NAME, filename, Name); \ - res = RES_IO_ERR; \ - goto error; \ - } \ - } (void)0 - READ(&version, 1, "version"); - if(version != GRID_VERSION) { - htrdr_log_err(htrdr, "%s:%s: incompatible grid version. Loaded version is " - "'%i' while the current version is '%i'.\n", - FUNC_NAME, filename, version, GRID_VERSION); - res = RES_BAD_ARG; - goto error; - } - - READ(&pagesize, 1, "pagesize"); - if(pagesize != grid->pagesize) { - htrdr_log_err(htrdr, "%s:%s: invalid pagesize `%lu'.\n", FUNC_NAME, - filename, (unsigned long)pagesize); - res = RES_BAD_ARG; - goto error; - } - - READ(&grid->cell_sz, 1, "sizeof_cell"); - if(grid->cell_sz == 0) { - htrdr_log_err(htrdr, "%s:%s: invalid cell size `%lu'.\n", FUNC_NAME, - filename, (unsigned long)grid->cell_sz); - res = RES_BAD_ARG; - goto error; - } - - READ(grid->definition, 3, "definition"); - if(!grid->definition[0] || !grid->definition[1] || !grid->definition[2]) { - htrdr_log_err(htrdr, "%s:%s: invalid definition [%lu, %lu, %lu].\n", - FUNC_NAME, filename, - (unsigned long)grid->definition[0], - (unsigned long)grid->definition[1], - (unsigned long)grid->definition[2]); - res = RES_BAD_ARG; - goto error; - } - #undef READ - - grid_offset = ALIGN_SIZE((size_t)ftell(grid->fp), grid->pagesize); - grid->def_adjusted = MMAX(grid->definition[0], grid->definition[1]); - grid->def_adjusted = MMAX(grid->definition[2], grid->def_adjusted); - grid->def_adjusted = round_up_pow2(grid->def_adjusted); - mcode_max = grid->def_adjusted*grid->def_adjusted*grid->def_adjusted; - - /* Define the grid size */ - grid->data_sz = mcode_max * grid->cell_sz; - grid->data_sz = ALIGN_SIZE(grid->data_sz, grid->pagesize); - - grid->data = mmap(NULL, grid->data_sz, PROT_READ|PROT_WRITE, - MAP_SHARED|MAP_POPULATE, fileno(grid->fp), (off_t)grid_offset); - - if(grid->data == MAP_FAILED) { - htrdr_log_err(htrdr, "%s:%s: could not map the grid data -- %s.\n", - FUNC_NAME, filename, strerror(errno)); - grid->data = NULL; - res = RES_IO_ERR; - goto error; - } - - rewind(grid->fp); - CHK(fwrite(&GRID_VERSION_NONE, sizeof(int), 1, grid->fp) == 1); - -exit: - *out_grid = grid; - return res; -error: - if(grid) { - htrdr_grid_ref_put(grid); - grid = NULL; - } - goto exit; -} - -void -htrdr_grid_ref_get(struct htrdr_grid* grid) -{ - ASSERT(grid); - ref_get(&grid->ref); -} - -void -htrdr_grid_ref_put(struct htrdr_grid* grid) -{ - ASSERT(grid); - ref_put(&grid->ref, grid_release); -} - -void* -htrdr_grid_at(struct htrdr_grid* grid, const size_t xyz[3]) -{ - uint32_t coords[3]; - uint64_t mcode; - ASSERT(grid && xyz); - ASSERT(xyz[0] < grid->definition[0]); - ASSERT(xyz[1] < grid->definition[1]); - ASSERT(xyz[2] < grid->definition[2]); - coords[0] = (uint32_t)xyz[0]; - coords[1] = (uint32_t)xyz[1]; - coords[2] = (uint32_t)xyz[2]; - mcode = morton_xyz_encode_u21(coords); - return htrdr_grid_at_mcode(grid, mcode); -} - -void* -htrdr_grid_at_mcode(struct htrdr_grid* grid, const uint64_t mcode) -{ - ASSERT(grid); - ASSERT(mcode < grid->def_adjusted*grid->def_adjusted*grid->def_adjusted); - ASSERT(morton3D_decode_u21(mcode>>2) < grid->definition[0]); - ASSERT(morton3D_decode_u21(mcode>>1) < grid->definition[1]); - ASSERT(morton3D_decode_u21(mcode>>0) < grid->definition[2]); - return grid->data + mcode*grid->cell_sz; -} - -void -htrdr_grid_get_definition(struct htrdr_grid* grid, size_t definition[3]) -{ - ASSERT(grid && definition); - definition[0] = grid->definition[0]; - definition[1] = grid->definition[1]; - definition[2] = grid->definition[2]; -} - diff --git a/src/htrdr_grid.h b/src/htrdr_grid.h @@ -1,71 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_GRID_H -#define HTRDR_GRID_H - -#include <rsys/rsys.h> - -/* Forwared declarations */ -struct htrdr; -struct htrdr_grid; - -/******************************************************************************* - * Out of core regular grid - ******************************************************************************/ -extern LOCAL_SYM res_T -htrdr_grid_create - (struct htrdr* htrdr, - const size_t definition[3], /* #voxels in X, Y and Z */ - const size_t sizeof_cell, /* Size of an cell in Bytes */ - const char* filename, /* Filename where the grid data are stored */ - const int force_overwrite, /* Force the overwrite of the grid data */ - struct htrdr_grid** grid); - -extern LOCAL_SYM res_T -htrdr_grid_open - (struct htrdr* htrdr, - const char* filename, - struct htrdr_grid** grid); - -extern LOCAL_SYM void -htrdr_grid_ref_get - (struct htrdr_grid* grid); - -extern LOCAL_SYM void -htrdr_grid_ref_put - (struct htrdr_grid* grid); - -/* Fetch the grid data from its 3D index */ -extern LOCAL_SYM void* -htrdr_grid_at - (struct htrdr_grid* grid, - const size_t xyz[3]); - -/* Retrieve the voxel data from its morton code. The morton code is computed - * from the 3D indices following the morton_xyz_encode_u21 convention. */ -extern LOCAL_SYM void* -htrdr_grid_at_mcode - (struct htrdr_grid* grid, - const uint64_t mcode); - -extern LOCAL_SYM void -htrdr_grid_get_definition - (struct htrdr_grid* grid, - size_t definition[3]); - -#endif /* HTRDR_GRID_H */ - diff --git a/src/htrdr_ground.c b/src/htrdr_ground.c @@ -1,753 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#define _POSIX_C_SOURCE 200112L /* strtok_r support */ - -#include "htrdr.h" -#include "htrdr_interface.h" -#include "htrdr_ground.h" -#include "htrdr_materials.h" -#include "htrdr_slab.h" - -#include <aw.h> -#include <rsys/clock_time.h> -#include <rsys/cstr.h> -#include <rsys/dynamic_array_double.h> -#include <rsys/dynamic_array_size_t.h> -#include <rsys/double2.h> -#include <rsys/double3.h> -#include <rsys/float2.h> -#include <rsys/float3.h> -#include <rsys/hash_table.h> - -#include <star/s3d.h> - -#include <string.h> /* strtok_r */ - -/* Define the hash table that maps an Obj vertex id to its position into the - * vertex buffer */ -#define HTABLE_NAME vertex -#define HTABLE_KEY size_t /* Obj vertex id */ -#define HTABLE_DATA size_t -#include <rsys/hash_table.h> - -/* Define the hash table that maps the Star-3D shape id to its interface */ -#define HTABLE_NAME interface -#define HTABLE_KEY unsigned /* Star-3D shape id */ -#define HTABLE_DATA struct htrdr_interface -#include <rsys/hash_table.h> - -struct mesh { - const struct darray_double* positions; - const struct darray_size_t* indices; -}; -static const struct mesh MESH_NULL; - -struct ray_context { - float range[2]; - struct s3d_hit hit_prev; - int id[2]; -}; -#define RAY_CONTEXT_NULL__ {{0,INF}, S3D_HIT_NULL__, {0,0}} -static const struct ray_context RAY_CONTEXT_NULL = RAY_CONTEXT_NULL__; - -struct trace_ground_context { - struct s3d_scene_view* view; - struct ray_context context; - struct s3d_hit* hit; -}; -static const struct trace_ground_context TRACE_GROUND_CONTEXT_NULL = { - NULL, RAY_CONTEXT_NULL__, NULL -}; - -struct htrdr_ground { - struct s3d_scene_view* view; - float lower[3]; /* Ground lower bound */ - float upper[3]; /* Ground upper bound */ - int repeat; /* Make the ground infinite in X and Y */ - - struct htable_interface interfaces; /* Map a Star3D shape to its interface */ - - struct htrdr* htrdr; - ref_T ref; -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -/* Check that `hit' roughly lies on an edge. For triangular primitives, a - * simple but approximative way is to test that its position have at least one - * barycentric coordinate roughly equal to 0 or 1. */ -static FINLINE int -hit_on_edge(const struct s3d_hit* hit) -{ - const float on_edge_eps = 1.e-4f; - float w; - ASSERT(hit && !S3D_HIT_NONE(hit)); - w = 1.f - hit->uv[0] - hit->uv[1]; - return eq_epsf(hit->uv[0], 0.f, on_edge_eps) - || eq_epsf(hit->uv[0], 1.f, on_edge_eps) - || eq_epsf(hit->uv[1], 0.f, on_edge_eps) - || eq_epsf(hit->uv[1], 1.f, on_edge_eps) - || eq_epsf(w, 0.f, on_edge_eps) - || eq_epsf(w, 1.f, on_edge_eps); -} - -static int -ground_filter - (const struct s3d_hit* hit, - const float ray_org[3], - const float ray_dir[3], - void* ray_data, - void* filter_data) -{ - const struct ray_context* ray_ctx = ray_data; - (void)ray_org, (void)ray_dir, (void)filter_data; - - if(!ray_ctx) return 0; - - if(S3D_PRIMITIVE_EQ(&hit->prim, &ray_ctx->hit_prev.prim)) return 1; - - if(!S3D_HIT_NONE(&ray_ctx->hit_prev) && eq_epsf(hit->distance, 0, 1.e-1f)) { - /* If the targeted point is near of the origin, check that it lies on an - * edge/vertex shared by the 2 primitives. */ - return hit_on_edge(&ray_ctx->hit_prev) && hit_on_edge(hit); - } - - return hit->distance <= ray_ctx->range[0] - || hit->distance >= ray_ctx->range[1]; -} - -static INLINE res_T -trace_ground - (const double org[3], - const double dir[3], - const double range[2], - void* context, - int* hit) -{ - struct trace_ground_context* ctx = context; - float ray_org[3]; - float ray_dir[3]; - float ray_range[2]; - res_T res = RES_OK; - ASSERT(org && dir && range && context && hit); - - f3_set_d3(ray_org, org); - f3_set_d3(ray_dir, dir); - f2_set_d2(ray_range, range); - - res = s3d_scene_view_trace_ray - (ctx->view, ray_org, ray_dir, ray_range, &ctx->context, ctx->hit); - if(res != RES_OK) return res; - - *hit = !S3D_HIT_NONE(ctx->hit); - return RES_OK; -} - -static res_T -parse_shape_interface - (struct htrdr* htrdr, - const char* name, - struct htrdr_interface* interf) -{ - struct str str; - char* mtl_name0 = NULL; - char* mtl_name1 = NULL; - char* mtl_name2 = NULL; - char* mtl_name_front = NULL; - char* mtl_name_thin = NULL; - char* mtl_name_back = NULL; - char* tk_ctx = NULL; - int has_front = 0; - int has_thin = 0; - int has_back = 0; - res_T res = RES_OK; - ASSERT(htrdr && name && interf); - - str_init(htrdr->allocator, &str); - - /* Locally copy the string to parse */ - res = str_set(&str, name); - if(res != RES_OK) { - htrdr_log_err(htrdr, - "Could not locally copy the shape material string `%s' -- %s.\n", - name, res_to_cstr(res)); - goto error; - } - - /* Reset the interface */ - memset(interf, 0, sizeof(*interf)); - - /* Parse the name of the front/back/thin materials */ - mtl_name0 = strtok_r(str_get(&str), ":", &tk_ctx); - mtl_name1 = strtok_r(NULL, ":", &tk_ctx); - mtl_name2 = strtok_r(NULL, ":", &tk_ctx); - ASSERT(mtl_name0); /* This can't be NULL */ - mtl_name_front = mtl_name0; - if(mtl_name2) { - mtl_name_thin = mtl_name1; - mtl_name_back = mtl_name2; - } else { - mtl_name_thin = NULL; - mtl_name_back = mtl_name1; - } - - if(!mtl_name_back) { - htrdr_log_err(htrdr, - "The back material name is missing `%s'.\n", name); - res = RES_BAD_ARG; - goto error; - } - - /* Fetch the interface material if any */ - if(mtl_name_thin) { - has_thin = htrdr_materials_find_mtl - (htrdr->mats, mtl_name_thin, &interf->mtl_thin); - if(!has_thin) { - htrdr_log_err(htrdr, - "Invalid interface `%s'. The interface material `%s' is unknown.\n", - name, mtl_name_thin); - res = RES_BAD_ARG; - goto error; - } - } - - /* Fetch the front material */ - has_front = htrdr_materials_find_mtl - (htrdr->mats, mtl_name_front, &interf->mtl_front); - if(!has_front) { - htrdr_log_err(htrdr, - "Invalid interface `%s'. The front material `%s' is unknown.\n", - name, mtl_name_front); - res = RES_BAD_ARG; - goto error; - } - - /* Fetch the back material */ - has_back = htrdr_materials_find_mtl - (htrdr->mats, mtl_name_back, &interf->mtl_back); - if(!has_back) { - htrdr_log_err(htrdr, - "Invalid interface `%s'. The back material `%s' is unknown.\n", - name, mtl_name_back); - res = RES_BAD_ARG; - goto error; - } - -exit: - str_release(&str); - return res; -error: - *interf = HTRDR_INTERFACE_NULL; - goto exit; -} - -static res_T -setup_mesh - (struct htrdr* htrdr, - const char* filename, - struct aw_obj* obj, - struct aw_obj_named_group* mtl, - struct darray_double* positions, - struct darray_size_t* indices, - struct htable_vertex* vertices) /* Scratch data structure */ -{ - size_t iface; - res_T res = RES_OK; - ASSERT(htrdr && filename && obj && mtl && positions && indices && vertices); - - darray_double_clear(positions); - darray_size_t_clear(indices); - htable_vertex_clear(vertices); - - FOR_EACH(iface, mtl->face_id, mtl->face_id+mtl->faces_count) { - struct aw_obj_face face; - size_t ivertex; - - AW(obj_get_face(obj, iface, &face)); - if(face.vertices_count != 3) { - htrdr_log_err(htrdr, - "The obj `%s' has non-triangulated polygons " - "while currently only triangular meshes are supported.\n", - filename); - res = RES_BAD_ARG; - goto error; - } - - FOR_EACH(ivertex, 0, face.vertices_count) { - struct aw_obj_vertex vertex; - size_t id; - size_t* pid; - - AW(obj_get_vertex(obj, face.vertex_id + ivertex, &vertex)); - pid = htable_vertex_find(vertices, &vertex.position_id); - if(pid) { - id = *pid; - } else { - struct aw_obj_vertex_data vdata; - - id = darray_double_size_get(positions) / 3; - - res = darray_double_resize(positions, id*3 + 3); - if(res != RES_OK) { - htrdr_log_err(htrdr, - "Could not locally copy the vertex position -- %s.\n", - res_to_cstr(res)); - goto error; - } - - AW(obj_get_vertex_data(obj, &vertex, &vdata)); - darray_double_data_get(positions)[id*3+0] = vdata.position[0]; - darray_double_data_get(positions)[id*3+1] = vdata.position[1]; - darray_double_data_get(positions)[id*3+2] = vdata.position[2]; - - res = htable_vertex_set(vertices, &vertex.position_id, &id); - if(res != RES_OK) { - htrdr_log_err(htrdr, - "Could not register the vertex position -- %s.\n", - res_to_cstr(res)); - goto error; - } - } - - res = darray_size_t_push_back(indices, &id); - if(res != RES_OK) { - htrdr_log_err(htrdr, - "Could not locally copy the face index -- %s\n", - res_to_cstr(res)); - goto error; - } - } - } -exit: - return res; -error: - darray_double_clear(positions); - darray_size_t_clear(indices); - htable_vertex_clear(vertices); - goto exit; -} - -static void -get_position(const unsigned ivert, float position[3], void* ctx) -{ - const struct mesh* mesh = ctx; - const double* pos = NULL; - ASSERT(mesh); - ASSERT(ivert < darray_double_size_get(mesh->positions) / 3); - pos = darray_double_cdata_get(mesh->positions) + ivert*3; - position[0] = (float)pos[0]; - position[1] = (float)pos[1]; - position[2] = (float)pos[2]; -} - -static void -get_indices(const unsigned itri, unsigned indices[3], void* ctx) -{ - const struct mesh* mesh = ctx; - const size_t* ids = NULL; - ASSERT(mesh); - ASSERT(itri < darray_size_t_size_get(mesh->indices) / 3); - ids = darray_size_t_cdata_get(mesh->indices) + itri*3; - indices[0] = (unsigned)ids[0]; - indices[1] = (unsigned)ids[1]; - indices[2] = (unsigned)ids[2]; -} - -static res_T -create_s3d_shape - (struct htrdr* htrdr, - const struct darray_double* positions, - const struct darray_size_t* indices, - struct s3d_shape** out_shape) -{ - struct s3d_shape* shape = NULL; - struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL; - struct mesh mesh = MESH_NULL; - res_T res = RES_OK; - ASSERT(htrdr && positions && indices && out_shape); - ASSERT(darray_double_size_get(positions) != 0); - ASSERT(darray_size_t_size_get(indices) != 0); - ASSERT(darray_double_size_get(positions)%3 == 0); - ASSERT(darray_size_t_size_get(indices)%3 == 0); - - mesh.positions = positions; - mesh.indices = indices; - - res = s3d_shape_create_mesh(htrdr->s3d, &shape); - if(res != RES_OK) { - htrdr_log_err(htrdr, "Error creating a Star-3D shape -- %s.\n", - res_to_cstr(res)); - goto error; - } - - vdata.usage = S3D_POSITION; - vdata.type = S3D_FLOAT3; - vdata.get = get_position; - - res = s3d_mesh_setup_indexed_vertices - (shape, (unsigned int)(darray_size_t_size_get(indices)/3), get_indices, - (unsigned int)(darray_double_size_get(positions)/3), &vdata, 1, &mesh); - if(res != RES_OK){ - htrdr_log_err(htrdr, "Could not setup the Star-3D shape -- %s.\n", - res_to_cstr(res)); - goto error; - } - - res = s3d_mesh_set_hit_filter_function(shape, ground_filter, NULL); - if(res != RES_OK) { - htrdr_log_err(htrdr, - "Could not setup the Star-3D hit filter function of the ground geometry " - "-- %s.\n", res_to_cstr(res)); - goto error; - } - -exit: - *out_shape = shape; - return res; -error: - if(shape) { - S3D(shape_ref_put(shape)); - shape = NULL; - } - goto exit; -} - -static res_T -setup_ground(struct htrdr_ground* ground, const char* obj_filename) -{ - struct aw_obj_desc desc; - struct htable_vertex vertices; - struct darray_double positions; - struct darray_size_t indices; - struct aw_obj* obj = NULL; - struct s3d_shape* shape = NULL; - struct s3d_scene* scene = NULL; - size_t iusemtl; - - res_T res = RES_OK; - ASSERT(obj_filename); - - htable_vertex_init(ground->htrdr->allocator, &vertices); - darray_double_init(ground->htrdr->allocator, &positions); - darray_size_t_init(ground->htrdr->allocator, &indices); - - res = aw_obj_create(&ground->htrdr->logger, ground->htrdr->allocator, - ground->htrdr->verbose, &obj); - if(res != RES_OK) { - htrdr_log_err(ground->htrdr, "Could not create the obj loader -- %s.\n", - res_to_cstr(res)); - goto error; - } - - res = s3d_scene_create(ground->htrdr->s3d, &scene); - if(res != RES_OK) { - htrdr_log_err(ground->htrdr, "Could not create the Star-3D scene -- %s.\n", - res_to_cstr(res)); - goto error; - } - - /* Load the geometry data */ - res = aw_obj_load(obj, obj_filename); - if(res != RES_OK) goto error; - - /* Fetch the descriptor of the loaded geometry */ - AW(obj_get_desc(obj, &desc)); - - if(desc.usemtls_count == 0) { - htrdr_log_err(ground->htrdr, "The obj `%s' has no material.\n", obj_filename); - res = RES_BAD_ARG; - goto error; - } - - /* Setup the geometry */ - FOR_EACH(iusemtl, 0, desc.usemtls_count) { - struct aw_obj_named_group mtl; - struct htrdr_interface interf; - unsigned ishape; - - AW(obj_get_mtl(obj, iusemtl , &mtl)); - - res = parse_shape_interface(ground->htrdr, mtl.name, &interf); - if(res != RES_OK) goto error; - - res = setup_mesh - (ground->htrdr, obj_filename, obj, &mtl, &positions, &indices, &vertices); - if(res != RES_OK) goto error; - - res = create_s3d_shape(ground->htrdr, &positions, &indices, &shape); - if(res != RES_OK) goto error; - - S3D(shape_get_id(shape, &ishape)); - res = htable_interface_set(&ground->interfaces, &ishape, &interf); - if(res != RES_OK) { - htrdr_log_err(ground->htrdr, - "Could not map the Star-3D shape to its Star-Materials -- %s.\n", - res_to_cstr(res)); - goto error; - } - - res = s3d_scene_attach_shape(scene, shape); - if(res != RES_OK) { - htrdr_log_err(ground->htrdr, - "Could not attach a Star-3D shape to the Star-3D scene -- %s.\n", - res_to_cstr(res)); - goto error; - } - - S3D(shape_ref_put(shape)); - shape = NULL; - } - - res = s3d_scene_view_create(scene, S3D_GET_PRIMITIVE|S3D_TRACE, &ground->view); - if(res != RES_OK) { - htrdr_log_err(ground->htrdr, - "Could not create the Star-3D scene view -- %s.\n", - res_to_cstr(res)); - goto error; - } - - res = s3d_scene_view_get_aabb(ground->view, ground->lower, ground->upper); - if(res != RES_OK) { - htrdr_log_err(ground->htrdr, - "Could not get the bounding box of the geometry -- %s.\n", - res_to_cstr(res)); - goto error; - } - -exit: - if(obj) AW(obj_ref_put(obj)); - if(shape) S3D(shape_ref_put(shape)); - if(scene) S3D(scene_ref_put(scene)); - htable_vertex_release(&vertices); - darray_double_release(&positions); - darray_size_t_release(&indices); - return res; -error: - goto exit; -} - -static void -release_ground(ref_T* ref) -{ - struct htrdr_ground* ground; - ASSERT(ref); - ground = CONTAINER_OF(ref, struct htrdr_ground, ref); - if(ground->view) S3D(scene_view_ref_put(ground->view)); - htable_interface_release(&ground->interfaces); - MEM_RM(ground->htrdr->allocator, ground); -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_ground_create - (struct htrdr* htrdr, - const char* obj_filename, /* May be NULL */ - const int repeat_ground, /* Infinitely repeat the ground in X and Y */ - struct htrdr_ground** out_ground) -{ - char buf[128]; - struct htrdr_ground* ground = NULL; - struct time t0, t1; - res_T res = RES_OK; - ASSERT(htrdr && out_ground); - - ground = MEM_CALLOC(htrdr->allocator, 1, sizeof(*ground)); - if(!ground) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "%s: could not allocate the ground data structure -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - ref_init(&ground->ref); - ground->htrdr = htrdr; - ground->repeat = repeat_ground; - f3_splat(ground->lower, (float)INF); - f3_splat(ground->upper,-(float)INF); - htable_interface_init(ground->htrdr->allocator, &ground->interfaces); - - if(!obj_filename) goto exit; - - htrdr_log(ground->htrdr, "Loading ground geometry from `%s'.\n",obj_filename); - time_current(&t0); - res = setup_ground(ground, obj_filename); - if(res != RES_OK) goto error; - time_sub(&t0, time_current(&t1), &t0); - time_dump(&t0, TIME_ALL, NULL, buf, sizeof(buf)); - htrdr_log(ground->htrdr, "Setup ground in %s\n", buf); - -exit: - *out_ground = ground; - return res; -error: - if(ground) { - htrdr_ground_ref_put(ground); - ground = NULL; - } - goto exit; -} - -void -htrdr_ground_ref_get(struct htrdr_ground* ground) -{ - ASSERT(ground); - ref_get(&ground->ref); -} - -void -htrdr_ground_ref_put(struct htrdr_ground* ground) -{ - ASSERT(ground); - ref_put(&ground->ref, release_ground); -} - -void -htrdr_ground_get_interface - (struct htrdr_ground* ground, - const struct s3d_hit* hit, - struct htrdr_interface* out_interface) -{ - struct htrdr_interface* interf = NULL; - ASSERT(ground && hit && out_interface); - - interf = htable_interface_find(&ground->interfaces, &hit->prim.geom_id); - ASSERT(interf); - - *out_interface = *interf; -} - -res_T -htrdr_ground_trace_ray - (struct htrdr_ground* ground, - const double org[3], - const double dir[3], /* Must be normalized */ - const double range[2], - const struct s3d_hit* prev_hit, - struct s3d_hit* hit) -{ - res_T res = RES_OK; - ASSERT(ground && org && dir && range && hit); - - if(!ground->view) { /* No ground geometry */ - *hit = S3D_HIT_NULL; - goto exit; - } - - if(!ground->repeat) { - struct ray_context ray_ctx = RAY_CONTEXT_NULL; - float ray_org[3]; - float ray_dir[3]; - - f3_set_d3(ray_org, org); - f3_set_d3(ray_dir, dir); - f2_set_d2(ray_ctx.range, range); - ray_ctx.hit_prev = prev_hit ? *prev_hit : S3D_HIT_NULL; - - res = s3d_scene_view_trace_ray - (ground->view, ray_org, ray_dir, ray_ctx.range, &ray_ctx, hit); - if(res != RES_OK) { - htrdr_log_err(ground->htrdr, - "%s: could not trace the ray against the ground geometry -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - } else { - struct trace_ground_context slab_ctx = TRACE_GROUND_CONTEXT_NULL; - double low[3], upp[3]; - - *hit = S3D_HIT_NULL; - slab_ctx.view = ground->view; - slab_ctx.context.range[0] = (float)range[0]; - slab_ctx.context.range[1] = (float)range[1]; - slab_ctx.context.hit_prev = prev_hit ? *prev_hit : S3D_HIT_NULL; - slab_ctx.hit = hit; - - d3_set_f3(low, ground->lower); - d3_set_f3(upp, ground->upper); - - res = htrdr_slab_trace_ray(ground->htrdr, org, dir, range, low, upp, - trace_ground, 32, &slab_ctx); - if(res != RES_OK) goto error; - } - -exit: - return res; -error: - goto exit; -} - -res_T -htrdr_ground_find_closest_point - (struct htrdr_ground* ground, - const double pos[3], - const double radius, - struct s3d_hit* hit) -{ - float query_pos[3]; - float query_radius; - float ground_sz[3]; - res_T res = RES_OK; - ASSERT(ground && pos && hit); - - if(!ground->view) { /* No ground geometry */ - *hit = S3D_HIT_NULL; - goto exit; - } - - query_radius = (float)radius; - f3_set_d3(query_pos, pos); - - if(ground->repeat) { - float translation[2] = {0, 0}; - int64_t xy[2]; - ground_sz[0] = ground->upper[0] - ground->lower[0]; - ground_sz[1] = ground->upper[1] - ground->lower[1]; - ground_sz[2] = ground->upper[2] - ground->lower[2]; - - /* Define the 2D index of the current ground instance. (0,0) is the index - * of the non instantiated ground */ - xy[0] = (int64_t)floor((query_pos[0] - ground->lower[0]) / ground_sz[0]); - xy[1] = (int64_t)floor((query_pos[1] - ground->lower[1]) / ground_sz[1]); - - /* Define the translation along the X and Y axis from world space to local - * ground geometry space */ - translation[0] = -(float)xy[0] * ground_sz[0]; - translation[1] = -(float)xy[1] * ground_sz[1]; - - /* Transform the query pos in local ground geometry space */ - query_pos[0] += translation[0]; - query_pos[1] += translation[1]; - } - - /* Closest point query */ - res = s3d_scene_view_closest_point - (ground->view, query_pos, query_radius, NULL, hit); - if(res != RES_OK) { - htrdr_log_err(ground->htrdr, - "%s: could not query the closest point to the ground geometry -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - -exit: - return res; -error: - goto exit; -} diff --git a/src/htrdr_ground.h b/src/htrdr_ground.h @@ -1,78 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_GROUND_H -#define HTRDR_GROUND_H - -#include <rsys/rsys.h> - -/* Forward declarations */ -struct htrdr; -struct htrdr_ground; -struct htrdr_interface; -struct s3d_hit; -struct ssf_bsdf; - -extern LOCAL_SYM res_T -htrdr_ground_create - (struct htrdr* htrdr, - const char* obj_filename, /* May be NULL <=> No ground geometry */ - const int repeat_ground, /* Infinitely repeat the ground in X and Y */ - struct htrdr_ground** ground); - -extern LOCAL_SYM void -htrdr_ground_ref_get - (struct htrdr_ground* ground); - -extern LOCAL_SYM void -htrdr_ground_ref_put - (struct htrdr_ground* ground); - -extern LOCAL_SYM void -htrdr_ground_get_interface - (struct htrdr_ground* ground, - const struct s3d_hit* hit, - struct htrdr_interface* interface); - -extern LOCAL_SYM res_T -htrdr_ground_create_bsdf - (struct htrdr_ground* ground, - const size_t ithread, - const double wavelength, - const double pos[3], - const double dir[3], /* Incoming ray */ - const struct s3d_hit* hit, - struct htrdr_interface* interf, /* NULL <=> do not return the interface */ - struct ssf_bsdf** bsdf); - -extern LOCAL_SYM res_T -htrdr_ground_trace_ray - (struct htrdr_ground* ground, - const double ray_origin[3], - const double ray_direction[3], /* Must be normalized */ - const double ray_range[2], - const struct s3d_hit* prev_hit,/* Previous hit. Avoid self hit. May be NULL*/ - struct s3d_hit* hit); - -extern LOCAL_SYM res_T -htrdr_ground_find_closest_point - (struct htrdr_ground* ground, - const double position[3], - const double radius, - struct s3d_hit* hit); - -#endif /* HTRDR_GROUND_H */ - diff --git a/src/htrdr_interface.h b/src/htrdr_interface.h @@ -1,82 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_INTERFACE_H -#define HTRDR_INTERFACE_H - -#include "htrdr_materials.h" -#include <star/s3d.h> -#include <rsys/double3.h> - -/* Forward declaration of external data type */ -struct mrumtl; -struct s3d_hit; -struct ssf_bsdf; -struct ssp_rng; - -struct htrdr_interface { - struct htrdr_mtl mtl_front; - struct htrdr_mtl mtl_back; - struct htrdr_mtl mtl_thin; /* != NULL <=> thin material */ -}; -static const struct htrdr_interface HTRDR_INTERFACE_NULL; - -static INLINE const struct htrdr_mtl* -htrdr_interface_fetch_hit_mtl - (const struct htrdr_interface* interf, - const double dir[3], /* Incoming ray */ - struct s3d_hit* hit) -{ - const struct htrdr_mtl* mtl = NULL; - enum { FRONT, BACK }; - ASSERT(interf && dir && d3_is_normalized(dir) && hit && !S3D_HIT_NONE(hit)); - ASSERT(interf->mtl_front.mrumtl - || interf->mtl_back.mrumtl - || interf->mtl_thin.mrumtl); - - if(interf->mtl_thin.mrumtl) { - mtl = &interf->mtl_thin; - } else { - double N[3]; - int hit_side; - d3_normalize(N, d3_set_f3(N, hit->normal)); - hit_side = d3_dot(N, dir) < 0 ? FRONT : BACK; - - /* Retrieve the brdf of the material on the *other side* of the hit side */ - switch(hit_side) { - case BACK: mtl = &interf->mtl_front; break; - case FRONT: mtl = &interf->mtl_back; break; - default: FATAL("Unreachable code.\n"); break; - } - - /* Due to numerical issue the hit side might be wrong and thus the fetched - * material might be undefined (e.g. semi-transparent materials). Handle this - * issue by fetching the other material. */ - if(!mtl->mrumtl) { - switch(hit_side) { - case BACK: mtl = &interf->mtl_back; break; - case FRONT: mtl = &interf->mtl_front; break; - default: FATAL("Unreachable code.\n"); break; - } - } - ASSERT(mtl->mrumtl); - } - - return mtl; -} - -#endif /* HTRDR_INTERFACE_H */ - diff --git a/src/htrdr_main.c b/src/htrdr_main.c @@ -1,91 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "htrdr.h" -#include "htrdr_args.h" - -#include <mpi.h> -#include <rsys/mem_allocator.h> - -static const char* -thread_support_string(const int val) -{ - switch(val) { - case MPI_THREAD_SINGLE: return "MPI_THREAD_SINGLE"; - case MPI_THREAD_FUNNELED: return "MPI_THREAD_FUNNELED"; - case MPI_THREAD_SERIALIZED: return "MPI_THREAD_SERIALIZED"; - case MPI_THREAD_MULTIPLE: return "MPI_THREAD_MULTIPLE"; - default: FATAL("Unreachable code.\n"); break; - } -} - -/******************************************************************************* - * Program - ******************************************************************************/ -int -main(int argc, char** argv) -{ - struct htrdr htrdr; - struct htrdr_args args = HTRDR_ARGS_DEFAULT; - size_t memsz = 0; - int err = 0; - int is_htrdr_init = 0; - int thread_support = 0; - res_T res = RES_OK; - - err = MPI_Init_thread(&argc, &argv, MPI_THREAD_SERIALIZED, &thread_support); - if(err != MPI_SUCCESS) { - fprintf(stderr, "Error initializing MPI.\n"); - goto error; - } - - if(thread_support != MPI_THREAD_SERIALIZED) { - fprintf(stderr, "The provided MPI implementation does not support " - "serialized API calls from multiple threads. Provided thread support: " - "%s.\n", thread_support_string(thread_support)); - goto error; - } - - res = htrdr_args_init(&args, argc, argv); - if(res != RES_OK) goto error; - if(args.quit) goto exit; - - if(args.dump_vtk) { - int rank; - CHK(MPI_Comm_rank(MPI_COMM_WORLD, &rank) == MPI_SUCCESS); - if(rank != 0) goto exit; /* Nothing to do except for the master process */ - } - - res = htrdr_init(NULL, &args, &htrdr); - if(res != RES_OK) goto error; - is_htrdr_init = 1; - - res = htrdr_run(&htrdr); - if(res != RES_OK) goto error; - -exit: - MPI_Finalize(); - if(is_htrdr_init) htrdr_release(&htrdr); - htrdr_args_release(&args); - if((memsz = mem_allocated_size()) != 0) { - fprintf(stderr, "Memory leaks: %lu Bytes\n", (unsigned long)memsz); - err = -1; - } - return err; -error: - err = -1; - goto exit; -} diff --git a/src/htrdr_materials.c b/src/htrdr_materials.c @@ -1,449 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#define _POSIX_C_SOURCE 200112L /* strtok_r and wordexp support */ - -#include "htrdr.h" -#include "htrdr_materials.h" - -#include <modradurb/mrumtl.h> -#include <star/ssf.h> -#include <star/ssp.h> - -#include <rsys/cstr.h> -#include <rsys/double3.h> -#include <rsys/hash_table.h> -#include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> -#include <rsys/str.h> -#include <rsys/text_reader.h> - -#include <string.h> -#include <wordexp.h> - -struct mtl { - struct mrumtl* mrumtl; - double temperature; /* In Kelvin */ -}; -static const struct mtl MTL_NULL = {NULL, -1}; - -/* Generate the hash table that maps a material name to its data */ -#define HTABLE_NAME name2mtl -#define HTABLE_DATA struct mtl -#define HTABLE_KEY struct str -#define HTABLE_KEY_FUNCTOR_INIT str_init -#define HTABLE_KEY_FUNCTOR_RELEASE str_release -#define HTABLE_KEY_FUNCTOR_COPY str_copy -#define HTABLE_KEY_FUNCTOR_COPY_AND_RELEASE str_copy_and_release -#define HTABLE_KEY_FUNCTOR_HASH str_hash -#define HTABLE_KEY_FUNCTOR_EQ str_eq -#include <rsys/hash_table.h> - -struct htrdr_materials { - struct htable_name2mtl name2mtl; - struct htrdr* htrdr; - ref_T ref; -}; - -/******************************************************************************* - * Local functions - ******************************************************************************/ -static res_T -parse_material - (struct htrdr_materials* mats, - struct txtrdr* txtrdr, - struct str* str) /* Scratch string */ -{ - wordexp_t wexp; - char* tk = NULL; - char* tk_ctx = NULL; - struct mtl mtl = MTL_NULL; - int err = 0; - int wexp_is_allocated = 0; - res_T res = RES_OK; - ASSERT(mats && txtrdr); - - tk = strtok_r(txtrdr_get_line(txtrdr), " \t", &tk_ctx); - ASSERT(tk); - - res = str_set(str, tk); - if(res != RES_OK) { - htrdr_log_err(mats->htrdr, - "%s:%lu: could not copy the material name `%s' -- %s.\n", - txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk, - res_to_cstr(res)); - goto error; - } - - tk = strtok_r(NULL, "", &tk_ctx); - if(!tk) { - htrdr_log_err(mats->htrdr, - "%s:%lu: missing the MruMtl file for the material `%s'.\n", - txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), - str_cget(str)); - res = RES_BAD_ARG; - goto error; - } - - err = wordexp(tk, &wexp, 0); - if(err) { - htrdr_log_err(mats->htrdr, - "%s:%lu: error in word expension of the mrumtl path.\n", - txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr)); - res = RES_BAD_ARG; - goto error; - } - wexp_is_allocated = 1; - - if(wexp.we_wordc < 1) { - htrdr_log_err(mats->htrdr, - "%s:%lu: missing the MruMtl file for the material `%s'.\n", - txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), - str_cget(str)); - res = RES_BAD_ARG; - goto error; - } - - /* Parse the mrumtl file if any */ - if(strcmp(wexp.we_wordv[0], "none")) { - res = mrumtl_create(&mats->htrdr->logger, mats->htrdr->allocator, - mats->htrdr->verbose, &mtl.mrumtl); - if(res != RES_OK) { - htrdr_log_err(mats->htrdr, - "%s:%lu: error creating the MruMtl loader for the material `%s'-- %s.\n", - txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), - str_cget(str), res_to_cstr(res)); - goto error; - } - - res = mrumtl_load(mtl.mrumtl, wexp.we_wordv[0]); - if(res != RES_OK) goto error; - } - - if(wexp.we_wordc < 2) { - if(mtl.mrumtl) { - htrdr_log_err(mats->htrdr, - "%s:%lu: missing temperature for the material `%s'.\n", - txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), - str_cget(str)); - res = RES_BAD_ARG; - goto error; - } - } else { - /* Parse the temperature */ - res = cstr_to_double(wexp.we_wordv[1], &mtl.temperature); - if(res != RES_OK) { - htrdr_log_err(mats->htrdr, - "%s:%lu: error parsing the temperature `%s' for the material `%s' " - "-- %s.\n", - txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), - wexp.we_wordv[1], str_cget(str), res_to_cstr(res)); - goto error; - } - } - - /* Register the material */ - res = htable_name2mtl_set(&mats->name2mtl, str, &mtl); - if(res != RES_OK) { - htrdr_log_err(mats->htrdr, - "%s:%lu: could not register the material `%s' -- %s.\n", - txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), - str_cget(str), res_to_cstr(res)); - goto error; - } - - if(wexp.we_wordc > 2) { - htrdr_log_warn(mats->htrdr, "%s:%lu: unexpected text `%s'.\n", - txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), - wexp.we_wordv[2]); - } - -exit: - if(wexp_is_allocated) wordfree(&wexp); - return res; -error: - if(mtl.mrumtl) MRUMTL(ref_put(mtl.mrumtl)); - goto exit; -} - -static res_T -parse_materials_list - (struct htrdr_materials* mats, - const char* filename, - const char* func_name) -{ - struct txtrdr* txtrdr = NULL; - struct str str; - res_T res = RES_OK; - ASSERT(mats && filename && func_name); - - str_init(mats->htrdr->allocator, &str); - - res = txtrdr_file(mats->htrdr->allocator, filename, '#', &txtrdr); - if(res != RES_OK) { - htrdr_log_err(mats->htrdr, - "%s: could not create the text reader for the material file `%s' -- %s.\n", - func_name, filename, res_to_cstr(res)); - goto error; - } - - for(;;) { - res = txtrdr_read_line(txtrdr); - if(res != RES_OK) { - htrdr_log_err(mats->htrdr, - "%s: error reading a line in the material file `%s' -- %s.\n", - func_name, filename, res_to_cstr(res)); - goto error; - } - - if(!txtrdr_get_cline(txtrdr)) break; - - res = parse_material(mats, txtrdr, &str); - if(res != RES_OK) goto error; - } - -exit: - str_release(&str); - if(txtrdr) txtrdr_ref_put(txtrdr); - return res; -error: - goto exit; -} - -static void -materials_release(ref_T* ref) -{ - struct htable_name2mtl_iterator it, it_end; - struct htrdr_materials* mats; - ASSERT(ref); - mats = CONTAINER_OF(ref, struct htrdr_materials, ref); - - htable_name2mtl_begin(&mats->name2mtl, &it); - htable_name2mtl_end(&mats->name2mtl, &it_end); - while(!htable_name2mtl_iterator_eq(&it, &it_end)) { - struct mtl* mtl = htable_name2mtl_iterator_data_get(&it); - /* The mrumtl can be NULL for semi transparent materials */ - if(mtl->mrumtl) MRUMTL(ref_put(mtl->mrumtl)); - htable_name2mtl_iterator_next(&it); - } - htable_name2mtl_release(&mats->name2mtl); - MEM_RM(mats->htrdr->allocator, mats); -} - -static res_T -create_bsdf_diffuse - (struct htrdr* htrdr, - const struct mrumtl_brdf* brdf, - const size_t ithread, - struct ssf_bsdf** out_bsdf) -{ - struct ssf_bsdf* bsdf = NULL; - double reflectivity = 0; - res_T res = RES_OK; - ASSERT(htrdr && brdf && out_bsdf); - ASSERT(mrumtl_brdf_get_type(brdf) == MRUMTL_BRDF_LAMBERTIAN); - ASSERT(ithread < htrdr->nthreads); - - res = ssf_bsdf_create - (&htrdr->lifo_allocators[ithread], &ssf_lambertian_reflection, &bsdf); - if(res != RES_OK) goto error; - - reflectivity = mrumtl_brdf_lambertian_get_reflectivity(brdf); - res = ssf_lambertian_reflection_setup(bsdf, reflectivity); - if(res != RES_OK) goto error; - -exit: - *out_bsdf = bsdf; - return res; -error: - if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; } - goto exit; -} - -static res_T -create_bsdf_specular - (struct htrdr* htrdr, - const struct mrumtl_brdf* brdf, - const size_t ithread, - struct ssf_bsdf** out_bsdf) -{ - struct ssf_bsdf* bsdf = NULL; - struct ssf_fresnel* fresnel = NULL; - double reflectivity = 0; - res_T res = RES_OK; - ASSERT(htrdr && brdf && out_bsdf); - ASSERT(mrumtl_brdf_get_type(brdf) == MRUMTL_BRDF_SPECULAR); - ASSERT(ithread < htrdr->nthreads); - - res = ssf_bsdf_create - (&htrdr->lifo_allocators[ithread], &ssf_specular_reflection, &bsdf); - if(res != RES_OK) goto error; - - res = ssf_fresnel_create - (&htrdr->lifo_allocators[ithread], &ssf_fresnel_constant, &fresnel); - if(res != RES_OK) goto error; - - reflectivity = mrumtl_brdf_specular_get_reflectivity(brdf); - res = ssf_fresnel_constant_setup(fresnel, reflectivity); - if(res != RES_OK) goto error; - - res = ssf_specular_reflection_setup(bsdf, fresnel); - if(res != RES_OK) goto error; - -exit: - if(fresnel) SSF(fresnel_ref_put(fresnel)); - *out_bsdf = bsdf; - return res; -error: - if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; } - goto exit; -} - -/******************************************************************************* - * Local symbol - ******************************************************************************/ -res_T -htrdr_materials_create - (struct htrdr* htrdr, - const char* filename, - struct htrdr_materials** out_mtl) -{ - struct htrdr_materials* mats = NULL; - res_T res = RES_OK; - ASSERT(htrdr && filename && out_mtl); - - mats = MEM_CALLOC(htrdr->allocator, 1, sizeof(*mats)); - if(!mats) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "%s: could not allocate the mats data structure -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - ref_init(&mats->ref); - mats->htrdr = htrdr; - htable_name2mtl_init(htrdr->allocator, &mats->name2mtl); - - res = parse_materials_list(mats, filename, FUNC_NAME); - if(res != RES_OK) goto error; - -exit: - if(out_mtl) *out_mtl = mats; - return res; -error: - if(mats) { - htrdr_materials_ref_put(mats); - mats = NULL; - } - goto exit; -} - -void -htrdr_materials_ref_get(struct htrdr_materials* mats) -{ - ASSERT(mats); - ref_get(&mats->ref); -} - -void -htrdr_materials_ref_put(struct htrdr_materials* mats) -{ - ASSERT(mats); - ref_put(&mats->ref, materials_release); -} - -int -htrdr_materials_find_mtl - (struct htrdr_materials* mats, - const char* name, - struct htrdr_mtl* htrdr_mtl) -{ - struct str str; - struct htable_name2mtl_iterator it, it_end; - int found = 0; - ASSERT(mats && name && htrdr_mtl); - - str_init(mats->htrdr->allocator, &str); - CHK(str_set(&str, name) == RES_OK); - - htable_name2mtl_find_iterator(&mats->name2mtl, &str, &it); - htable_name2mtl_end(&mats->name2mtl, &it_end); - if(htable_name2mtl_iterator_eq(&it, &it_end)) { /* No material found */ - *htrdr_mtl = HTRDR_MTL_NULL; - found = 0; - } else { - struct mtl* mtl = htable_name2mtl_iterator_data_get(&it); - ASSERT(mtl != NULL); - htrdr_mtl->name = str_cget(htable_name2mtl_iterator_key_get(&it)); - htrdr_mtl->mrumtl = mtl->mrumtl; - htrdr_mtl->temperature = mtl->temperature; - found = 1; - } - str_release(&str); - - return found; -} - -res_T -htrdr_mtl_create_bsdf - (struct htrdr* htrdr, - const struct htrdr_mtl* mtl, - const size_t ithread, - const double wavelength, - struct ssp_rng* rng, - struct ssf_bsdf** out_bsdf) -{ - struct ssf_bsdf* bsdf = NULL; - const struct mrumtl_brdf* brdf = NULL; - double r; - res_T res = RES_OK; - ASSERT(htrdr && mtl && wavelength && rng && out_bsdf); - ASSERT(ithread < htrdr->nthreads); - - r = ssp_rng_canonical(rng); - - res = mrumtl_fetch_brdf2(mtl->mrumtl, wavelength, r, &brdf); - if(res != RES_OK) { - htrdr_log_err(htrdr, - "%s: error retrieving the MruMtl BRDF for the wavelength %g.\n", - FUNC_NAME, wavelength); - res = RES_BAD_ARG; - goto error; - } - - switch(mrumtl_brdf_get_type(brdf)) { - case MRUMTL_BRDF_LAMBERTIAN: - res = create_bsdf_diffuse(htrdr, brdf, ithread, &bsdf); - break; - case MRUMTL_BRDF_SPECULAR: - res = create_bsdf_specular(htrdr, brdf, ithread, &bsdf); - break; - default: FATAL("Unreachable code.\n"); break; - } - if(res != RES_OK) { - htrdr_log_err(htrdr, "%s: could not create the BSDF -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - -exit: - *out_bsdf = bsdf; - return res; -error: - if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; } - goto exit; -} - diff --git a/src/htrdr_materials.h b/src/htrdr_materials.h @@ -1,67 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_MATERIALS_H -#define HTRDR_MATERIALS_H - -#include <rsys/rsys.h> - -/* Forward declarations */ -struct htrdr_materials; -struct mrumtl; -struct s3d_hit; -struct ssf_bsdf; -struct ssp_rng; - -struct htrdr_mtl { - const char* name; - const struct mrumtl* mrumtl; - double temperature; -}; -static const struct htrdr_mtl HTRDR_MTL_NULL; - -extern LOCAL_SYM res_T -htrdr_materials_create - (struct htrdr* htrdr, - const char* filename, - struct htrdr_materials** mats); - -extern LOCAL_SYM void -htrdr_materials_ref_get - (struct htrdr_materials* mats); - -extern LOCAL_SYM void -htrdr_materials_ref_put - (struct htrdr_materials* mats); - -/* Return 1 if the material exist and 0 otherwise */ -extern LOCAL_SYM int -htrdr_materials_find_mtl - (struct htrdr_materials* mats, - const char* mtl_name, - struct htrdr_mtl* mtl); - -extern LOCAL_SYM res_T -htrdr_mtl_create_bsdf - (struct htrdr* htrdr, - const struct htrdr_mtl* mtl, - const size_t ithread, - const double wavelength, - struct ssp_rng* rng, - struct ssf_bsdf** bsdf); - -#endif /* HTRDR_MATERIALS_H */ - diff --git a/src/htrdr_ran_wlen.c b/src/htrdr_ran_wlen.c @@ -1,364 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#define _POSIX_C_SOURCE 200112L /* nextafter */ - -#include "htrdr.h" -#include "htrdr_c.h" -#include "htrdr_ran_wlen.h" - -#include <high_tune/htsky.h> - -#include <rsys/algorithm.h> -#include <rsys/cstr.h> -#include <rsys/dynamic_array_double.h> -#include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> - -#include <math.h> /* nextafter */ - -struct htrdr_ran_wlen { - struct darray_double pdf; - struct darray_double cdf; - double range[2]; /* Boundaries of the spectral integration interval */ - double band_len; /* Length in nanometers of a band */ - double ref_temperature; /* In Kelvin */ - size_t nbands; /* # bands */ - - struct htrdr* htrdr; - ref_T ref; -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static res_T -setup_wlen_ran_cdf - (struct htrdr_ran_wlen* wlen_ran, - const char* func_name) -{ - double* pdf = NULL; - double* cdf = NULL; - double sum = 0; - size_t i; - res_T res = RES_OK; - ASSERT(wlen_ran && func_name && wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE); - - res = darray_double_resize(&wlen_ran->cdf, wlen_ran->nbands); - if(res != RES_OK) { - htrdr_log_err(wlen_ran->htrdr, - "%s: Error allocating the CDF of the spectral bands -- %s.\n", - func_name, res_to_cstr(res)); - goto error; - } - res = darray_double_resize(&wlen_ran->pdf, wlen_ran->nbands); - if(res != RES_OK) { - htrdr_log_err(wlen_ran->htrdr, - "%s: Error allocating the pdf of the spectral bands -- %s.\n", - func_name, res_to_cstr(res)); - goto error; - } - - cdf = darray_double_data_get(&wlen_ran->cdf); - pdf = darray_double_data_get(&wlen_ran->pdf); /* Now save pdf to correct weight */ - - htrdr_log(wlen_ran->htrdr, - "Number of bands of the spectrum cumulative: %lu\n", - (unsigned long)wlen_ran->nbands); - - /* Compute the *unnormalized* probability to sample a small band */ - FOR_EACH(i, 0, wlen_ran->nbands) { - double lambda_lo = wlen_ran->range[0] + (double)i * wlen_ran->band_len; - double lambda_hi = MMIN(lambda_lo + wlen_ran->band_len, wlen_ran->range[1]); - ASSERT(lambda_lo<= lambda_hi); - ASSERT(lambda_lo > wlen_ran->range[0] || eq_eps(lambda_lo, wlen_ran->range[0], 1.e-6)); - ASSERT(lambda_lo < wlen_ran->range[1] || eq_eps(lambda_lo, wlen_ran->range[1], 1.e-6)); - - /* Convert from nanometer to meter */ - lambda_lo *= 1.e-9; - lambda_hi *= 1.e-9; - - /* Compute the probability of the current band */ - pdf[i] = blackbody_fraction(lambda_lo, lambda_hi, wlen_ran->ref_temperature); - - /* Update the norm */ - sum += pdf[i]; - } - - /* Compute the cumulative of the previously computed probabilities */ - FOR_EACH(i, 0, wlen_ran->nbands) { - /* Normalize the probability */ - pdf[i] /= sum; - - /* Setup the cumulative */ - if(i == 0) { - cdf[i] = pdf[i]; - } else { - cdf[i] = pdf[i] + cdf[i-1]; - ASSERT(cdf[i] >= cdf[i-1]); - } - } - ASSERT(eq_eps(cdf[wlen_ran->nbands-1], 1, 1.e-6)); - cdf[wlen_ran->nbands - 1] = 1.0; /* Handle numerical issue */ - -exit: - return res; -error: - darray_double_clear(&wlen_ran->cdf); - darray_double_clear(&wlen_ran->pdf); - goto exit; -} - -static double -wlen_ran_sample_continue - (const struct htrdr_ran_wlen* wlen_ran, - const double r, - const double range[2], /* In nanometer */ - const char* func_name, - double* pdf) -{ - /* Numerical parameters of the dichotomy search */ - const size_t MAX_ITER = 100; - const double EPSILON_LAMBDA_M = 1e-15; - const double EPSILON_BF = 1e-6; - - /* Local variables */ - double bf = 0; - double bf_prev = 0; - double bf_min_max = 0; - double lambda_m = 0; - double lambda_m_prev = 0; - double lambda_m_min = 0; - double lambda_m_max = 0; - double range_m[2] = {0, 0}; - size_t i; - - /* Check precondition */ - ASSERT(wlen_ran && func_name); - ASSERT(range && range[0] < range[1]); - ASSERT(0 <= r && r < 1); - - /* Convert the wavelength range in meters */ - range_m[0] = range[0] * 1.0e-9; - range_m[1] = range[1] * 1.0e-9; - - /* Setup the dichotomy search */ - lambda_m_min = range_m[0]; - lambda_m_max = range_m[1]; - bf_min_max = blackbody_fraction - (range_m[0], range_m[1], wlen_ran->ref_temperature); - - /* Numerically search the lambda corresponding to the submitted canonical - * number */ - FOR_EACH(i, 0, MAX_ITER) { - double r_test; - lambda_m = (lambda_m_min + lambda_m_max) * 0.5; - bf = blackbody_fraction(range_m[0], lambda_m, wlen_ran->ref_temperature); - - r_test = bf / bf_min_max; - if(r_test < r) { - lambda_m_min = lambda_m; - } else { - lambda_m_max = lambda_m; - } - - if(fabs(lambda_m_prev - lambda_m) < EPSILON_LAMBDA_M - || fabs(bf_prev - bf) < EPSILON_BF) - break; - - lambda_m_prev = lambda_m; - bf_prev = bf; - } - if(i >= MAX_ITER) { - htrdr_log_warn(wlen_ran->htrdr, - "%s: could not sample a wavelength in the range [%g, %g] nanometers " - "for the reference temperature %g Kelvin.\n", - func_name, SPLIT2(range), wlen_ran->ref_temperature); - } - - if(pdf) { - const double Tref = wlen_ran->ref_temperature; - const double B_lambda = planck(lambda_m, lambda_m, Tref); - const double B_mean = planck(range_m[0], range_m[1], Tref); - *pdf = B_lambda / (B_mean * (range_m[1]-range_m[0])); - } - - return lambda_m * 1.0e9; /* Convert in nanometers */ -} - -static double -wlen_ran_sample_discrete - (const struct htrdr_ran_wlen* wlen_ran, - const double r0, - const double r1, - const char* func_name, - double* pdf) -{ - const double* cdf = NULL; - const double* find = NULL; - double r0_next = nextafter(r0, DBL_MAX); - double band_range[2]; - double lambda = 0; - double pdf_continue = 0; - double pdf_band = 0; - size_t cdf_length = 0; - size_t i; - ASSERT(wlen_ran && wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE); - ASSERT(0 <= r0 && r0 < 1); - ASSERT(0 <= r1 && r1 < 1); - (void)func_name; - (void)pdf_band; - - cdf = darray_double_cdata_get(&wlen_ran->cdf); - cdf_length = darray_double_size_get(&wlen_ran->cdf); - ASSERT(cdf_length > 0); - - /* Use r_next rather than r0 in order to find the first entry that is not less - * than *or equal* to r0 */ - find = search_lower_bound(&r0_next, cdf, cdf_length, sizeof(double), cmp_dbl); - ASSERT(find); - - i = (size_t)(find - cdf); - ASSERT(i < cdf_length && cdf[i] > r0 && (!i || cdf[i-1] <= r0)); - - band_range[0] = wlen_ran->range[0] + (double)i*wlen_ran->band_len; - band_range[1] = band_range[0] + wlen_ran->band_len; - - /* Fetch the pdf of the sampled band */ - pdf_band = darray_double_cdata_get(&wlen_ran->pdf)[i]; - - /* Uniformly sample a wavelength in the sampled band */ - lambda = band_range[0] + (band_range[1] - band_range[0]) * r1; - pdf_continue = 1.0 / ((band_range[1] - band_range[0])*1.e-9); - - if(pdf) { - *pdf = pdf_band * pdf_continue; - } - - return lambda; -} - -static void -release_wlen_ran(ref_T* ref) -{ - struct htrdr_ran_wlen* wlen_ran = NULL; - ASSERT(ref); - wlen_ran = CONTAINER_OF(ref, struct htrdr_ran_wlen, ref); - darray_double_release(&wlen_ran->cdf); - darray_double_release(&wlen_ran->pdf); - MEM_RM(wlen_ran->htrdr->allocator, wlen_ran); -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_ran_wlen_create - (struct htrdr* htrdr, - /* range must be included in [200,1000] nm for solar or in [1000, 100000] - * nanometers for longwave (thermal)*/ - const double range[2], - const size_t nbands, /* # bands used to discretized CDF */ - const double ref_temperature, - struct htrdr_ran_wlen** out_wlen_ran) -{ - struct htrdr_ran_wlen* wlen_ran = NULL; - double min_band_len = 0; - res_T res = RES_OK; - ASSERT(htrdr && range && out_wlen_ran && ref_temperature > 0); - ASSERT(ref_temperature > 0); - ASSERT(range[0] <= range[1]); - - wlen_ran = MEM_CALLOC(htrdr->allocator, 1, sizeof(*wlen_ran)); - if(!wlen_ran) { - res = RES_MEM_ERR; - htrdr_log_err(htrdr, - "%s: could not allocate longwave random variate data structure -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - ref_init(&wlen_ran->ref); - wlen_ran->htrdr = htrdr; - darray_double_init(htrdr->allocator, &wlen_ran->cdf); - darray_double_init(htrdr->allocator, &wlen_ran->pdf); - - wlen_ran->range[0] = range[0]; - wlen_ran->range[1] = range[1]; - wlen_ran->ref_temperature = ref_temperature; - wlen_ran->nbands = nbands; - - min_band_len = compute_sky_min_band_len(wlen_ran->htrdr->sky, wlen_ran->range); - - if(nbands == HTRDR_WLEN_RAN_CONTINUE) { - wlen_ran->band_len = 0; - } else { - wlen_ran->band_len = (range[1] - range[0]) / (double)wlen_ran->nbands; - - /* Adjust the band length to ensure that each sky spectral interval is - * overlapped by at least one band */ - if(wlen_ran->band_len > min_band_len) { - wlen_ran->band_len = min_band_len; - wlen_ran->nbands = (size_t)ceil((range[1] - range[0]) / wlen_ran->band_len); - } - - res = setup_wlen_ran_cdf(wlen_ran, FUNC_NAME); - if(res != RES_OK) goto error; - } - - htrdr_log(htrdr, "Spectral interval defined on [%g, %g] nanometers.\n", - range[0], range[1]); - -exit: - *out_wlen_ran = wlen_ran; - return res; -error: - if(wlen_ran) htrdr_ran_wlen_ref_put(wlen_ran); - goto exit; -} - -void -htrdr_ran_wlen_ref_get(struct htrdr_ran_wlen* wlen_ran) -{ - ASSERT(wlen_ran); - ref_get(&wlen_ran->ref); -} - -void -htrdr_ran_wlen_ref_put(struct htrdr_ran_wlen* wlen_ran) -{ - ASSERT(wlen_ran); - ref_put(&wlen_ran->ref, release_wlen_ran); -} - -double -htrdr_ran_wlen_sample - (const struct htrdr_ran_wlen* wlen_ran, - const double r0, - const double r1, - double* pdf) -{ - ASSERT(wlen_ran); - if(wlen_ran->nbands != HTRDR_WLEN_RAN_CONTINUE) { /* Discrete */ - return wlen_ran_sample_discrete(wlen_ran, r0, r1, FUNC_NAME, pdf); - } else if(eq_eps(wlen_ran->range[0], wlen_ran->range[1], 1.e-6)) { - if(pdf) *pdf = 1; - return wlen_ran->range[0]; - } else { /* Continue */ - return wlen_ran_sample_continue - (wlen_ran, r0, wlen_ran->range, FUNC_NAME, pdf); - } -} - diff --git a/src/htrdr_ran_wlen.h b/src/htrdr_ran_wlen.h @@ -1,54 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_RAN_WLEN_H -#define HTRDR_RAN_WLEN_H - -#include <rsys/rsys.h> - -#define HTRDR_WLEN_RAN_CONTINUE 0 - -struct htrdr; -struct htrdr_ran_wlen; - -extern LOCAL_SYM res_T -htrdr_ran_wlen_create - (struct htrdr* htrdr, - const double range[2], - /* # bands used to discretisze the spectral domain. HTRDR_WLEN_RAN_CONTINUE - * <=> no discretisation */ - const size_t nbands, /* Hint on #bands used to discretised th CDF */ - const double ref_temperature, /* Reference temperature */ - struct htrdr_ran_wlen** wlen_ran); - -extern LOCAL_SYM void -htrdr_ran_wlen_ref_get - (struct htrdr_ran_wlen* wlen_ran); - -extern LOCAL_SYM void -htrdr_ran_wlen_ref_put - (struct htrdr_ran_wlen* wlen_ran); - -/* Return a wavelength in nanometer */ -extern LOCAL_SYM double -htrdr_ran_wlen_sample - (const struct htrdr_ran_wlen* wlen_ran, - const double r0, /* Canonical number in [0, 1[ */ - const double r1, /* Canonical number in [0, 1[ */ - double* pdf); /* May be NULL */ - -#endif /* HTRDR_RAN_WLEN_H */ - diff --git a/src/htrdr_rectangle.c b/src/htrdr_rectangle.c @@ -1,142 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "htrdr.h" -#include "htrdr_rectangle.h" - -#include <rsys/double3.h> -#include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> - -struct htrdr_rectangle { - /* Frame of the rectangle in world space */ - double axis_x[3]; - double axis_y[3]; - - double normal[3]; - double position[3]; /* Center of the rectangle */ - struct htrdr* htrdr; - ref_T ref; -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static void -rectangle_release(ref_T* ref) -{ - struct htrdr_rectangle* rect; - ASSERT(ref); - rect = CONTAINER_OF(ref, struct htrdr_rectangle, ref); - MEM_RM(rect->htrdr->allocator, rect); -} - -/******************************************************************************* - * Local fuuction - ******************************************************************************/ -res_T -htrdr_rectangle_create - (struct htrdr* htrdr, - const double sz[2], - const double pos[3], - const double tgt[3], - const double up[3], - struct htrdr_rectangle** out_rect) -{ - struct htrdr_rectangle* rect = NULL; - double x[3], y[3], z[3]; - res_T res = RES_OK; - ASSERT(htrdr && pos && tgt && up && sz && out_rect); - - rect = MEM_CALLOC(htrdr->allocator, 1, sizeof(*rect)); - if(!rect) { - htrdr_log_err(htrdr, "could not allocate the rectangle data structure.\n"); - res = RES_MEM_ERR; - goto error; - } - ref_init(&rect->ref); - rect->htrdr = htrdr; - - if(sz[0] <= 0 || sz[1] <= 0) { - htrdr_log_err(htrdr, - "invalid rectangle size `%g %g'. It must be strictly positive.\n", - SPLIT2(sz)); - res = RES_BAD_ARG; - goto error; - } - - if(d3_normalize(z, d3_sub(z, tgt, pos)) <= 0 - || d3_normalize(x, d3_cross(x, z, up)) <= 0 - || d3_normalize(y, d3_cross(y, z, x)) <= 0) { - htrdr_log_err(htrdr, "invalid rectangle frame:\n" - "\tposition = %g %g %g\n" - "\ttarget = %g %g %g\n" - "\tup = %g %g %g\n", - SPLIT3(pos), SPLIT3(tgt), SPLIT3(up)); - res = RES_BAD_ARG; - goto error; - } - - d3_muld(rect->axis_x, x, sz[0]*0.5); - d3_muld(rect->axis_y, y, sz[1]*0.5); - d3_set(rect->normal, z); - d3_set(rect->position, pos); - -exit: - *out_rect = rect; - return res; -error: - if(rect) { - htrdr_rectangle_ref_put(rect); - rect = NULL; - } - goto exit; -} - -void -htrdr_rectangle_sample_pos - (const struct htrdr_rectangle* rect, - const double sample[2], /* In [0, 1[ */ - double pos[3]) -{ - double x[3], y[3]; - ASSERT(rect && sample && pos); - d3_muld(x, rect->axis_x, sample[0]*2.0 - 1.0); - d3_muld(y, rect->axis_y, sample[1]*2.0 - 1.0); - d3_add(pos, d3_add(pos, rect->position, x), y); -} - -void -htrdr_rectangle_ref_get(struct htrdr_rectangle* rect) -{ - ASSERT(rect); - ref_get(&rect->ref); -} - -void -htrdr_rectangle_ref_put(struct htrdr_rectangle* rect) -{ - ASSERT(rect); - ref_put(&rect->ref, rectangle_release); -} - -void -htrdr_rectangle_get_normal(const struct htrdr_rectangle* rect, double normal[3]) -{ - ASSERT(rect && normal); - d3_set(normal, rect->normal); -} - diff --git a/src/htrdr_rectangle.h b/src/htrdr_rectangle.h @@ -1,54 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_RECTANGLE_H -#define HTRDR_RECTANGLE_H - -#include <rsys/rsys.h> - -struct htrdr; -struct htrdr_rectangle; /* 2D rectangle transformed in 3D */ - -extern LOCAL_SYM res_T -htrdr_rectangle_create - (struct htrdr* htrdr, - const double sz[2], /* Size of the rectangle along its local X and Y axis */ - const double pos[3], /* World space position of the rectangle center */ - const double tgt[3], /* Vector orthognal to the rectangle plane */ - const double up[3], /* vector orthogonal to the rectangle X axis */ - struct htrdr_rectangle** rect); - -extern LOCAL_SYM void -htrdr_rectangle_ref_get - (struct htrdr_rectangle* rect); - -extern LOCAL_SYM void -htrdr_rectangle_ref_put - (struct htrdr_rectangle* rect); - -extern LOCAL_SYM void -htrdr_rectangle_sample_pos - (const struct htrdr_rectangle* rect, - const double sample[2], /* In [0, 1[ */ - double pos[3]); - -extern LOCAL_SYM void -htrdr_rectangle_get_normal - (const struct htrdr_rectangle* rect, - double normal[3]); - -#endif /* HTRDR_RECTANGLE_H */ - diff --git a/src/htrdr_sensor.c b/src/htrdr_sensor.c @@ -1,136 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "htrdr.h" -#include "htrdr_camera.h" -#include "htrdr_ground.h" -#include "htrdr_interface.h" -#include "htrdr_rectangle.h" -#include "htrdr_sensor.h" - -#include <star/s3d.h> -#include <star/ssp.h> - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static res_T -sample_camera_ray - (struct htrdr_camera* cam, - const size_t ipix[2], - const double pix_sz[2], - struct ssp_rng* rng, - double ray_org[3], - double ray_dir[3]) -{ - double pix_samp[2]; - ASSERT(cam && ipix && pix_sz && rng && ray_org && ray_dir); - - /* Sample a position into the pixel, in the normalized image plane */ - pix_samp[0] = ((double)ipix[0] + ssp_rng_canonical(rng)) * pix_sz[0]; - pix_samp[1] = ((double)ipix[1] + ssp_rng_canonical(rng)) * pix_sz[1]; - - /* Generate a ray starting from the pinhole camera and passing through the - * pixel sample */ - htrdr_camera_ray(cam, pix_samp, ray_org, ray_dir); - - return RES_OK; -} - -static res_T -sample_rectangle_ray - (struct htrdr_rectangle* rect, - struct htrdr* htrdr, - const size_t ipix[2], - const double pix_sz[2], - struct ssp_rng* rng, - double ray_org[3], - double ray_dir[3]) -{ - struct s3d_hit hit = S3D_HIT_NULL; - double pix_samp[2]; - const double up_dir[3] = {0,0,1}; - const double range[2] = {0, DBL_MAX}; - double normal[3]; - ASSERT(rect && htrdr && ipix && pix_sz && rng && ray_org && ray_dir); - - /* Sample a position into the pixel, in the normalized image plane */ - pix_samp[0] = ((double)ipix[0] + ssp_rng_canonical(rng)) * pix_sz[0]; - pix_samp[1] = ((double)ipix[1] + ssp_rng_canonical(rng)) * pix_sz[1]; - - /* Retrieve the world space position of pix_samp */ - htrdr_rectangle_sample_pos(rect, pix_samp, ray_org); - - /* Check that `ray_org' is not included into a geometry */ - HTRDR(ground_trace_ray(htrdr->ground, ray_org, up_dir, range, NULL, &hit)); - - /* Up direction is occluded. Check if the sample must be rejected, i.e. does it - * lies inside a geometry? */ - if(!S3D_HIT_NONE(&hit)) { - struct htrdr_interface interf = HTRDR_INTERFACE_NULL; - const struct htrdr_mtl* mtl = NULL; - float N[3]; /* Normalized normal of the hit */ - float wi[3]; - float cos_wi_N; - - /* Compute the cosine between the up direction and the hit normal */ - f3_set_d3(wi, up_dir); - f3_normalize(N, hit.normal); - cos_wi_N = f3_dot(wi, N); - - /* Fetch the hit interface and retrieve the material into which the ray was - * traced */ - htrdr_ground_get_interface(htrdr->ground, &hit, &interf); - mtl = cos_wi_N < 0 ? &interf.mtl_front : &interf.mtl_back; - - /* Reject the sample if the incident direction do not travel into the sky */ - if(strcmp(mtl->name, htrdr->sky_mtl_name) != 0) return RES_BAD_OP; - } - - /* Sample a ray direction */ - htrdr_rectangle_get_normal(rect, normal); - ssp_ran_hemisphere_cos(rng, normal, ray_dir, NULL); - - return RES_OK; -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_sensor_sample_primary_ray - (const struct htrdr_sensor* sensor, - struct htrdr* htrdr, - const size_t ipix[2], - const double pix_sz[2], - struct ssp_rng* rng, - double ray_org[3], - double ray_dir[3]) -{ - res_T res = RES_OK; - switch(sensor->type) { - case HTRDR_SENSOR_CAMERA: - res = sample_camera_ray(sensor->camera, ipix, pix_sz, rng, ray_org, ray_dir); - break; - case HTRDR_SENSOR_RECTANGLE: - res = sample_rectangle_ray(sensor->rectangle, htrdr, ipix, - pix_sz, rng, ray_org, ray_dir); - break; - default: FATAL("Unreachable code.\n"); break; - } - return res; -} - diff --git a/src/htrdr_sensor.h b/src/htrdr_sensor.h @@ -1,48 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_SENSOR_H -#define HTRDR_SENSOR_H - -#include <rsys/rsys.h> - -/* Forward declarations */ -struct htrdr; -struct ssp_rng; - -enum htrdr_sensor_type { - HTRDR_SENSOR_CAMERA, - HTRDR_SENSOR_RECTANGLE -}; - -struct htrdr_sensor { - struct htrdr_camera* camera; - struct htrdr_rectangle* rectangle; - enum htrdr_sensor_type type; -}; - -extern LOCAL_SYM res_T -htrdr_sensor_sample_primary_ray - (const struct htrdr_sensor* sensor, - struct htrdr* htrdr, - const size_t ipix[2], - const double pix_sz[2], - struct ssp_rng* rng, - double ray_org[3], - double ray_dir[3]); - -#endif /* HTRDR_SENSOR_H */ - diff --git a/src/htrdr_slab.c b/src/htrdr_slab.c @@ -1,134 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "htrdr.h" -#include "htrdr_slab.h" - -#include <rsys/cstr.h> -#include <math.h> - -/******************************************************************************* - * Local function - ******************************************************************************/ -res_T -htrdr_slab_trace_ray - (struct htrdr* htrdr, - const double org[3], - const double dir[3], - const double range[2], - const double cell_low[2], - const double cell_upp[2], - htrdr_trace_cell_T trace_cell, - const size_t max_steps, - void* trace_cell_context) -{ - double pos[2]; - double org_cs[3]; /* Origin of the ray transformed in local cell space */ - double cell_low_ws[3]; /* Cell lower bound in world space */ - double cell_upp_ws[3]; /* Cell upper bound in world space */ - double cell_sz[3]; /* Size of a cell */ - double t_max[3], t_delta[2], t_min_z; - size_t istep; - int64_t xy[2]; /* 2D index of the repeated cell */ - int incr[2]; /* Index increment */ - res_T res = RES_OK; - ASSERT(htrdr && org && dir && range && cell_low && cell_upp && trace_cell); - ASSERT(range[0] < range[1]); - - /* Check that the ray intersects the slab */ - t_min_z = (cell_low[2] - org[2]) / dir[2]; - t_max[2] = (cell_upp[2] - org[2]) / dir[2]; - if(t_min_z > t_max[2]) SWAP(double, t_min_z, t_max[2]); - t_min_z = MMAX(t_min_z, range[0]); - t_max[2] = MMIN(t_max[2], range[1]); - if(t_min_z > t_max[2]) return RES_OK; - - /* Compute the size of a cell */ - cell_sz[0] = cell_upp[0] - cell_low[0]; - cell_sz[1] = cell_upp[1] - cell_low[1]; - cell_sz[2] = cell_upp[2] - cell_low[2]; - - /* Define the 2D index of the current cell. (0,0) is the index of the - * non duplicated cell */ - pos[0] = org[0] + t_min_z*dir[0]; - pos[1] = org[1] + t_min_z*dir[1]; - xy[0] = (int64_t)floor((pos[0] - cell_low[0]) / cell_sz[0]); - xy[1] = (int64_t)floor((pos[1] - cell_low[1]) / cell_sz[1]); - - /* Define the 2D index increment wrt dir sign */ - incr[0] = dir[0] < 0 ? -1 : 1; - incr[1] = dir[1] < 0 ? -1 : 1; - - /* Compute the world space AABB of the repeated cell currently hit */ - cell_low_ws[0] = cell_low[0] + (double)xy[0]*cell_sz[0]; - cell_low_ws[1] = cell_low[1] + (double)xy[1]*cell_sz[1]; - cell_low_ws[2] = cell_low[2]; - cell_upp_ws[0] = cell_low_ws[0] + cell_sz[0]; - cell_upp_ws[1] = cell_low_ws[1] + cell_sz[1]; - cell_upp_ws[2] = cell_upp[2]; - - /* Compute the max ray intersection with the current cell */ - t_max[0] = ((dir[0]<0 ? cell_low_ws[0] : cell_upp_ws[0]) - org[0]) / dir[0]; - t_max[1] = ((dir[1]<0 ? cell_low_ws[1] : cell_upp_ws[1]) - org[1]) / dir[1]; - /*t_max[2] = ((dir[2]<0 ? cell_low_ws[2] : cell_upp_ws[2]) - org[2]) / dir[2];*/ - ASSERT(t_max[0] >= 0 && t_max[1] >= 0 && t_max[2] >= 0); - - /* Compute the distance along the ray to traverse in order to move of a - * distance equal to the cloud size along the X and Y axis */ - t_delta[0] = (dir[0]<0 ? -cell_sz[0] : cell_sz[0]) / dir[0]; - t_delta[1] = (dir[1]<0 ? -cell_sz[1] : cell_sz[1]) / dir[1]; - ASSERT(t_delta[0] >= 0 && t_delta[1] >= 0); - - org_cs[2] = org[2]; - FOR_EACH(istep, 0, max_steps) { - int iaxis; - int hit; - - /* Transform the ray origin in the local cell space */ - org_cs[0] = org[0] - (double)xy[0]*cell_sz[0]; - org_cs[1] = org[1] - (double)xy[1]*cell_sz[1]; - - res = trace_cell(org_cs, dir, range, trace_cell_context, &hit); - if(res != RES_OK) { - htrdr_log_err(htrdr, - "%s: could not trace the ray in the repeated cells -- %s.\n", - FUNC_NAME, res_to_cstr(res)); - goto error; - } - if(hit) goto exit; - - /* Define the next axis to traverse */ - iaxis = t_max[0] < t_max[1] - ? (t_max[0] < t_max[2] ? 0 : 2) - : (t_max[1] < t_max[2] ? 1 : 2); - - if(iaxis == 2) break; /* The ray traverse the slab */ - - if(t_max[iaxis] >= range[1]) break; /* Out of bound */ - - t_max[iaxis] += t_delta[iaxis]; - - /* Define the 2D index of the next traversed cloud */ - xy[iaxis] += incr[iaxis]; - } - -exit: - return res; -error: - goto exit; -} - - diff --git a/src/htrdr_slab.h b/src/htrdr_slab.h @@ -1,47 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_SLAB_H -#define HTRDR_SLAB_H - -#include <rsys/rsys.h> - -/* Forward declaration */ -struct htrdr; - -typedef res_T -(*htrdr_trace_cell_T) - (const double org[3], /* Ray origin */ - const double dir[3], /* Ray direction. Must be normalized */ - const double range[2], /* Ray range */ - void* ctx, /* User defined data */ - int* hit); /* Hit something ? */ - -/* Trace a ray into a slab composed of a cell infinitely repeated in X and Y */ -extern LOCAL_SYM res_T -htrdr_slab_trace_ray - (struct htrdr* htrdr, - const double org[3], - const double dir[3], - const double range[2], - const double cell_low[2], - const double cell_upp[2], - htrdr_trace_cell_T trace_cell, - const size_t max_steps, /* Max traversed cell */ - void* trace_cell_context); - -#endif /* HTRDR_SLAB_H */ - diff --git a/src/htrdr_solve.h b/src/htrdr_solve.h @@ -1,185 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_SOLVE_H -#define HTRDR_SOLVE_H - -#include <rsys/rsys.h> - -/* Define the radiance component */ -enum htrdr_radiance_cpnt_flag { - HTRDR_RADIANCE_DIRECT = BIT(0), - HTRDR_RADIANCE_DIFFUSE = BIT(1), - HTRDR_RADIANCE_ALL = HTRDR_RADIANCE_DIRECT | HTRDR_RADIANCE_DIFFUSE -}; - -/* Monte carlo accumulator */ -struct htrdr_accum { - double sum_weights; /* Sum of Monte-Carlo weights */ - double sum_weights_sqr; /* Sum of Monte-Carlo square weights */ - size_t nweights; /* #accumlated weights */ - size_t nfailures; /* #failures */ -}; -static const struct htrdr_accum HTRDR_ACCUM_NULL; - -/* Monte carlo estimate */ -struct htrdr_estimate { - double E; /* Expected value */ - double SE; /* Standard error */ -}; -static const struct htrdr_estimate HTRDR_ESTIMATE_NULL; - -struct htrdr_pixel_xwave { - struct htrdr_estimate radiance; /* In W/m^2/sr */ - struct htrdr_estimate radiance_temperature; /* In K */ - struct htrdr_accum time; /* In microseconds */ -}; -static const struct htrdr_pixel_xwave HTRDR_PIXEL_XWAVE_NULL; - -struct htrdr_pixel_flux { - struct htrdr_accum flux; - struct htrdr_accum time; -}; -static const struct htrdr_pixel_flux HTRDR_PIXEL_FLUX; - -struct htrdr_pixel_image { - struct htrdr_estimate X; /* In W/m^2/sr */ - struct htrdr_estimate Y; /* In W/m^2/sr */ - struct htrdr_estimate Z; /* In W/m^2/sr */ - struct htrdr_accum time; /* In microseconds */ -}; -static const struct htrdr_pixel_image HTRDR_PIXEL_IMAGE_NULL; - -/* Forward declarations */ -struct htrdr; -struct htrdr_camera; -struct s3d_hit; -struct ssp_rng; - -/* Return the shortwave radiance in W/m^2/sr/m */ -extern LOCAL_SYM double -htrdr_compute_radiance_sw - (struct htrdr* htrdr, - const size_t ithread, - struct ssp_rng* rng, - const int cpnt_mask, /* Combination of enum htrdr_radiance_cpnt_flag */ - const double pos[3], - const double dir[3], - const double wlen, /* In nanometer */ - const size_t iband, /* Index of the spectral band */ - const size_t iquad); /* Index of the quadrature point into the band */ - -/* Return the longwave radiance in W/m^2/sr/m */ -extern LOCAL_SYM double -htrdr_compute_radiance_lw - (struct htrdr* htrdr, - const size_t ithread, - struct ssp_rng* rng, - const double pos[3], - const double dir[3], - const double wlen, /* In nanometer */ - const size_t iband, /* Index of the spectral band */ - const size_t iquad); /* Index of the quadrature point into the band */ - -extern LOCAL_SYM res_T -htrdr_draw_map - (struct htrdr* htrdr, - const struct htrdr_sensor* sensor, - const size_t width, /* Image width */ - const size_t height, /* Image height */ - const size_t spp, /* #samples per pixel, i.e. #realisations */ - /* Buffer of struct htrdr_accum[4]. May be NULL on non master processes */ - struct htrdr_buffer* buf); - -extern LOCAL_SYM int -htrdr_ground_filter - (const struct s3d_hit* hit, - const float ray_dorg[3], - const float ray_dir[3], - void* ray_data, - void* filter_data); - -static FINLINE void -htrdr_accum_get_estimation - (const struct htrdr_accum* acc, - struct htrdr_estimate* estimate) -{ - ASSERT(acc && estimate); - - if(!acc->nweights) { - estimate->E = 0; - estimate->SE = 0; - } else { - const double N = (double)acc->nweights; - double E, V, SE; - E = acc->sum_weights / N; - V = MMAX(acc->sum_weights_sqr / N - E*E, 0); - SE = sqrt(V/N); - - estimate->E = E; - estimate->SE = SE; - } -} - -static INLINE size_t -htrdr_spectral_type_get_pixsz - (const enum htrdr_spectral_type spectral_type, - const enum htrdr_sensor_type sensor_type) -{ - size_t sz = 0; - if(sensor_type == HTRDR_SENSOR_RECTANGLE) { - sz = sizeof(struct htrdr_pixel_flux); - } else { - ASSERT(sensor_type == HTRDR_SENSOR_CAMERA); - switch(spectral_type) { - case HTRDR_SPECTRAL_LW: - case HTRDR_SPECTRAL_SW: - sz = sizeof(struct htrdr_pixel_xwave); - break; - case HTRDR_SPECTRAL_SW_CIE_XYZ: - sz = sizeof(struct htrdr_pixel_image); - break; - default: FATAL("Unreachable code.\n"); break; - } - } - return sz; -} - -static INLINE size_t -htrdr_spectral_type_get_pixal - (const enum htrdr_spectral_type spectral_type, - const enum htrdr_sensor_type sensor_type) -{ - size_t al = 0; - if(sensor_type == HTRDR_SENSOR_RECTANGLE) { - al = ALIGNOF(struct htrdr_pixel_flux); - } else { - ASSERT(sensor_type == HTRDR_SENSOR_CAMERA); - switch(spectral_type) { - case HTRDR_SPECTRAL_LW: - case HTRDR_SPECTRAL_SW: - al = ALIGNOF(struct htrdr_pixel_xwave); - break; - case HTRDR_SPECTRAL_SW_CIE_XYZ: - al = ALIGNOF(struct htrdr_pixel_image); - break; - default: FATAL("Unreachable code.\n"); break; - } - } - return al; -} - -#endif /* HTRDR_SOLVE_H */ diff --git a/src/htrdr_spectral.c b/src/htrdr_spectral.c @@ -1,79 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "htrdr.h" -#include "htrdr_spectral.h" - -res_T -brightness_temperature - (struct htrdr* htrdr, - const double lambda_min, - const double lambda_max, - const double radiance, /* In W/m2/sr/m */ - double* temperature) -{ - const size_t MAX_ITER = 100; - const double epsilon_T = 1e-4; /* In K */ - const double epsilon_B = radiance * 1e-8; - double T, T0, T1, T2; - double B, B0; - size_t i; - res_T res = RES_OK; - ASSERT(temperature && lambda_min <= lambda_max); - - /* Search for a brightness temperature whose radiance is greater than or - * equal to the estimated radiance */ - T2 = 200; - FOR_EACH(i, 0, MAX_ITER) { - const double B2 = planck(lambda_min, lambda_max, T2); - if(B2 >= radiance) break; - T2 *= 2; - } - if(i >= MAX_ITER) { res = RES_BAD_OP; goto error; } - - B0 = T0 = T1 = 0; - FOR_EACH(i, 0, MAX_ITER) { - T = (T1+T2)*0.5; - B = planck(lambda_min, lambda_max, T); - - if(B < radiance) { - T1 = T; - } else { - T2 = T; - } - - if(fabs(T-T0) < epsilon_T || fabs(B-B0) < epsilon_B) - break; - - T0 = T; - B0 = B; - } - if(i >= MAX_ITER) { res = RES_BAD_OP; goto error; } - - *temperature = T; - -exit: - return res; -error: - htrdr_log_err(htrdr, - "Could not compute the brightness temperature for the estimated radiance %g " - "averaged over [%g, %g] nanometers.\n", - radiance, - lambda_min*1e9, - lambda_max*1e9); - goto exit; -} - diff --git a/src/htrdr_spectral.h b/src/htrdr_spectral.h @@ -1,137 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_SPECTRAL_H -#define HTRDR_SPECTRAL_H - -#include <rsys/rsys.h> -#include <rsys/math.h> /* PI support */ - -#define HTRDR_SUN_TEMPERATURE 5778 /* In K */ -#define HTRDR_DEFAULT_LW_REF_TEMPERATURE 290 /* Default longwave temperature in K */ - -enum htrdr_spectral_type { - HTRDR_SPECTRAL_LW, /* Longwave */ - HTRDR_SPECTRAL_SW, /* Shortwave */ - HTRDR_SPECTRAL_SW_CIE_XYZ /* Shortwave wrt the CIE XYZ tristimulus */ -}; - -struct htrdr; - -static INLINE double -wiebelt(const double v) -{ - int m; - double w, v2, v4; - /*.153989717364e+00;*/ - const double fifteen_over_pi_power_4 = 15.0/(PI*PI*PI*PI); - const double z0 = 1.0/3.0; - const double z1 = 1.0/8.0; - const double z2 = 1.0/60.0; - const double z4 = 1.0/5040.0; - const double z6 = 1.0/272160.0; - const double z8 = 1.0/13305600.0; - - if(v >= 2.) { - w = 0; - for(m=1; m<6 ;m++) - w+=exp(-m*v)/(m*m*m*m) * (((m*v+3)*m*v+6)*m*v+6); - w = w * fifteen_over_pi_power_4; - } else { - v2 = v*v; - v4 = v2*v2; - w = z0 - z1*v + z2*v2 - z4*v2*v2 + z6*v4*v2 - z8*v4*v4; - w = 1. - fifteen_over_pi_power_4*v2*v*w; - } - ASSERT(w >= 0.0 && w <= 1.0); - return w; -} - -static INLINE double -blackbody_fraction - (const double lambda0, /* In meters */ - const double lambda1, /* In meters */ - const double temperature) /* In Kelvin */ -{ - const double C2 = 1.43877735e-2; /* m.K */ - double x0 = C2 / lambda0; - double x1 = C2 / lambda1; - double v0 = x0 / temperature; - double v1 = x1 / temperature; - double w0 = wiebelt(v0); - double w1 = wiebelt(v1); - return w1 - w0; -} - -/* Return the Planck value in W/m^2/sr/m at a given wavelength */ -static INLINE double -planck_monochromatic - (const double lambda, /* In meters */ - const double temperature) /* In Kelvin */ -{ - const double c = 299792458; /* m/s */ - const double h = 6.62607015e-34; /* J.s */ - const double k = 1.380649e-23; /* J/K */ - const double lambda2 = lambda*lambda; - const double lambda5 = lambda2*lambda2*lambda; - const double B = ((2.0 * h * c*c) / lambda5) /* W/m^2/sr/m */ - / (exp(h*c/(lambda*k*temperature))-1.0); - ASSERT(temperature > 0); - return B; -} - -/* Return the average Planck value in W/m^2/sr/m over the - * [lambda_min, lambda_max] interval. */ -static INLINE double -planck_interval - (const double lambda_min, /* In meters */ - const double lambda_max, /* In meters */ - const double temperature) /* In Kelvin */ -{ - const double T2 = temperature*temperature; - const double T4 = T2*T2; - const double BOLTZMANN_CONSTANT = 5.6696e-8; /* W/m^2/K^4 */ - ASSERT(lambda_min < lambda_max && temperature > 0); - return blackbody_fraction(lambda_min, lambda_max, temperature) - * BOLTZMANN_CONSTANT * T4 / (PI * (lambda_max-lambda_min)); /* In W/m^2/sr/m */ -} - -/* Invoke planck_monochromatic or planck_interval whether the submitted - * interval is null or not, respectively. The returned value is in W/m^2/sr/m */ -static FINLINE double -planck - (const double lambda_min, /* In meters */ - const double lambda_max, /* In meters */ - const double temperature) /* In Kelvin */ -{ - ASSERT(lambda_min <= lambda_max && temperature > 0); - if(lambda_min == lambda_max) { - return planck_monochromatic(lambda_min, temperature); - } else { - return planck_interval(lambda_min, lambda_max, temperature); - } -} - -extern LOCAL_SYM res_T -brightness_temperature - (struct htrdr* htrdr, - const double lambda_min, /* In meters */ - const double lambda_max, /* In meters */ - /* Averaged over [lambda_min, lambda_max]. In W/m^2/sr/m */ - const double radiance, - double* temperature); - -#endif /* HTRDR_SPECTRAL_H */ diff --git a/src/htrdr_sun.c b/src/htrdr_sun.c @@ -1,144 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "htrdr.h" -#include "htrdr_c.h" -#include "htrdr_sun.h" - -#include <rsys/algorithm.h> -#include <rsys/double33.h> -#include <rsys/ref_count.h> -#include <rsys/math.h> - -#include <star/ssp.h> - -struct htrdr_sun { - double half_angle; /* In radian */ - double cos_half_angle; - double solid_angle; /* In sr; solid_angle = 2*PI*(1 - cos(half_angle)) */ - double frame[9]; - double temperature; /* In K */ - - ref_T ref; - struct htrdr* htrdr; -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static void -release_sun(ref_T* ref) -{ - struct htrdr_sun* sun; - ASSERT(ref); - sun = CONTAINER_OF(ref, struct htrdr_sun, ref); - MEM_RM(sun->htrdr->allocator, sun); -} - -/******************************************************************************* - * Local functions - ******************************************************************************/ -res_T -htrdr_sun_create(struct htrdr* htrdr, struct htrdr_sun** out_sun) -{ - const double main_dir[3] = {0, 0, 1}; /* Default main sun direction */ - struct htrdr_sun* sun = NULL; - res_T res = RES_OK; - ASSERT(htrdr && out_sun); - - sun = MEM_CALLOC(htrdr->allocator, 1, sizeof(*sun)); - if(!sun) { - htrdr_log_err(htrdr, "could not allocate the sun data structure.\n"); - res = RES_MEM_ERR; - goto error; - } - ref_init(&sun->ref); - sun->htrdr = htrdr; - sun->half_angle = 4.6524e-3; - sun->temperature = 5778; - sun->cos_half_angle = cos(sun->half_angle); - sun->solid_angle = 2*PI*(1-sun->cos_half_angle); - d33_basis(sun->frame, main_dir); - -exit: - *out_sun = sun; - return res; -error: - if(sun) { - htrdr_sun_ref_put(sun); - sun = NULL; - } - goto exit; -} - -void -htrdr_sun_ref_get(struct htrdr_sun* sun) -{ - ASSERT(sun); - ref_get(&sun->ref); -} - -void -htrdr_sun_ref_put(struct htrdr_sun* sun) -{ - ASSERT(sun); - ref_put(&sun->ref, release_sun); -} - -void -htrdr_sun_set_direction(struct htrdr_sun* sun, const double dir[3]) -{ - ASSERT(sun && dir && d3_is_normalized(dir)); - d33_basis(sun->frame, dir); -} - -double -htrdr_sun_sample_direction - (struct htrdr_sun* sun, - struct ssp_rng* rng, - double dir[3]) -{ - ASSERT(sun && rng && dir); - ssp_ran_sphere_cap_uniform_local(rng, sun->cos_half_angle, dir, NULL); - d33_muld3(dir, sun->frame, dir); - return 1.0 / htrdr_sun_get_solid_angle(sun); -} - -double -htrdr_sun_get_solid_angle(const struct htrdr_sun* sun) -{ - ASSERT(sun); - return sun->solid_angle; -} - -double -htrdr_sun_get_radiance(const struct htrdr_sun* sun, const double wlen/*In nm*/) -{ - return planck_monochromatic(wlen*1.e-9/*From nm to m*/, sun->temperature); -} - -int -htrdr_sun_is_dir_in_solar_cone(const struct htrdr_sun* sun, const double dir[3]) -{ - const double* main_dir; - double dot; - ASSERT(sun && dir && d3_is_normalized(dir)); - ASSERT(d3_is_normalized(sun->frame + 6)); - main_dir = sun->frame + 6; - dot = d3_dot(dir, main_dir); - return dot >= sun->cos_half_angle; -} - diff --git a/src/htrdr_sun.h b/src/htrdr_sun.h @@ -1,67 +0,0 @@ -/* Copyright (C) 2018, 2019, 2020 |Meso|Star> (contact@meso-star.com) - * Copyright (C) 2018, 2019 CNRS, Université Paul Sabatier - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef HTRDR_SUN_H -#define HTRDR_SUN_H - -#include <rsys/rsys.h> - -/* Forward declaration */ -struct htrdr; -struct htrdr_sun; -struct ssp_rng; - -extern LOCAL_SYM res_T -htrdr_sun_create - (struct htrdr* htrdr, - struct htrdr_sun** out_sun); - -extern LOCAL_SYM void -htrdr_sun_ref_get - (struct htrdr_sun* sun); - -extern LOCAL_SYM void -htrdr_sun_ref_put - (struct htrdr_sun* sun); - -/* Setup the direction *toward* the sun "center" */ -extern LOCAL_SYM void -htrdr_sun_set_direction - (struct htrdr_sun* sun, - const double direction[3]); /* Must be normalized */ - -/* Return a pdf of the sampled dir */ -extern LOCAL_SYM double -htrdr_sun_sample_direction - (struct htrdr_sun* sun, - struct ssp_rng* rng, - double dir[3]); - -extern LOCAL_SYM double -htrdr_sun_get_solid_angle - (const struct htrdr_sun* sun); - -extern LOCAL_SYM double /* W/m^2/sr/m */ -htrdr_sun_get_radiance - (const struct htrdr_sun* sun, - const double wavelength); - -extern LOCAL_SYM int -htrdr_sun_is_dir_in_solar_cone - (const struct htrdr_sun* sun, - const double dir[3]); - -#endif /* HTRDR_SUN_H */