star-phor

Radiative transfer solver for photoreactors.
git clone https://www.edstar.cnrs.fr/git/star-phor.git
Log | Files | Refs | README | LICENSE

commit fbe60e6000694c1cf95afed6521719449e74736f
parent 46e0ec243da62bcdefee4e080fdc107808433ec0
Author: Eduardo Fontana Lazzari <edufonlaz@gmail.com>
Date:   Wed,  9 Jul 2025 18:00:04 +0200

Implement first Monte Carlo algorithm

This commit implements a Monte Carlo algorithm that computes the
fraction of photons emitted in a scene that are absorbed in a volume
acting as a sensor. This type of algorithm is particularly useful for
computing the Mean Volumetric Rate of Energy Absorption (MVREA). This
quantity is related to absorptivity via its product with a factor that
accounts for the source power and the specific illuminated surface.

Several useful abstractions are also introduced, such as functions for
sampling path departure directions according to a directional
distribution, and handling reflections on BRDF surfaces.

The realization loop is parallelized. As a result, this commit
introduces a dependency on the OpenMPI library.

Diffstat:
MMakefile | 6++++++
MREADME.md | 1+
Mconfig.mk | 8++++++--
Msrc/sphor.c | 6+++++-
Asrc/sphor_compute_mvrea.c | 334+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sphor_compute_mvrea.h | 39+++++++++++++++++++++++++++++++++++++++
Asrc/sphor_interface.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/sphor_interface.h | 12++++++++++++
Asrc/sphor_ran_brdf.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sphor_ran_brdf.h | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/sphor_ran_geometry.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sphor_ran_geometry.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sphor_ran_source.c | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sphor_ran_source.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/sphor_sources.c | 83-------------------------------------------------------------------------------
Msrc/sphor_sources.h | 28+---------------------------
16 files changed, 951 insertions(+), 113 deletions(-)

diff --git a/Makefile b/Makefile @@ -38,7 +38,12 @@ all: executable library tests ################################################################################ SRC_LIB =\ src/sphor.c\ + src/sphor_compute_mvrea.c\ src/sphor_config.c\ + src/sphor_interface.c\ + src/sphor_ran_brdf.c\ + src/sphor_ran_geometry.c\ + src/sphor_ran_source.c\ src/sphor_sources.c OBJ_LIB = $(SRC_LIB:.c=.o) DEP_LIB = $(SRC_LIB:.c=.d) @@ -58,6 +63,7 @@ $(LIBNAME): $(OBJ_LIB) $(CC) $(CFLAGS_LIB) -o $@ $(OBJ_LIB) $(LDFLAGS_LIB) .config: config.mk + $(PKG_CONFIG) --atleast-version $(MPI_VERSION) $(MPI_PC) $(PKG_CONFIG) --atleast-version $(RSYS_VERSION) rsys $(PKG_CONFIG) --atleast-version $(S3D_VERSION) s3d $(PKG_CONFIG) --atleast-version $(SPHIN_VERSION) sphin diff --git a/README.md b/README.md @@ -12,6 +12,7 @@ Solver for radiative transfer in photoreactors. - star-3d - star-sp - star-uniq +- Open MPI ## Installation diff --git a/config.mk b/config.mk @@ -18,18 +18,22 @@ LD = ld OBJCOPY = objcopy PKG_CONFIG = pkg-config RANLIB = ranlib +MPI_PC = ompi ################################################################################ # Dependencies ################################################################################ +MPI_VERSION = 2 RSYS_VERSION=0.14 S3D_VERSION=0.10 SPHIN_VERSION=0.0 SSP_VERSION=0.14 SUNIQ_VERSION=0.0 -INCS = $$($(PKG_CONFIG) --cflags rsys s3d sphin star-sp suniq) -LIBS = $$($(PKG_CONFIG) --libs rsys s3d sphin star-sp suniq) +INCS = $$($(PKG_CONFIG) --cflags $(MPI_PC) rsys s3d sphin star-sp suniq)\ + -fopenmp -lm +LIBS = $$($(PKG_CONFIG) --libs $(MPI_PC) rsys s3d sphin star-sp suniq)\ + -fopenmp -lm ################################################################################ # Compilation options diff --git a/src/sphor.c b/src/sphor.c @@ -24,6 +24,7 @@ #include "sphor.h" #include "sphor_c.h" +#include "sphor_compute_mvrea.h" #include <star/sphin.h> #include <star/s3d.h> @@ -147,11 +148,14 @@ sphor_ref_put(struct sphor* sphor) res_T sphor_run(struct sphor* sphor) { + double estim = 0; + double std = 0; res_T res = RES_OK; if (NULL == sphor) { goto error; } - /* TODO Run Monte Carlo algorithm */ + res = sphor_compute_MVREA(sphor, &estim, &std); + if (RES_OK != res) { goto error; } exit: return res; diff --git a/src/sphor_compute_mvrea.c b/src/sphor_compute_mvrea.c @@ -0,0 +1,334 @@ +/* Copyright (C) 2024-2025 Centre National de la Recherche Scientifique + * Copyright (C) 2024-2025 Clermont Auvergne INP + * Copyright (C) 2024-2025 INSA Lyon + * Copyright (C) 2024-2025 Institut Mines Télécom Albi-Carmaux + * Copyright (C) 2024-2025 Institut National Polytechnique de Toulouse + * Copyright (C) 2024-2025 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2024-2025 PhotonLyX (info@photonlyx.com) + * Copyright (C) 2024-2025 Université de Lorraine + * Copyright (C) 2024-2025 Université Paul Sabatier + * Copyright (C) 2024-2025 Université Toulouse - Jean Jaurès + * + * 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 "sphor.h" +#include "sphor_c.h" +#include "sphor_compute_mvrea.h" +#include "sphor_interface.h" +#include "sphor_ran_source.h" +#include "sphor_ran_brdf.h" + +#include <star/sphin.h> +#include <star/s3d.h> +#include <star/ssp.h> +#include <omp.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static res_T +compute_MVREA_realization +(struct sphor* sphor, + struct ssp_rng* rng, + double* weight/* TODO make this a list in sphor, since we can have multiple + sensors. Each sensor should have its own weight */) +{ + double ka = 0; /* Absorption coefficient of current volume */ + double normal[3] = {0}; /* Geometry normal */ + double dir[3] = {0}; /* Current direction in path sampling */ + double pos[3] = {0}; /* Current position in path sampling */ + double sample[3] = {0}; + double reflectivity = 0; /* Surface reflectivity */ + double response_function = 0; + double s = 0; /* Random number */ + enum sphin_brdf_type brdf_type = SPHIN_BRDF_NONE__; + enum sphin_side side_id = SPHIN_SIDE_NONE__; + /* Position and direction using when calling s3d functions */ + float r[3] = {0}; /* Position */ + float w[3] = {0}; /* Direction */ + /* Allowed range for rays during path sampling */ + float range[2] = {0.f, FLT_MAX}; + float st[2] = {0}; /* Parametric coordinates of a point in a s3d_primitive */ + int i = 0; /* Iterator */ + size_t triangle_id; + struct interface interface = INTERFACE_NULL; + struct s3d_attrib attrib; + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_primitive prim = S3D_PRIMITIVE_NULL; + struct source_view* source_view = NULL; + struct sphin_brdf* brdf = NULL; + struct sphin_sensor_volume* sensor_volume = NULL; + struct sphin_source_surface* source = NULL; + struct sphin_surface* surface = NULL; + struct sphin_volume* volume = NULL; + res_T res = RES_OK; + + ASSERT(NULL != sphor); + ASSERT(NULL != rng); + + /* Sample a random position from a source in the whole scene */ + res = sample_source_position(sphor, rng, &source_view, &prim, st); + if (RES_OK != res){ goto error; } + res = s3d_primitive_get_attrib (&prim, S3D_POSITION, st, &attrib); + if (RES_OK != res){ goto error; } + d3_set_f3(pos, attrib.value); + + /* Retrieve geometry normal of the sampled primitive */ + res = s3d_primitive_get_attrib (&prim, S3D_GEOMETRY_NORMAL, st, &attrib); + if (RES_OK != res){ goto error; } + d3_normalize(normal, d3_minus(normal, d3_set_f3(normal, attrib.value))); + + /* TODO Sample a wavelength when spectral information available in sphin */ + + res = sphin_config_get_surface + (sphor->config, source_view->sphin_id, &surface); + if (RES_OK != res){ goto error; } + res = sphin_surface_get_source(surface, &source); + if (RES_OK != res){ goto error; } + if (NULL == source){ res = RES_BAD_ARG; goto error; } + + /* Sample a direction according to the source direction distribution */ + res = source_surface_sample_direction(source, rng, &prim, dir); + if (RES_OK != res){ goto error; } + + /* While no absorption takes place and an interface is found */ + for(;;) { + /* Trace a ray in the scene from the sampled primitive in the sampled + * direction and retrive the hit distance */ + f3_set_d3(r, pos); + f3_set_d3(w, dir); + res = s3d_scene_view_trace_ray + (sphor->scene_view, r, w, range, &prim, &hit); + if (RES_OK != res){ goto error; } + + /* If there is no intersection in the sampled direction from the sampled + * position, the photon is lost and it counts zero in the weight. */ + if (S3D_HIT_NONE(&hit)) { + *weight = 0; + break; + } + + /* Retrieve the intercepted triangle and the corresponding interface in the + * sphor structure */ + triangle_id = hit.prim.prim_id; + interface = darray_interface_data_get(&sphor->interfaces)[triangle_id]; + + /* Retrieve the position of the hit and set the new position */ + res = s3d_primitive_get_attrib + (&hit.prim, S3D_POSITION, st, &attrib); + if (RES_OK != res){ goto error; } + d3_set_f3(pos, attrib.value); + /* Update current primitive */ + prim = hit.prim; + + /* Retrieve the side of the intercepted interface corresponding to the + * income direction of the hit */ + res = interface_get_side_id(dir, &hit, &side_id); + if (RES_OK != res){ goto error; } + + /* Get the absorption coefficient of the current volume to compute an + * absorption free length. If the intersected interface does not correspond + * to any volume in the config, the absorption coefficient is supposed to be + * zero and the absorption free length is set to FLT_MAX */ + if (INVALID_ID != interface.volumes[side_id]) { + /* Retrieve the volume we are currently in */ + res = sphin_config_get_volume + (sphor->config, interface.volumes[side_id], &volume); + if (RES_OK != res){ goto error; } + + res = sphin_volume_get_ka(volume, &ka); + if (RES_OK != res){ goto error; } + + /* Sample free path length according to the volume absorption coefficent */ + s = ssp_ran_exp(rng, ka);} + else { + s = FLT_MAX; + } + + /* If the distance between the ray origin and the intersection is bigger + * than the absorption free path length, an absorption takes place (check + * if the volume is a sensor and return the weight accordingly) */ + if (s < hit.distance) { + /* Check if the volume is a sensor */ + res = sphin_volume_get_sensor(volume, &sensor_volume); + /* If the volume is a sensor, compute the weight using the response + * function */ + if (NULL != sensor_volume) { + res = sphin_sensor_volume_get_response_function + (sensor_volume, &response_function); + if (RES_OK != res){ goto error; } + ASSERT(response_function == 1); + *weight = response_function; + } + /* If the volume is not a sensor, the absorbed photon does not count in + * the weight */ + else { + *weight = 0; + } + break; + } + + /* Else, the distance between the ray origin and the intersection is smaller + * than the absorption free path length. The photon intersects a primitive + * in the scene view. */ + + /* Use sphin to retrieve the properties of the intersected surface */ + FOR_EACH(i, 0, interface.surface_count[side_id]) { + res = sphin_config_get_surface + (sphor->config, interface.surfaces[side_id][i], &surface); + if (RES_OK != res){ goto error; } + res = sphin_surface_get_brdf(surface, &brdf); + if (RES_OK != res){ goto error; } + if (NULL != brdf){ break; } + } + + /* If the interface side is a sphin_surface with a BRDF declared, + * verify if a reflection takes place based on the reflectivity of the + * surface */ + if((NULL != brdf) & (interface.surface_count[side_id] > 0)){ + res = sphin_brdf_get_type(brdf, &brdf_type); + if (RES_OK != res){ goto error; } + switch (brdf_type) { + case SPHIN_BRDF_LAMBERT: + res = sphin_brdf_lambertian_get_reflectivity(brdf, &reflectivity); + if (RES_OK != res){ goto error; } + break; + case SPHIN_BRDF_SPECULAR: + res = sphin_brdf_specular_get_reflectivity(brdf, &reflectivity); + if (RES_OK != res){ goto error; } + break; + case SPHIN_BRDF_NONE__: + res = RES_BAD_ARG; + goto error; + default: + FATAL("Unreachable code\n"); + break; + } + + /* Test if the photon was absorbed or reflected */ + s = ssp_rng_canonical(rng); + /* An absorption took place, the weight is zero and the path stops */ + if (s > reflectivity) { + *weight = 0; + break; + } + + /* Else, if a reflection takes place, sample a new direction according to + * the photon arriving direction, geometry normak and surface brdf + * properties */ + res = ran_brdf_reflection_direction(brdf, rng, &hit.prim, dir, sample); + if (RES_OK != res){ goto error; } + d3_set(dir, sample); + } + + /* If the interface side does not have a BRDF (i.e., it defines only a + * sphin_volume or a sphin_surface without a BRDF), the photon + * crosses the interface the path continues */ + } +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +sphor_compute_MVREA + (struct sphor* sphor, + double* out_estim, + double* out_std) +{ + res_T res = RES_OK; + size_t nthreads, samples; + size_t i; /* iterator */ + struct ssp_rng_proxy *rng_proxy = NULL; + struct ssp_rng **rngs = NULL; + double* sum_thread = NULL; + double* sum2_thread = NULL; + double sum = 0, sum2 = 0; + double estim = 0, std = 0; + + samples = sphor->samples; + nthreads = sphor->nthreads; + if (UINT_MAX == nthreads) { /* use all threads available if not in args */ + nthreads = (size_t)omp_get_num_procs(); + } + + /* Création du générateur mandataire RNG_MT19937_64 (Mersenne Twister) */ + res = ssp_rng_proxy_create(NULL, SSP_RNG_MT19937_64, nthreads, &rng_proxy); + if (res != RES_OK) { goto error; } + + /* Allocation des generateurs aleatoire pour chaque processus */ + rngs = mem_calloc(nthreads, sizeof(*rngs)); + if (NULL == rngs) { + res = RES_MEM_ERR; + goto error; + } + /* Le generateur mandataire attribue un generateur par sequence aleatoire + unique que nous stockons dans le tableau rngs (un par processus) */ + FOR_EACH(i, 0, nthreads) { + res = ssp_rng_proxy_create_rng(rng_proxy, i, &rngs[i]); + if (RES_OK != res) { goto error; } + } + omp_set_num_threads((int)nthreads); + sum_thread = mem_calloc(nthreads, sizeof(*sum_thread)); + sum2_thread = mem_calloc(nthreads, sizeof(*sum2_thread)); + /* Realizations loop */ +#pragma omp parallel for schedule(static) + for(i=0; i<samples; i++) { + const int ithread = omp_get_thread_num(); + double w = 0; + res_T res_local = RES_OK; + + if (RES_OK != res) continue; + + res_local = compute_MVREA_realization(sphor, rngs[ithread], &w); + if (RES_OK != res_local) { + res = res_local; + } else { + sum_thread[ithread] += w; + sum2_thread[ithread] += w*w; + } + } + if(res != RES_OK) goto error; + + FOR_EACH(i, 0, nthreads) { + sum += sum_thread[i]; + sum2 += sum2_thread[i]; + } + estim = sum/(double)samples; + std = sqrt + ((sum2/(double)samples - estim*estim)/((double)samples-1)); + + printf("%f +/- %f\n", estim, std); + +exit: + if(sum_thread) mem_rm(sum_thread); + if(sum2_thread) mem_rm(sum2_thread); + /* Liberation de l'ensemble des generateurs */ + if(rng_proxy) ssp_rng_proxy_ref_put(rng_proxy); + if(rngs) { + FOR_EACH(i, 0, nthreads) { + if(rngs[i]) ssp_rng_ref_put(rngs[i]); + } + mem_rm(rngs); + } + *out_estim = estim; + *out_std = std; + return res; +error: + goto exit; +} diff --git a/src/sphor_compute_mvrea.h b/src/sphor_compute_mvrea.h @@ -0,0 +1,39 @@ +/* Copyright (C) 2024-2025 Centre National de la Recherche Scientifique + * Copyright (C) 2024-2025 Clermont Auvergne INP + * Copyright (C) 2024-2025 INSA Lyon + * Copyright (C) 2024-2025 Institut Mines Télécom Albi-Carmaux + * Copyright (C) 2024-2025 Institut National Polytechnique de Toulouse + * Copyright (C) 2024-2025 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2024-2025 PhotonLyX (info@photonlyx.com) + * Copyright (C) 2024-2025 Université de Lorraine + * Copyright (C) 2024-2025 Université Paul Sabatier + * Copyright (C) 2024-2025 Université Toulouse - Jean Jaurès + * + * 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 SPHOR_COMPUTE_MVREA_H +#define SPHOR_COMPUTE_MVREA_H + +#include <rsys/rsys.h> + +/* Forward declarations */ +struct sphor; +struct sphor_create_args; + +extern LOCAL_SYM res_T +sphor_compute_MVREA + (struct sphor* sphor, + double *out_estim, + double *out_std); + +#endif /* SPHOR_COMPUTE_MVREA_H */ diff --git a/src/sphor_interface.c b/src/sphor_interface.c @@ -0,0 +1,61 @@ +/* Copyright (C) 2024-2025 Centre National de la Recherche Scientifique + * Copyright (C) 2024-2025 Clermont Auvergne INP + * Copyright (C) 2024-2025 INSA Lyon + * Copyright (C) 2024-2025 Institut Mines Télécom Albi-Carmaux + * Copyright (C) 2024-2025 Institut National Polytechnique de Toulouse + * Copyright (C) 2024-2025 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2024-2025 PhotonLyX (info@photonlyx.com) + * Copyright (C) 2024-2025 Université de Lorraine + * Copyright (C) 2024-2025 Université Paul Sabatier + * Copyright (C) 2024-2025 Université Toulouse - Jean Jaurès + * + * 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 "sphor_interface.h" + +#include <rsys/double3.h> +#include <star/s3d.h> +#include <star/sphin.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ + + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +interface_get_side_id + (const double dir[3], /* Direction of arrival of the hit into the geometry */ + const struct s3d_hit* hit, + enum sphin_side* side) +{ + double normal[3] = {0}; + res_T res = RES_OK; + + ASSERT(NULL != dir); + ASSERT(NULL != side); + + /* hit->normal: unormalized hit normal that uses the left hand convention */ + d3_normalize(normal, d3_set_f3(normal, hit->normal)); + + /* Test the angle between the two vectors dir and normal using the dot + * product. If it is negative, the S3D normal points to the hemisphere of the + * arriving hit, which corresponds to the BACK side in the sphin convention. + * Otherwise, it corresponds to the front side. */ + *side = (d3_dot(dir, normal) < 0 ) ? SPHIN_SIDE_BACK : SPHIN_SIDE_FRONT; + + return res; +} diff --git a/src/sphor_interface.h b/src/sphor_interface.h @@ -25,7 +25,10 @@ #ifndef SPHOR_INTERFACE_H #define SPHOR_INTERFACE_H +#include "sphor.h" + #include <rsys/dynamic_array.h> +#include <star/sphin.h> /* Maximum number of surfaces a single triangle can be part of (per side). * We use a constant to avoid dynamic memory allocation. @@ -34,6 +37,9 @@ #define MAX_SURFACE_COUNT 4 #define INVALID_ID SIZE_MAX +/* Forward declarations */ +struct s3d_hit; + struct interface { /* Interface side; FRONT and BACK */ size_t volumes[2/*#sides*/]; @@ -51,4 +57,10 @@ static const struct interface INTERFACE_NULL = INTERFACE_NULL__; #define DARRAY_DATA struct interface #include <rsys/dynamic_array.h> +extern LOCAL_SYM res_T +interface_get_side_id + (const double dir[3], + const struct s3d_hit* hit, + enum sphin_side* side); + #endif /* SPHOR_INTERFACE_H */ diff --git a/src/sphor_ran_brdf.c b/src/sphor_ran_brdf.c @@ -0,0 +1,99 @@ +/* Copyright (C) 2024-2025 Centre National de la Recherche Scientifique + * Copyright (C) 2024-2025 Clermont Auvergne INP + * Copyright (C) 2024-2025 INSA Lyon + * Copyright (C) 2024-2025 Institut Mines Télécom Albi-Carmaux + * Copyright (C) 2024-2025 Institut National Polytechnique de Toulouse + * Copyright (C) 2024-2025 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2024-2025 PhotonLyX (info@photonlyx.com) + * Copyright (C) 2024-2025 Université de Lorraine + * Copyright (C) 2024-2025 Université Paul Sabatier + * Copyright (C) 2024-2025 Université Toulouse - Jean Jaurès + * + * 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 "sphor_sources.h" +#include "sphor_ran_brdf.h" + +#include <star/sphin.h> +#include <star/ssp.h> +#include <star/suniq.h> +#include <rsys/dynamic_array_double.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +ran_brdf_reflection_direction + (struct sphin_brdf* brdf, + struct ssp_rng* rng, + struct s3d_primitive* prim, + double initial_dir[3], + double final_dir[3]) +{ + struct s3d_attrib attrib; + enum sphin_brdf_type brdf_type = SPHIN_BRDF_NONE__; + float st[2] = {0}; + double normal[3] = {0}; + double new_dir[3] = {0}; + double dot = 0; + double invert_normal = 1.f; + res_T res = RES_OK; + + res = s3d_primitive_get_attrib + (prim, S3D_GEOMETRY_NORMAL, st, &attrib); + if (RES_OK != res){ goto error; } + + d3_set_f3(normal, attrib.value); + /* Ensure the geometry normal points into the same hemisphere as the incoming + * direction. If the dot product between the normal and the incoming (initial) + * direction is positive, then the normal points in the same direction as the + * incoming ray, which is incorrect for reflection. Invert the normal in that + * case. */ + invert_normal = (d3_dot(normal, initial_dir) > 0 ) ?-1.f : 1.f; + + res = sphin_brdf_get_type(brdf, &brdf_type); + if (RES_OK != res){ goto error; } + + d3_normalize(normal, d3_muld(normal, normal, invert_normal)); + + switch (brdf_type) { + case SPHIN_BRDF_LAMBERT: + /* Sample a cosine-weighted direction over the hemisphere oriented + * by the surface normal. */ + ssp_ran_hemisphere_cos(rng, normal, new_dir, NULL); + break; + case SPHIN_BRDF_SPECULAR: + /* Use the formula to compute the reflection: + * reflected = initial_dir − 2 (initial_dir dot normal) normal */ + dot = d3_dot(normal, initial_dir); + d3_add(new_dir, initial_dir, d3_muld(new_dir, normal, -2*dot)); + break; + case SPHIN_BRDF_NONE__: + res = RES_BAD_ARG; + goto error; + default: + FATAL("Unreachable code\n"); + break; + } + d3_normalize(final_dir, new_dir); + +exit: + return res; +error: + goto exit; +} diff --git a/src/sphor_ran_brdf.h b/src/sphor_ran_brdf.h @@ -0,0 +1,42 @@ +/* Copyright (C) 2024-2025 Centre National de la Recherche Scientifique + * Copyright (C) 2024-2025 Clermont Auvergne INP + * Copyright (C) 2024-2025 INSA Lyon + * Copyright (C) 2024-2025 Institut Mines Télécom Albi-Carmaux + * Copyright (C) 2024-2025 Institut National Polytechnique de Toulouse + * Copyright (C) 2024-2025 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2024-2025 PhotonLyX (info@photonlyx.com) + * Copyright (C) 2024-2025 Université de Lorraine + * Copyright (C) 2024-2025 Université Paul Sabatier + * Copyright (C) 2024-2025 Université Toulouse - Jean Jaurès + * + * 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 SPHOR_RAN_BRDF_H +#define SPHOR_RAN_BRDF_H + +#include <rsys/rsys.h> + +/* Forward declarations */ +struct sphin_brdf; +struct ssp_rng; +struct s3d_primitive; + +extern LOCAL_SYM res_T +ran_brdf_reflection_direction + (struct sphin_brdf* brdf, + struct ssp_rng* rng, + struct s3d_primitive* prim, + double initial_dir[3], + double final_dir[3]); + +#endif /* SPHOR_RAN_BRDF */ diff --git a/src/sphor_ran_geometry.c b/src/sphor_ran_geometry.c @@ -0,0 +1,102 @@ +/* Copyright (C) 2024-2025 Centre National de la Recherche Scientifique + * Copyright (C) 2024-2025 Clermont Auvergne INP + * Copyright (C) 2024-2025 INSA Lyon + * Copyright (C) 2024-2025 Institut Mines Télécom Albi-Carmaux + * Copyright (C) 2024-2025 Institut National Polytechnique de Toulouse + * Copyright (C) 2024-2025 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2024-2025 PhotonLyX (info@photonlyx.com) + * Copyright (C) 2024-2025 Université de Lorraine + * Copyright (C) 2024-2025 Université Paul Sabatier + * Copyright (C) 2024-2025 Université Toulouse - Jean Jaurès + * + * 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 "sphor_ran_geometry.h" + +#include <star/s3d.h> +#include <star/ssp.h> + +/******************************************************************************* + * Local functions + ******************************************************************************/ +/* Compute the rotation of the vector 'vec' by an angle of 'theta' radians in + * the axis 'axis' using the Rodrigues' rotation formula: + * https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula */ +res_T +rotate_vector + (double* vrot, + double* vec, + double* axis, + double theta) +{ + res_T res = RES_OK; + + double cos_theta = cos(theta); + double sin_theta = sin(theta); + double dot; + double cross[3]; + double tmp1[3]; + double tmp2[3]; + double tmp3[3]; + + dot = d3_dot(axis, vec); + d3_cross(cross, axis, vec); + + /* Compute the three terms of the Rodrigues' rotation formula */ + d3_muld(tmp1, axis, (1-cos_theta) * dot); + d3_muld(tmp2, cross, sin_theta); + d3_muld(tmp3, vec, cos_theta); + + /* Sum the three terms of the formula */ + d3_add(vrot, tmp1, tmp2); + d3_add(vrot, vrot, tmp3); + + return res; +} + +res_T +sample_surface_position + (struct sphor* sphor, + struct ssp_rng* rng, + struct s3d_scene_view* view, + struct s3d_primitive* primitive, + float st[2]) +{ + float u = 0; + float v = 0; + float w = 0; + float uv[2]; + res_T res = RES_OK; + + ASSERT(NULL != rng); + ASSERT(NULL != sphor); + ASSERT(NULL != view); + + (void)sphor; + + u = (float)ssp_rng_canonical(rng); + v = (float)ssp_rng_canonical(rng); + w = (float)ssp_rng_canonical(rng); + + res = s3d_scene_view_sample(view, u, v, w, primitive, uv); + if (RES_OK != res) { goto error; } + + st[0] = uv[0]; + st[1] = uv[1]; + +exit: + return res; +error: + goto exit; +} diff --git a/src/sphor_ran_geometry.h b/src/sphor_ran_geometry.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2024-2025 Centre National de la Recherche Scientifique + * Copyright (C) 2024-2025 Clermont Auvergne INP + * Copyright (C) 2024-2025 INSA Lyon + * Copyright (C) 2024-2025 Institut Mines Télécom Albi-Carmaux + * Copyright (C) 2024-2025 Institut National Polytechnique de Toulouse + * Copyright (C) 2024-2025 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2024-2025 PhotonLyX (info@photonlyx.com) + * Copyright (C) 2024-2025 Université de Lorraine + * Copyright (C) 2024-2025 Université Paul Sabatier + * Copyright (C) 2024-2025 Université Toulouse - Jean Jaurès + * + * 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 SPHOR_RAN_GEOMETRY_H +#define SPHOR_RAN_GEOMETRY_H + +#include <rsys/rsys.h> + +/* Forward declarations */ +struct sphor; +struct ssp_rng; +struct s3d_scene_view; +struct s3d_primitive; + +extern LOCAL_SYM res_T +rotate_vector + (double* vrot, + double* vec, + double* axis, + double theta); + +extern LOCAL_SYM res_T +sample_surface_position + (struct sphor* sphor, + struct ssp_rng* rng, + struct s3d_scene_view* view, + struct s3d_primitive* primitive, + float st[2]); + +#endif /* SPHOR_RAN_GEOMETRY_H */ diff --git a/src/sphor_ran_source.c b/src/sphor_ran_source.c @@ -0,0 +1,133 @@ +/* Copyright (C) 2024-2025 Centre National de la Recherche Scientifique + * Copyright (C) 2024-2025 Clermont Auvergne INP + * Copyright (C) 2024-2025 INSA Lyon + * Copyright (C) 2024-2025 Institut Mines Télécom Albi-Carmaux + * Copyright (C) 2024-2025 Institut National Polytechnique de Toulouse + * Copyright (C) 2024-2025 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2024-2025 PhotonLyX (info@photonlyx.com) + * Copyright (C) 2024-2025 Université de Lorraine + * Copyright (C) 2024-2025 Université Paul Sabatier + * Copyright (C) 2024-2025 Université Toulouse - Jean Jaurès + * + * 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 "sphor_c.h" +#include "sphor_sources.h" +#include "sphor_ran_source.h" +#include "sphor_ran_geometry.h" + +#include <star/sphin.h> +#include <star/ssp.h> +#include <star/suniq.h> +#include <rsys/dynamic_array_double.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ + +/******************************************************************************* + * Local functions + ******************************************************************************/ +res_T +source_surface_sample_direction + (struct sphin_source_surface* source, + struct ssp_rng* rng, + struct s3d_primitive* prim, + double dir[3]) +{ + struct s3d_attrib attrib; + struct sphin_source_surface_direction_distribution src_dir_dist + = SPHIN_SOURCE_SURFACE_DIRECTION_DISTRIBUTION_NULL; + double normal[3] = {0}; + double sample[3] = {0}; + float st[2] = {0}; + res_T res = RES_OK; + + /* Sample a direction according to the source direction distribution */ + res = sphin_source_surface_get_direction_distribution(source, &src_dir_dist); + if (RES_OK != res){ goto error; } + + res = s3d_primitive_get_attrib + (prim, S3D_GEOMETRY_NORMAL, st, &attrib); + if (RES_OK != res){ goto error; } + d3_minus(normal, d3_set_f3(normal, attrib.value)); + + switch (src_dir_dist.type) { + case SPHIN_SOURCE_DIRECTION_COLLIM: + d3_set(sample, normal); + break; + case SPHIN_SOURCE_DIRECTION_COS_POW_N: + /* TODO Sample direction with collimation degree */ + break; + case SPHIN_SOURCE_DIRECTION_ISOTROPIC: + ssp_ran_hemisphere_cos(rng, normal, sample, NULL); + break; + case SPHIN_SOURCE_DIRECTION_NONE__: + res = RES_BAD_ARG; + goto error; + default: FATAL("Unreachable code\n"); break; + } + d3_normalize(dir, sample); +exit: + return res; +error: + goto exit; +} + +res_T +sample_source_view + (struct sphor* sphor, + struct ssp_rng* rng, + struct source_view** source_view) +{ + struct source_view* source_views = NULL; + size_t isource_view = 0; + + ASSERT(NULL != sphor); + ASSERT(NULL != rng); + + isource_view = ssp_ranst_discrete_get(rng, sphor->source_distrib_power); + + source_views = darray_source_view_data_get(&sphor->source_views); + *source_view = &source_views[isource_view]; + + return RES_OK; +} + +res_T +sample_source_position + (struct sphor* sphor, + struct ssp_rng* rng, + struct source_view** source, + struct s3d_primitive* primitive, + float st[2]) +{ + struct source_view* source_to_sample = NULL; + res_T res = RES_OK; + + ASSERT(NULL != sphor); + ASSERT(NULL != rng); + + res = sample_source_view(sphor, rng, &source_to_sample); + if (RES_OK != res) { goto error; } + + res = sample_surface_position(sphor, rng, source_to_sample->view, primitive, st); + if (RES_OK != res) { goto error; } + + *source = source_to_sample; +exit: + return res; +error: + goto exit; +} diff --git a/src/sphor_ran_source.h b/src/sphor_ran_source.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2024-2025 Centre National de la Recherche Scientifique + * Copyright (C) 2024-2025 Clermont Auvergne INP + * Copyright (C) 2024-2025 INSA Lyon + * Copyright (C) 2024-2025 Institut Mines Télécom Albi-Carmaux + * Copyright (C) 2024-2025 Institut National Polytechnique de Toulouse + * Copyright (C) 2024-2025 |Méso|Star> (contact@meso-star.com) + * Copyright (C) 2024-2025 PhotonLyX (info@photonlyx.com) + * Copyright (C) 2024-2025 Université de Lorraine + * Copyright (C) 2024-2025 Université Paul Sabatier + * Copyright (C) 2024-2025 Université Toulouse - Jean Jaurès + * + * 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 SPHOR_RAN_SOURCES_H +#define SPHOR_RAN_SOURCES_H + +#include <rsys/rsys.h> + +/* Forward declarations */ +struct sphin_source_surface; +struct ssp_rng; +struct s3d_primitive; + +extern LOCAL_SYM res_T +source_surface_sample_direction + (struct sphin_source_surface* source, + struct ssp_rng* rng, + struct s3d_primitive* prim, + double dir[3]); + +/* Sample a source view considering the discrete distribution of power of all + * sources in the config */ +extern LOCAL_SYM res_T +sample_source_view + (struct sphor* sphor, + struct ssp_rng* rng, + struct source_view** source_view); + +/* Sample a source considering the discrete distribution of power and then + * sample a position uniformly in this source. */ +extern LOCAL_SYM res_T +sample_source_position + (struct sphor* sphor, + struct ssp_rng* rng, + struct source_view** source, + struct s3d_primitive* primitive, + float st[2]); + +#endif /* SPHOR_RAN_SOURCES_H */ diff --git a/src/sphor_sources.c b/src/sphor_sources.c @@ -160,7 +160,6 @@ register_source_view ASSERT(NULL != sphor); ASSERT(NULL != args); - ASSERT(NULL != surface); source_init(sphor->allocator, &source); @@ -279,85 +278,3 @@ exit: error: goto exit; } - -res_T -sample_source_view - (struct sphor* sphor, - struct ssp_rng* rng, - const struct source_view** source_view) -{ - const struct source_view* source_views = NULL; - size_t isource_view = 0; - - ASSERT(NULL != sphor); - ASSERT(NULL != rng); - - isource_view = ssp_ranst_discrete_get(rng, sphor->source_distrib_power); - - source_views = darray_source_view_cdata_get(&sphor->source_views); - *source_view = &source_views[isource_view]; - - return RES_OK; -} - -res_T -sample_source_position - (struct sphor* sphor, - struct ssp_rng* rng, - const struct source_view* source_view, - struct s3d_primitive* primitive, - double st[2]) -{ - float u = 0; - float v = 0; - float w = 0; - float uv[2]; - res_T res = RES_OK; - - ASSERT(NULL != rng); - ASSERT(NULL != sphor); - ASSERT(NULL != source_view); - - (void)sphor; - - u = (float)ssp_rng_canonical(rng); - v = (float)ssp_rng_canonical(rng); - w = (float)ssp_rng_canonical(rng); - - res = s3d_scene_view_sample(source_view->view, u, v, w, primitive, uv); - if (RES_OK != res) { goto error; } - - st[0] = uv[0]; - st[1] = uv[1]; - -exit: - return res; -error: - goto exit; -} - -res_T -sample_source_view_and_position - (struct sphor* sphor, - struct ssp_rng* rng, - struct s3d_primitive* primitive, - double st[2]) -{ - const struct source_view* source_to_sample = NULL; - res_T res = RES_OK; - - ASSERT(NULL != sphor); - ASSERT(NULL != rng); - - res = sample_source_view(sphor, rng, &source_to_sample); - if (RES_OK != res) { goto error; } - - res = sample_source_position(sphor, rng, source_to_sample, primitive, st); - if (RES_OK != res) { goto error; } - -exit: - return res; -error: - goto exit; - -} diff --git a/src/sphor_sources.h b/src/sphor_sources.h @@ -35,7 +35,7 @@ struct ssp_rng; struct source_view { struct s3d_scene_view* view; /* View of the source */ - size_t sphin_id; /* Source identifier in star-phor-input */ + size_t sphin_id; /* Surface identifier in star-phor-input */ }; #define SOURCE_VIEW_NULL__ {NULL, 0} @@ -116,30 +116,4 @@ setup_source_distrib_power (struct sphor* sphor, const struct sphor_create_args* args); -/* Sample a source view considering the discrete distribution of power of all - * sources in the config */ -extern LOCAL_SYM res_T -sample_source_view - (struct sphor* sphor, - struct ssp_rng* rng, - const struct source_view** source_view); - -/* Sample a position unifomly in a given source */ -extern LOCAL_SYM res_T -sample_source_position - (struct sphor* sphor, - struct ssp_rng* rng, - const struct source_view* source_view, - struct s3d_primitive* primitive, - double st[2]); - -/* Sample a source considering the discrete distribution of power and then - * sample a position uniformly in this source. */ -extern LOCAL_SYM res_T -sample_source_view_and_position - (struct sphor* sphor, - struct ssp_rng* rng, - struct s3d_primitive* primitive, - double st[2]); - #endif /* SPHOR_SOURCES_H */