htrdr_materials.c (13178B)
1 /* Copyright (C) 2018-2019, 2022-2025 Centre National de la Recherche Scientifique 2 * Copyright (C) 2020-2022 Institut Mines Télécom Albi-Carmaux 3 * Copyright (C) 2022-2025 Institut Pierre-Simon Laplace 4 * Copyright (C) 2022-2025 Institut de Physique du Globe de Paris 5 * Copyright (C) 2018-2025 |Méso|Star> (contact@meso-star.com) 6 * Copyright (C) 2022-2025 Observatoire de Paris 7 * Copyright (C) 2022-2025 Université de Reims Champagne-Ardenne 8 * Copyright (C) 2022-2025 Université de Versaille Saint-Quentin 9 * Copyright (C) 2018-2019, 2022-2025 Université Paul Sabatier 10 * 11 * This program is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation, either version 3 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program. If not, see <http://www.gnu.org/licenses/>. */ 23 24 #define _POSIX_C_SOURCE 200112L /* strtok_r and wordexp support */ 25 26 #include "core/htrdr.h" 27 #include "core/htrdr_log.h" 28 #include "core/htrdr_materials.h" 29 30 #include <mrumtl.h> 31 #include <star/ssf.h> 32 #include <star/ssp.h> 33 34 #include <rsys/cstr.h> 35 #include <rsys/double3.h> 36 #include <rsys/hash_table.h> 37 #include <rsys/mem_allocator.h> 38 #include <rsys/ref_count.h> 39 #include <rsys/str.h> 40 #include <rsys/text_reader.h> 41 42 #include <string.h> 43 #include <wordexp.h> 44 45 struct mtl { 46 struct mrumtl* mrumtl; 47 double temperature; /* In Kelvin */ 48 }; 49 static const struct mtl MTL_NULL = {NULL, -1}; 50 51 /* Generate the hash table that maps a material name to its data */ 52 #define HTABLE_NAME name2mtl 53 #define HTABLE_DATA struct mtl 54 #define HTABLE_KEY struct str 55 #define HTABLE_KEY_FUNCTOR_INIT str_init 56 #define HTABLE_KEY_FUNCTOR_RELEASE str_release 57 #define HTABLE_KEY_FUNCTOR_COPY str_copy 58 #define HTABLE_KEY_FUNCTOR_COPY_AND_RELEASE str_copy_and_release 59 #define HTABLE_KEY_FUNCTOR_HASH str_hash 60 #define HTABLE_KEY_FUNCTOR_EQ str_eq 61 #include <rsys/hash_table.h> 62 63 struct htrdr_materials { 64 struct htable_name2mtl name2mtl; 65 struct htrdr* htrdr; 66 ref_T ref; 67 }; 68 69 /******************************************************************************* 70 * Local functions 71 ******************************************************************************/ 72 static res_T 73 parse_material 74 (struct htrdr_materials* mats, 75 struct txtrdr* txtrdr, 76 struct str* str) /* Scratch string */ 77 { 78 struct mrumtl_create_args mrumtl_args = MRUMTL_CREATE_ARGS_DEFAULT; 79 wordexp_t wexp; 80 char* tk = NULL; 81 char* tk_ctx = NULL; 82 struct mtl mtl = MTL_NULL; 83 int err = 0; 84 int wexp_is_allocated = 0; 85 res_T res = RES_OK; 86 ASSERT(mats && txtrdr); 87 88 tk = strtok_r(txtrdr_get_line(txtrdr), " \t", &tk_ctx); 89 ASSERT(tk); 90 91 res = str_set(str, tk); 92 if(res != RES_OK) { 93 htrdr_log_err(mats->htrdr, 94 "%s:%lu: could not copy the material name `%s' -- %s.\n", 95 txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk, 96 res_to_cstr(res)); 97 goto error; 98 } 99 100 tk = strtok_r(NULL, "", &tk_ctx); 101 if(!tk) { 102 htrdr_log_err(mats->htrdr, 103 "%s:%lu: missing the MruMtl file for the material `%s'.\n", 104 txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), 105 str_cget(str)); 106 res = RES_BAD_ARG; 107 goto error; 108 } 109 110 err = wordexp(tk, &wexp, 0); 111 if(err) { 112 htrdr_log_err(mats->htrdr, 113 "%s:%lu: error in word expension of the mrumtl path.\n", 114 txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr)); 115 res = RES_BAD_ARG; 116 goto error; 117 } 118 wexp_is_allocated = 1; 119 120 if(wexp.we_wordc < 1) { 121 htrdr_log_err(mats->htrdr, 122 "%s:%lu: missing the MruMtl file for the material `%s'.\n", 123 txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), 124 str_cget(str)); 125 res = RES_BAD_ARG; 126 goto error; 127 } 128 129 /* Parse the mrumtl file if any */ 130 if(strcmp(wexp.we_wordv[0], "none")) { 131 mrumtl_args.logger = htrdr_get_logger(mats->htrdr); 132 mrumtl_args.allocator = htrdr_get_allocator(mats->htrdr); 133 mrumtl_args.verbose = htrdr_get_verbosity_level(mats->htrdr); 134 res = mrumtl_create(&mrumtl_args, &mtl.mrumtl); 135 if(res != RES_OK) { 136 htrdr_log_err(mats->htrdr, 137 "%s:%lu: error creating the MruMtl loader for the material `%s'-- %s.\n", 138 txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), 139 str_cget(str), res_to_cstr(res)); 140 goto error; 141 } 142 143 res = mrumtl_load(mtl.mrumtl, wexp.we_wordv[0]); 144 if(res != RES_OK) goto error; 145 } 146 147 if(wexp.we_wordc < 2) { 148 if(mtl.mrumtl) { 149 htrdr_log_err(mats->htrdr, 150 "%s:%lu: missing temperature for the material `%s'.\n", 151 txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), 152 str_cget(str)); 153 res = RES_BAD_ARG; 154 goto error; 155 } 156 } else { 157 /* Parse the temperature */ 158 res = cstr_to_double(wexp.we_wordv[1], &mtl.temperature); 159 if(res != RES_OK) { 160 htrdr_log_err(mats->htrdr, 161 "%s:%lu: error parsing the temperature `%s' for the material `%s' " 162 "-- %s.\n", 163 txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), 164 wexp.we_wordv[1], str_cget(str), res_to_cstr(res)); 165 goto error; 166 } 167 } 168 169 /* Register the material */ 170 res = htable_name2mtl_set(&mats->name2mtl, str, &mtl); 171 if(res != RES_OK) { 172 htrdr_log_err(mats->htrdr, 173 "%s:%lu: could not register the material `%s' -- %s.\n", 174 txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), 175 str_cget(str), res_to_cstr(res)); 176 goto error; 177 } 178 179 if(wexp.we_wordc > 2) { 180 htrdr_log_warn(mats->htrdr, "%s:%lu: unexpected text `%s'.\n", 181 txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), 182 wexp.we_wordv[2]); 183 } 184 185 exit: 186 if(wexp_is_allocated) wordfree(&wexp); 187 return res; 188 error: 189 if(mtl.mrumtl) MRUMTL(ref_put(mtl.mrumtl)); 190 goto exit; 191 } 192 193 static res_T 194 parse_materials_list 195 (struct htrdr_materials* mats, 196 const char* filename, 197 const char* func_name) 198 { 199 struct txtrdr* txtrdr = NULL; 200 struct str str; 201 res_T res = RES_OK; 202 ASSERT(mats && filename && func_name); 203 204 str_init(htrdr_get_allocator(mats->htrdr), &str); 205 206 res = txtrdr_file(htrdr_get_allocator(mats->htrdr), filename, '#', &txtrdr); 207 if(res != RES_OK) { 208 htrdr_log_err(mats->htrdr, 209 "%s: could not create the text reader for the material file `%s' -- %s.\n", 210 func_name, filename, res_to_cstr(res)); 211 goto error; 212 } 213 214 for(;;) { 215 res = txtrdr_read_line(txtrdr); 216 if(res != RES_OK) { 217 htrdr_log_err(mats->htrdr, 218 "%s: error reading a line in the material file `%s' -- %s.\n", 219 func_name, filename, res_to_cstr(res)); 220 goto error; 221 } 222 223 if(!txtrdr_get_cline(txtrdr)) break; 224 225 res = parse_material(mats, txtrdr, &str); 226 if(res != RES_OK) goto error; 227 } 228 229 exit: 230 str_release(&str); 231 if(txtrdr) txtrdr_ref_put(txtrdr); 232 return res; 233 error: 234 goto exit; 235 } 236 237 static void 238 materials_release(ref_T* ref) 239 { 240 struct htable_name2mtl_iterator it, it_end; 241 struct htrdr_materials* mats; 242 struct htrdr* htrdr; 243 ASSERT(ref); 244 mats = CONTAINER_OF(ref, struct htrdr_materials, ref); 245 246 htable_name2mtl_begin(&mats->name2mtl, &it); 247 htable_name2mtl_end(&mats->name2mtl, &it_end); 248 while(!htable_name2mtl_iterator_eq(&it, &it_end)) { 249 struct mtl* mtl = htable_name2mtl_iterator_data_get(&it); 250 /* The mrumtl can be NULL for semi transparent materials */ 251 if(mtl->mrumtl) MRUMTL(ref_put(mtl->mrumtl)); 252 htable_name2mtl_iterator_next(&it); 253 } 254 htable_name2mtl_release(&mats->name2mtl); 255 htrdr = mats->htrdr; 256 MEM_RM(htrdr_get_allocator(htrdr), mats); 257 htrdr_ref_put(htrdr); 258 } 259 260 static res_T 261 create_bsdf_diffuse 262 (struct htrdr* htrdr, 263 const struct mrumtl_brdf* brdf, 264 const size_t ithread, 265 struct ssf_bsdf** out_bsdf) 266 { 267 struct mrumtl_brdf_lambertian lambert; 268 struct ssf_bsdf* bsdf = NULL; 269 res_T res = RES_OK; 270 ASSERT(htrdr && brdf && out_bsdf); 271 ASSERT(mrumtl_brdf_get_type(brdf) == MRUMTL_BRDF_LAMBERTIAN); 272 273 res = ssf_bsdf_create(htrdr_get_thread_allocator(htrdr, ithread), 274 &ssf_lambertian_reflection, &bsdf); 275 if(res != RES_OK) goto error; 276 277 MRUMTL(brdf_get_lambertian(brdf, &lambert)); 278 res = ssf_lambertian_reflection_setup(bsdf, lambert.reflectivity); 279 if(res != RES_OK) goto error; 280 281 exit: 282 *out_bsdf = bsdf; 283 return res; 284 error: 285 if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; } 286 goto exit; 287 } 288 289 static res_T 290 create_bsdf_specular 291 (struct htrdr* htrdr, 292 const struct mrumtl_brdf* brdf, 293 const size_t ithread, 294 struct ssf_bsdf** out_bsdf) 295 { 296 struct mrumtl_brdf_specular spec; 297 struct ssf_bsdf* bsdf = NULL; 298 struct ssf_fresnel* fresnel = NULL; 299 struct mem_allocator* allocator = NULL; 300 res_T res = RES_OK; 301 ASSERT(htrdr && brdf && out_bsdf); 302 ASSERT(mrumtl_brdf_get_type(brdf) == MRUMTL_BRDF_SPECULAR); 303 304 allocator = htrdr_get_thread_allocator(htrdr, ithread); 305 306 res = ssf_bsdf_create(allocator, &ssf_specular_reflection, &bsdf); 307 if(res != RES_OK) goto error; 308 309 res = ssf_fresnel_create(allocator, &ssf_fresnel_constant, &fresnel); 310 if(res != RES_OK) goto error; 311 312 MRUMTL(brdf_get_specular(brdf, &spec)); 313 res = ssf_fresnel_constant_setup(fresnel, spec.reflectivity); 314 if(res != RES_OK) goto error; 315 316 res = ssf_specular_reflection_setup(bsdf, fresnel); 317 if(res != RES_OK) goto error; 318 319 exit: 320 if(fresnel) SSF(fresnel_ref_put(fresnel)); 321 *out_bsdf = bsdf; 322 return res; 323 error: 324 if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; } 325 goto exit; 326 } 327 328 /******************************************************************************* 329 * Local symbol 330 ******************************************************************************/ 331 res_T 332 htrdr_materials_create 333 (struct htrdr* htrdr, 334 const char* filename, 335 struct htrdr_materials** out_mtl) 336 { 337 struct htrdr_materials* mats = NULL; 338 res_T res = RES_OK; 339 ASSERT(htrdr && filename && out_mtl); 340 341 mats = MEM_CALLOC(htrdr_get_allocator(htrdr), 1, sizeof(*mats)); 342 if(!mats) { 343 res = RES_MEM_ERR; 344 htrdr_log_err(htrdr, 345 "%s: could not allocate the mats data structure -- %s.\n", 346 FUNC_NAME, res_to_cstr(res)); 347 goto error; 348 } 349 ref_init(&mats->ref); 350 htrdr_ref_get(htrdr); 351 mats->htrdr = htrdr; 352 htable_name2mtl_init(htrdr_get_allocator(htrdr), &mats->name2mtl); 353 354 res = parse_materials_list(mats, filename, FUNC_NAME); 355 if(res != RES_OK) goto error; 356 357 exit: 358 if(out_mtl) *out_mtl = mats; 359 return res; 360 error: 361 if(mats) { 362 htrdr_materials_ref_put(mats); 363 mats = NULL; 364 } 365 goto exit; 366 } 367 368 void 369 htrdr_materials_ref_get(struct htrdr_materials* mats) 370 { 371 ASSERT(mats); 372 ref_get(&mats->ref); 373 } 374 375 void 376 htrdr_materials_ref_put(struct htrdr_materials* mats) 377 { 378 ASSERT(mats); 379 ref_put(&mats->ref, materials_release); 380 } 381 382 int 383 htrdr_materials_find_mtl 384 (struct htrdr_materials* mats, 385 const char* name, 386 struct htrdr_mtl* htrdr_mtl) 387 { 388 struct str str; 389 struct htable_name2mtl_iterator it, it_end; 390 int found = 0; 391 ASSERT(mats && name && htrdr_mtl); 392 393 str_init(htrdr_get_allocator(mats->htrdr), &str); 394 CHK(str_set(&str, name) == RES_OK); 395 396 htable_name2mtl_find_iterator(&mats->name2mtl, &str, &it); 397 htable_name2mtl_end(&mats->name2mtl, &it_end); 398 if(htable_name2mtl_iterator_eq(&it, &it_end)) { /* No material found */ 399 *htrdr_mtl = HTRDR_MTL_NULL; 400 found = 0; 401 } else { 402 struct mtl* mtl = htable_name2mtl_iterator_data_get(&it); 403 ASSERT(mtl != NULL); 404 htrdr_mtl->name = str_cget(htable_name2mtl_iterator_key_get(&it)); 405 htrdr_mtl->mrumtl = mtl->mrumtl; 406 htrdr_mtl->temperature = mtl->temperature; 407 found = 1; 408 } 409 str_release(&str); 410 411 return found; 412 } 413 414 res_T 415 htrdr_mtl_create_bsdf 416 (struct htrdr* htrdr, 417 const struct htrdr_mtl* mtl, 418 const size_t ithread, 419 const double wavelength, 420 struct ssp_rng* rng, 421 struct ssf_bsdf** out_bsdf) 422 { 423 struct ssf_bsdf* bsdf = NULL; 424 const struct mrumtl_brdf* brdf = NULL; 425 size_t ibrdf; 426 double r; 427 res_T res = RES_OK; 428 ASSERT(htrdr && mtl && wavelength && rng && out_bsdf); 429 430 r = ssp_rng_canonical(rng); 431 432 res = mrumtl_fetch_brdf(mtl->mrumtl, wavelength, r, &ibrdf); 433 if(res != RES_OK) { 434 htrdr_log_err(htrdr, 435 "%s: error retrieving the MruMtl BRDF for the wavelength %g.\n", 436 FUNC_NAME, wavelength); 437 res = RES_BAD_ARG; 438 goto error; 439 } 440 441 brdf = mrumtl_get_brdf(mtl->mrumtl, ibrdf); 442 switch(mrumtl_brdf_get_type(brdf)) { 443 case MRUMTL_BRDF_LAMBERTIAN: 444 res = create_bsdf_diffuse(htrdr, brdf, ithread, &bsdf); 445 break; 446 case MRUMTL_BRDF_SPECULAR: 447 res = create_bsdf_specular(htrdr, brdf, ithread, &bsdf); 448 break; 449 default: FATAL("Unreachable code.\n"); break; 450 } 451 if(res != RES_OK) { 452 htrdr_log_err(htrdr, "%s: could not create the BSDF -- %s.\n", 453 FUNC_NAME, res_to_cstr(res)); 454 goto error; 455 } 456 457 exit: 458 *out_bsdf = bsdf; 459 return res; 460 error: 461 if(bsdf) { SSF(bsdf_ref_put(bsdf)); bsdf = NULL; } 462 goto exit; 463 } 464