star-phor

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

sphor_ran_bsdf.c (18789B)


      1 /* Copyright (C) 2024-2026 Centre National de la Recherche Scientifique
      2  * Copyright (C) 2024-2026 Clermont Auvergne INP
      3  * Copyright (C) 2024-2026 INSA Lyon
      4  * Copyright (C) 2024-2026 Institut Mines Télécom Albi-Carmaux
      5  * Copyright (C) 2024-2026 Institut National Polytechnique de Toulouse
      6  * Copyright (C) 2024-2026 |Méso|Star> (contact@meso-star.com)
      7  * Copyright (C) 2024-2026 PhotonLyX (info@photonlyx.com)
      8  * Copyright (C) 2024-2026 Université de Lorraine
      9  * Copyright (C) 2024-2026 Université Paul Sabatier
     10  * Copyright (C) 2024-2026 Université Toulouse - Jean Jaurès
     11  *
     12  * This program is free software: you can redistribute it and/or modify
     13  * it under the terms of the GNU General Public License as published by
     14  * the Free Software Foundation, either version 3 of the License, or
     15  * (at your option) any later version.
     16  *
     17  * This program is distributed in the hope that it will be useful,
     18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     20  * GNU General Public License for more details.
     21  *
     22  * You should have received a copy of the GNU General Public License
     23  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     24 
     25 #include "sphor_c.h"
     26 #include "sphor_ran_bsdf.h"
     27 #include "sphor_interface.h"
     28 
     29 #include <rsys/rsys.h>
     30 #include <star/ssf.h>
     31 
     32 /*******************************************************************************
     33  * Internal BSDFs
     34  ******************************************************************************/
     35 
     36 /* BTDF LAMBERTIAN */
     37 struct lambertian_transmission {
     38   double transmissivity;
     39 };
     40 
     41 static double
     42 lambertian_transmission_eval
     43   (void* data,
     44    const double wo[3],
     45    const double N[3],
     46    const double wi[3])
     47 {
     48   struct lambertian_transmission* btdf = data;
     49 
     50   ASSERT(NULL != data);
     51   ASSERT(NULL != N);
     52   ASSERT(NULL != wi);
     53   ASSERT(d3_is_normalized(N) && d3_is_normalized(wi));
     54   ASSERT(d3_dot(wi, N) < 0 && d3_dot(wo, N) > 0);
     55   (void)wo, (void)N, (void)wi;
     56 
     57   return btdf->transmissivity/ PI;
     58 }
     59 
     60 static double
     61 lambertian_transmission_sample
     62   (void* data,
     63    struct ssp_rng* rng,
     64    const double wo[3],
     65    const double N[3],
     66    double wi[3],
     67    int* type,
     68    double* pdf)
     69 {
     70   double sample[3];
     71 
     72   ASSERT(NULL != data);
     73   ASSERT(NULL != rng);
     74   ASSERT(NULL != N);
     75   ASSERT(NULL != wi);
     76   ASSERT(d3_is_normalized(wo) && d3_is_normalized(N) && d3_dot(wo, N) > 0);
     77   (void)wo;
     78 
     79   ssp_ran_hemisphere_cos(rng, N, sample, pdf);
     80   d3_muld(wi, sample, -1);
     81   if (type) *type = SSF_TRANSMISSION | SSF_DIFFUSE;
     82   return ((struct lambertian_transmission*)data)->transmissivity;
     83 }
     84 
     85 static double
     86 lambertian_transmission_pdf
     87   (void* data,
     88    const double wo[3],
     89    const double N[3],
     90    const double wi[3])
     91 {
     92   double cos_wi_N;
     93   ASSERT(NULL != data);
     94   ASSERT(NULL != N);
     95   ASSERT(NULL != wi);
     96   ASSERT(d3_is_normalized(N) && d3_is_normalized(wi));
     97   (void)data, (void)wo;
     98 
     99   cos_wi_N = d3_dot(wi, N);
    100   return cos_wi_N < 0.0 ? -cos_wi_N / PI : 0.0;
    101 }
    102 
    103 static res_T
    104 lambertian_transmission_setup
    105   (struct ssf_bsdf* bsdf,
    106    const double transmissivity)
    107 {
    108   void* ptr = NULL;
    109 
    110   ASSERT(NULL != bsdf);
    111   ASSERT(0 <= transmissivity && transmissivity <=1);
    112 
    113   ssf_bsdf_get_data(bsdf, &ptr);
    114   struct lambertian_transmission* data = ptr;
    115   data->transmissivity = transmissivity;
    116 
    117   return RES_OK;
    118 }
    119 
    120 const struct ssf_bsdf_type lambertian_transmission = {
    121   NULL,
    122   NULL,
    123   lambertian_transmission_sample,
    124   lambertian_transmission_eval,
    125   lambertian_transmission_pdf,
    126   sizeof(struct lambertian_transmission),
    127   ALIGNOF(struct lambertian_transmission)
    128 };
    129 
    130 /* BTDF KEEP_CURRENT_DIR */
    131 struct keep_current_dir_transmission {
    132   struct ssf_fresnel* fresnel;
    133 };
    134 
    135 static void
    136 keep_current_dir_transmission_release
    137   (void* data)
    138 {
    139   struct keep_current_dir_transmission* btdf = data;
    140   ASSERT(data);
    141   if (btdf->fresnel) SSF(fresnel_ref_put(btdf->fresnel));
    142 }
    143 
    144 static double
    145 keep_current_dir_transmission_sample
    146   (void* data,
    147    struct ssp_rng* rng,
    148    const double wo[3],
    149    const double N[3],
    150    double wi[3],
    151    int* type,
    152    double* pdf)
    153 {
    154   struct keep_current_dir_transmission* btdf = data;
    155   double cos_wo_N;
    156 
    157   ASSERT(NULL != data);
    158   ASSERT(NULL != rng);
    159   ASSERT(NULL != N);
    160   ASSERT(NULL != wi);
    161   ASSERT(d3_is_normalized(wo) && d3_is_normalized(N) && d3_dot(wo, N) > 0);
    162   (void)rng;
    163 
    164   /* In ssf convention, wo points outwards the surface */
    165   d3_minus(wi, wo);
    166   if (pdf) *pdf = INF;
    167   if (type) *type = SSF_TRANSMISSION;
    168 
    169   cos_wo_N = d3_dot(wo, N);
    170   return 1 - ssf_fresnel_eval(btdf->fresnel, cos_wo_N);
    171 }
    172 
    173 static double
    174 keep_current_dir_transmission_eval
    175   (void* data,
    176    const double wo[3],
    177    const double N[3],
    178    const double wi[3])
    179 {
    180   (void)data, (void)wi, (void)N, (void)wo;
    181   return 0.0;
    182 }
    183 
    184 static double
    185 keep_current_dir_transmission_pdf
    186   (void* data,
    187    const double wo[3],
    188    const double N[3],
    189    const double wi[3])
    190 {
    191   (void)data, (void)wi, (void)N, (void)wo;
    192   return 0.0;
    193 }
    194 
    195 static res_T
    196 keep_current_dir_transmission_setup
    197   (struct ssf_bsdf* bsdf,
    198    struct ssf_fresnel* fresnel)
    199 {
    200   void* ptr = NULL;
    201   res_T res = RES_OK;
    202 
    203   ASSERT(NULL != bsdf);
    204   ASSERT(NULL != fresnel);
    205 
    206   ssf_bsdf_get_data(bsdf, &ptr);
    207   struct keep_current_dir_transmission* data = ptr;
    208 
    209   if (data->fresnel != fresnel) {
    210     if (NULL != data->fresnel) {
    211       res = ssf_fresnel_ref_put(data->fresnel);
    212       if (RES_OK != res) { return res; }
    213     }
    214     res = ssf_fresnel_ref_get(fresnel);
    215     if (RES_OK != res) { return res; }
    216 
    217     data->fresnel = fresnel;
    218   }
    219 
    220   return RES_OK;
    221 }
    222 
    223 const struct ssf_bsdf_type keep_current_dir_transmission = {
    224   NULL,
    225   keep_current_dir_transmission_release,
    226   keep_current_dir_transmission_sample,
    227   keep_current_dir_transmission_eval,
    228   keep_current_dir_transmission_pdf,
    229   sizeof(struct keep_current_dir_transmission),
    230   ALIGNOF(struct keep_current_dir_transmission)
    231 };
    232 
    233 /* BTDF SNELL DIELECTRIC */
    234 struct snell_dielectric_transmission {
    235   struct ssf_fresnel* fresnel;
    236   double eta_i; /* Refractive index of the incoming medium */
    237   double eta_t; /* Refractive index of the transmissive medium */
    238 };
    239 
    240 /* Refract the vect V wrt the normal N using the relative refractive index eta.
    241  * Eta is the refraction index of the outside medium (where N points into)
    242  * devided by the refraction index of the inside medium. By convention N and V
    243  * points on the same side of the surface. */
    244 static INLINE void
    245 refract(double res[3], const double V[3], const double N[3], const double eta)
    246 {
    247   double tmp0[3];
    248   double tmp1[3];
    249   double cos_theta_i;
    250   double cos_theta_t;
    251   double sin2_theta_i;
    252   double sin2_theta_t;
    253 
    254   ASSERT(res && V && N);
    255   ASSERT(d3_is_normalized(V) && d3_is_normalized(N));
    256   cos_theta_i = d3_dot(V, N);
    257   sin2_theta_i = MMAX(0, 1.0 - cos_theta_i*cos_theta_i);
    258   sin2_theta_t = eta * eta * sin2_theta_i;
    259   cos_theta_t = sqrt(1 - sin2_theta_t);
    260 
    261   d3_muld(tmp0, V, eta);
    262   d3_muld(tmp1, N, eta * cos_theta_i - cos_theta_t);
    263   d3_sub(res, tmp1, tmp0);
    264 }
    265 
    266 static void
    267 snell_dielectric_transmission_release
    268   (void* data)
    269 {
    270   struct keep_current_dir_transmission* btdf = data;
    271   ASSERT(data);
    272   if (btdf->fresnel) SSF(fresnel_ref_put(btdf->fresnel));
    273 }
    274 
    275 static double
    276 snell_dielectric_transmission_sample
    277   (void* data,
    278    struct ssp_rng* rng,
    279    const double wo[3],
    280    const double N[3],
    281    double wi[3],
    282    int* type,
    283    double* pdf)
    284 {
    285   struct snell_dielectric_transmission* btdf = data;
    286   double wt[3];
    287   double cos_wo_N;
    288   double eta; /* Ratio of eta_i / eta_t */
    289 
    290   ASSERT(NULL != btdf);
    291   ASSERT(NULL != data);
    292   ASSERT(NULL != rng);
    293   ASSERT(NULL != N);
    294   ASSERT(NULL != wi);
    295   ASSERT(d3_is_normalized(wo) && d3_is_normalized(N));
    296   ASSERT(d3_dot(wo, N) > -1.e-6);
    297   (void)rng;
    298 
    299   eta = btdf->eta_i / btdf->eta_t;
    300   refract(wt, wo, N, eta);
    301 
    302   cos_wo_N = MMAX(0.0, d3_dot(wo, N));
    303 
    304   if(pdf) *pdf = INF;
    305   d3_set(wi, wt);
    306   if(type) *type = SSF_SPECULAR | SSF_TRANSMISSION;
    307   return 1 - ssf_fresnel_eval(btdf->fresnel, cos_wo_N);
    308 }
    309 
    310 static double
    311 snell_dielectric_transmission_eval
    312   (void* bsdf,
    313    const double wo[3],
    314    const double N[3],
    315    const double wi[3])
    316 {
    317    (void)bsdf, (void)wo, (void)N, (void)wi;
    318    return 0.0;
    319 }
    320 
    321 static double
    322 snell_dielectric_transmission_pdf
    323   (void* bsdf,
    324    const double wo[3],
    325    const double N[3],
    326    const double wi[3])
    327 {
    328   (void)bsdf, (void)wo, (void)N, (void)wi;
    329   return 0.0;
    330 }
    331 
    332 static res_T
    333 snell_dielectric_transmission_setup
    334   (struct ssf_bsdf* bsdf,
    335    struct ssf_fresnel* fresnel,
    336    double eta_i,
    337    double eta_t)
    338 {
    339   void* ptr = NULL;
    340   res_T res = RES_OK;
    341 
    342   ASSERT(NULL != bsdf);
    343 
    344   ssf_bsdf_get_data(bsdf, &ptr);
    345   struct snell_dielectric_transmission* data = ptr;
    346 
    347   if (data->fresnel != fresnel) {
    348     if (NULL != data->fresnel) {
    349       res = ssf_fresnel_ref_put(data->fresnel);
    350       if (RES_OK != res) { return res; }
    351     }
    352     res = ssf_fresnel_ref_get(fresnel);
    353     if (RES_OK != res) { return res; }
    354 
    355     data->fresnel = fresnel;
    356   }
    357   data->eta_i = eta_i;
    358   data->eta_t = eta_t;
    359 
    360   return RES_OK;
    361 }
    362 
    363 const struct ssf_bsdf_type snell_dielectric_transmission = {
    364   NULL,
    365   snell_dielectric_transmission_release,
    366   snell_dielectric_transmission_sample,
    367   snell_dielectric_transmission_eval,
    368   snell_dielectric_transmission_pdf,
    369   sizeof(struct snell_dielectric_transmission),
    370   ALIGNOF(struct snell_dielectric_transmission)
    371 };
    372 
    373 /*******************************************************************************
    374  * Helper functions
    375  ******************************************************************************/
    376 static res_T
    377 setup_bsdf_reflection
    378   (struct sphor* sphor,
    379    struct ray* ray,
    380    struct intersection* intersection,
    381    double wavelength,
    382    double reflectivity,
    383    struct ssf_bsdf** out_bsdf)
    384 {
    385   struct primitive* primitive = &intersection->position.primitive;
    386   struct sphin_brdf* brdf = NULL;
    387   struct ssf_bsdf* bsdf = NULL;
    388   struct ssf_fresnel* fresnel = NULL;
    389   enum sphin_brdf_direction_distribution brdf_dir = SPHIN_BRDF_DIRECTION_NONE__;
    390 
    391   res_T res = RES_OK;
    392 
    393   ASSERT(NULL != sphor);
    394   ASSERT(NULL != ray);
    395   ASSERT(NULL != intersection);
    396 
    397   (void)ray;
    398   (void)wavelength;
    399 
    400   primitive = (struct primitive*)&intersection->position.primitive;
    401 
    402   res = primitive_get_brdf(sphor, primitive, &brdf);
    403   if (RES_OK != res) { goto error; }
    404   if (NULL == brdf) { res = RES_BAD_ARG; goto error; }
    405 
    406   res = sphin_brdf_get_direction_distribution(brdf, &brdf_dir);
    407   if (RES_OK != res) { goto error; }
    408 
    409   switch (brdf_dir) {
    410     case SPHIN_BRDF_DIRECTION_LAMBERT:
    411 
    412       res = ssf_bsdf_create
    413         (sphor->allocator, &ssf_lambertian_reflection, &bsdf);
    414       if (RES_OK != res) { goto error; }
    415 
    416       res = ssf_lambertian_reflection_setup(bsdf, reflectivity);
    417       if (RES_OK != res) { goto error; }
    418 
    419       break;
    420 
    421     case SPHIN_BRDF_DIRECTION_SPECULAR:
    422       res = ssf_bsdf_create
    423         (sphor->allocator, &ssf_specular_reflection, &bsdf);
    424       if (RES_OK != res) { goto error; }
    425 
    426       res = ssf_fresnel_create
    427         (sphor->allocator, &ssf_fresnel_constant, &fresnel);
    428       if (RES_OK != res) { goto error; }
    429 
    430       res = ssf_fresnel_constant_setup(fresnel, reflectivity);
    431       if (RES_OK != res) { goto error; }
    432 
    433       res = ssf_specular_reflection_setup(bsdf, fresnel);
    434       if (RES_OK != res) { goto error; }
    435 
    436       break;
    437 
    438     case SPHIN_BRDF_DIRECTION_NONE__:
    439       res = RES_BAD_ARG;
    440       goto error;
    441 
    442     default:
    443       FATAL("Unreachable code\n");
    444       break;
    445   }
    446 
    447 exit:
    448   if (NULL != fresnel) { SSF(fresnel_ref_put(fresnel)); }
    449   *out_bsdf = bsdf;
    450 error:
    451   return res;
    452   if (NULL != bsdf) { SSF(bsdf_ref_put(bsdf)); }
    453   goto exit;
    454 }
    455 
    456 static res_T
    457 setup_bsdf_transmission
    458   (struct sphor* sphor,
    459    struct ray* ray,
    460    struct intersection* intersection,
    461    double wavelength,
    462    double transmissivity,
    463    struct ssf_bsdf** out_bsdf)
    464 {
    465   double eta_i = 0, k_i = 0;
    466   double eta_t = 0, k_t = 0;
    467   struct primitive* primitive = &intersection->position.primitive;
    468   struct sphin_btdf* btdf = NULL;
    469   struct ssf_bsdf* bsdf = NULL;
    470   struct ssf_fresnel* fresnel = NULL;
    471   enum sphin_btdf_direction_distribution btdf_dir = SPHIN_BTDF_DIRECTION_NONE__;
    472 
    473   res_T res = RES_OK;
    474 
    475   ASSERT(NULL != sphor);
    476   ASSERT(NULL != ray);
    477   ASSERT(NULL != intersection);
    478 
    479   (void)ray;
    480   (void)wavelength;
    481 
    482   primitive = (struct primitive*)&intersection->position.primitive;
    483 
    484   res = primitive_get_btdf(sphor, primitive, &btdf);
    485   if (RES_OK != res) { goto error; }
    486   if (NULL == btdf) { res = RES_BAD_ARG; goto error; }
    487 
    488   res = sphin_btdf_get_direction_distribution(btdf, &btdf_dir);
    489   if (RES_OK != res) { goto error; }
    490 
    491   switch (btdf_dir) {
    492     case SPHIN_BTDF_DIRECTION_LAMBERT:
    493       res = ssf_bsdf_create
    494         (sphor->allocator, &lambertian_transmission, &bsdf);
    495       if (RES_OK != res) { goto error; }
    496 
    497       res = lambertian_transmission_setup(bsdf, transmissivity);
    498       if (RES_OK != res) { goto error; }
    499 
    500       break;
    501 
    502     case SPHIN_BTDF_DIRECTION_KEEP_CURRENT_DIR:
    503       res = ssf_bsdf_create
    504         (sphor->allocator, &keep_current_dir_transmission, &bsdf);
    505       if (RES_OK != res) { goto error; }
    506 
    507       res = ssf_fresnel_create
    508         (sphor->allocator, &ssf_fresnel_constant, &fresnel);
    509       if (RES_OK != res) { goto error; }
    510 
    511       res = ssf_fresnel_constant_setup(fresnel, 1 - transmissivity);
    512       if (RES_OK != res) { goto error; }
    513 
    514       res = keep_current_dir_transmission_setup(bsdf, fresnel);
    515       if (RES_OK != res) { goto error; }
    516 
    517       break;
    518 
    519     case SPHIN_BTDF_DIRECTION_SNELL_DIELECTRIC:
    520 
    521       res = ssf_bsdf_create
    522         (sphor->allocator, &snell_dielectric_transmission, &bsdf);
    523       if (RES_OK != res) { goto error; }
    524 
    525       /* Get refractive index of the medium in the incident side */
    526       res = primitive_get_in_refraction_index
    527         (sphor, primitive, wavelength, &eta_i, &k_i);
    528       if (RES_OK != res) { goto error; }
    529 
    530       /* Get refractive index of the medium in the transmitted side */
    531       res = primitive_get_out_refraction_index
    532         (sphor, primitive, wavelength, &eta_t, &k_t);
    533 
    534       res = ssf_fresnel_create
    535         (sphor->allocator, &ssf_fresnel_constant, &fresnel);
    536       if (RES_OK != res) { goto error; }
    537 
    538       res = ssf_fresnel_constant_setup(fresnel, 1 - transmissivity);
    539       if (RES_OK != res) { goto error; }
    540 
    541       res = snell_dielectric_transmission_setup(bsdf, fresnel, eta_i, eta_t);
    542       if (RES_OK != res) { goto error; }
    543 
    544       break;
    545 
    546     case SPHIN_BTDF_DIRECTION_NONE__:
    547       res = RES_BAD_ARG;
    548       goto error;
    549 
    550     default:
    551       FATAL("Unreachable code\n");
    552       break;
    553   }
    554 
    555 exit:
    556   if (NULL != fresnel) { SSF(fresnel_ref_put(fresnel)); }
    557   *out_bsdf = bsdf;
    558   return res;
    559 error:
    560   if (NULL != bsdf) { SSF(bsdf_ref_put(bsdf)); }
    561   goto exit;
    562 }
    563 
    564 static res_T
    565 sample_interaction_type
    566   (struct sphor* sphor,
    567    struct ssp_rng* rng,
    568    double reflectivity,
    569    double transmissivity,
    570    enum intersection_ray_interaction_type* interaction_type)
    571 {
    572   double s = 0; /* Random number */
    573   res_T res = RES_OK;
    574 
    575   ASSERT(NULL != sphor);
    576   ASSERT(NULL != rng);
    577 
    578   (void)sphor;
    579 
    580   if (reflectivity + transmissivity > 1) {
    581     res = RES_BAD_ARG; goto error; }
    582 
    583   if (reflectivity < 0) {
    584     res = RES_BAD_ARG; goto error; }
    585 
    586   if (transmissivity < 0) {
    587     res = RES_BAD_ARG; goto error; }
    588 
    589   s = ssp_rng_canonical(rng);
    590 
    591   if (s >= 0 &&  s <= reflectivity) {
    592     *interaction_type = INTERSECTION_RAY_INTERACTION_REFLECTION;
    593   } else if (s > reflectivity && s <= reflectivity + transmissivity) {
    594     *interaction_type = INTERSECTION_RAY_INTERACTION_TRANSMISSION;
    595   } else if (s > reflectivity + transmissivity && s <= 1) {
    596     *interaction_type = INTERSECTION_RAY_INTERACTION_ABSORPTION;
    597   } else { res = RES_BAD_ARG; goto error; }
    598 
    599 exit:
    600   return res;
    601 error:
    602   goto exit;
    603 }
    604 
    605 /*******************************************************************************
    606  * Local functions
    607  ******************************************************************************/
    608 res_T
    609 sample_interface_ray_interaction
    610   (struct sphor* sphor,
    611    struct ssp_rng* rng,
    612    struct ray* ray,
    613    struct intersection* intersection,
    614    double wavelength,
    615    double dir[3],
    616    enum intersection_ray_interaction_type* interaction_type)
    617 {
    618   double invert_normal = 0;
    619   double normal[3] = {0};
    620   double wo[3] = {0};
    621   double reflectivity = 0;
    622   double transmissivity = 0;
    623   double sample[3] = {0};
    624   struct primitive* primitive = NULL;
    625   struct sphin_brdf* brdf = NULL;
    626   struct sphin_btdf* btdf = NULL;
    627   struct ssf_bsdf* bsdf = NULL;
    628   res_T res = RES_OK;
    629 
    630   ASSERT(NULL != sphor);
    631   ASSERT(NULL != rng);
    632   ASSERT(NULL != ray);
    633   ASSERT(NULL != intersection);
    634 
    635   primitive = &intersection->position.primitive;
    636 
    637   /* Ensure that the ray direction and the geometry normal are compatible with
    638    * the star-sf library. */
    639   d3_minus(wo, ray->direction);
    640   d3_set(normal, primitive->normal);
    641   /* Ensure normal and direction point to the same hemisphere */
    642   invert_normal = (d3_dot(normal, wo) > 0 ) ? 1. : -1.;
    643   d3_normalize(normal, d3_muld(normal, normal, invert_normal));
    644 
    645   /* Verify if one of the surfaces defined in the intersected interface
    646    * side has a BRDF defined */
    647   res = primitive_get_brdf(sphor, primitive, &brdf);
    648   if (RES_OK != res) { goto error; }
    649 
    650   /* Verify if one of the surfaces defined in the intersected interface
    651    * side has a BTDF defined */
    652   res = primitive_get_btdf(sphor, primitive, &btdf);
    653   if (RES_OK != res) { goto error; }
    654 
    655   if (NULL == brdf && NULL == btdf) { /* Nothing to do */
    656     transmissivity = 1;
    657     *interaction_type = INTERSECTION_RAY_INTERACTION_TRANSMISSION;
    658     d3_set(dir, ray->direction);
    659   } else {
    660     /* Setup reflectivity */
    661     if (NULL != brdf) {
    662       res = primitive_get_reflectivity
    663         (sphor, primitive, wavelength,
    664          d3_dot(normal, wo), &reflectivity);
    665       if (RES_OK != res) { goto error; }
    666     }
    667 
    668     /* Setup transmissivity */
    669     if (NULL != btdf) {
    670       res = primitive_get_transmissivity
    671         (sphor, primitive, wavelength,
    672          d3_dot(normal, wo), &transmissivity);
    673       if (RES_OK != res) { goto error; }
    674     }
    675 
    676     /* Sample interaction type */
    677     res = sample_interaction_type
    678       (sphor, rng, reflectivity, transmissivity, interaction_type);
    679     if (RES_OK != res) { goto error; }
    680 
    681     if (*interaction_type == INTERSECTION_RAY_INTERACTION_ABSORPTION) {
    682       goto exit; /* Nothing else to do */
    683     }
    684 
    685     if (*interaction_type == INTERSECTION_RAY_INTERACTION_REFLECTION) {
    686       res = setup_bsdf_reflection
    687         (sphor, ray, intersection, wavelength, reflectivity, &bsdf);
    688     }
    689     if (*interaction_type == INTERSECTION_RAY_INTERACTION_TRANSMISSION) {
    690       res = setup_bsdf_transmission
    691         (sphor, ray, intersection, wavelength, transmissivity, &bsdf);
    692     }
    693     ssf_bsdf_sample(bsdf, rng, wo, normal, sample, NULL, NULL);
    694 
    695     d3_set(dir, sample);
    696   }
    697 
    698 exit:
    699   if (NULL != bsdf) { SSF(bsdf_ref_put(bsdf)); }
    700   return res;
    701 error:
    702   goto exit;
    703 }