sdis_heat_path_radiative_Xd.h (16363B)
1 /* Copyright (C) 2016-2025 |Méso|Star> (contact@meso-star.com) 2 * 3 * This program is free software: you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation, either version 3 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. */ 15 16 #include "sdis_brdf.h" 17 #include "sdis_device_c.h" 18 #include "sdis_green.h" 19 #include "sdis_heat_path.h" 20 #include "sdis_heat_path_boundary_c.h" 21 #include "sdis_interface_c.h" 22 #include "sdis_log.h" 23 #include "sdis_medium_c.h" 24 #include "sdis_misc.h" 25 #include "sdis_radiative_env_c.h" 26 #include "sdis_scene_c.h" 27 28 #include <star/ssp.h> 29 30 #include "sdis_Xd_begin.h" 31 32 /******************************************************************************* 33 * Non generic helper functions 34 ******************************************************************************/ 35 #ifndef SDIS_HEAT_PATH_RADIATIVE_XD_H 36 #define SDIS_HEAT_PATH_RADIATIVE_XD_H 37 38 static res_T 39 set_limit_radiative_temperature 40 (struct sdis_scene* scn, 41 struct rwalk_context* ctx, 42 struct rwalk* rwalk, 43 /* Direction along which the random walk reached the radiative environment */ 44 const double dir[3], 45 const int branch_id, 46 struct temperature* T) 47 { 48 struct sdis_radiative_ray ray = SDIS_RADIATIVE_RAY_NULL; 49 double trad = 0; /* [K] */ 50 res_T res = RES_OK; 51 52 /* Check pre-conditions */ 53 ASSERT(scn && ctx && rwalk && dir && T); 54 ASSERT(SXD_HIT_NONE(&rwalk->XD(hit))); 55 56 rwalk->hit_side = SDIS_SIDE_NULL__; 57 d3_set(rwalk->dir, dir); 58 d3_normalize(rwalk->dir, rwalk->dir); 59 d3_set(ray.dir, rwalk->dir); 60 ray.time = rwalk->vtx.time; 61 62 trad = radiative_env_get_temperature(scn->radenv, &ray); 63 if(SDIS_TEMPERATURE_IS_UNKNOWN(trad)) { 64 log_err(scn->dev, 65 "%s:%s: the random walk has reached an invalid radiative environment from " 66 "position (%g, %g, %g) along direction (%g, %g, %g): the temperature is " 67 "unknown. This may be due to numerical inaccuracies or inconsistencies " 68 "in the simulated system (e.g. non-closed geometry). For systems where " 69 "sampled paths can reach such a temperature, we need to define a valid " 70 "radiative temperature, i.e. one with a known temperature.\n", 71 __FILE__, FUNC_NAME, SPLIT3(rwalk->vtx.P), SPLIT3(rwalk->dir)); 72 res = RES_BAD_OP; 73 goto error; 74 } 75 76 /* The limit condition is reached */ 77 T->value += trad; 78 T->done = 1; 79 80 /* Update green path */ 81 if(ctx->green_path) { 82 res = green_path_set_limit_radiative_ray 83 (ctx->green_path, &ray, rwalk->elapsed_time); 84 if(res != RES_OK) goto error; 85 } 86 87 /* Record the limit vertex of the sampled path. Set it arbitrarily at a 88 * distance of 0.1 meters from the surface along the direction reaching the 89 * radiative environment */ 90 if(ctx->heat_path) { 91 const double empirical_dst = 0.1 * (float)scn->fp_to_meter; 92 struct sdis_rwalk_vertex vtx = SDIS_RWALK_VERTEX_NULL; 93 94 vtx = rwalk->vtx; 95 vtx.P[0] += dir[0] * empirical_dst; 96 vtx.P[1] += dir[1] * empirical_dst; 97 vtx.P[2] += dir[2] * empirical_dst; 98 res = register_heat_vertex(ctx->heat_path, &vtx, T->value, 99 SDIS_HEAT_VERTEX_RADIATIVE, branch_id); 100 if(res != RES_OK) goto error; 101 } 102 103 exit: 104 return res; 105 error: 106 goto exit; 107 } 108 109 /* Check that the trajectory reaches a valid interface, i.e. that it is on a 110 * fluid/solid interface and has reached it from the fluid */ 111 static res_T 112 check_interface 113 (const struct sdis_interface* interf, 114 const struct sdis_interface_fragment* frag, 115 const int verbose) /* Control the verbosity of the function */ 116 { 117 enum sdis_medium_type mdm_frt_type = SDIS_MEDIUM_TYPES_COUNT__; 118 enum sdis_medium_type mdm_bck_type = SDIS_MEDIUM_TYPES_COUNT__; 119 enum sdis_side fluid_side = SDIS_SIDE_NULL__; 120 res_T res = RES_OK; 121 122 mdm_frt_type = sdis_medium_get_type(interf->medium_front); 123 mdm_bck_type = sdis_medium_get_type(interf->medium_back); 124 125 /* Semi-transparent materials are not supported. This means that a solid/solid 126 * interface must not be intersected when tracing radiative paths */ 127 if(mdm_frt_type == SDIS_SOLID && mdm_bck_type == SDIS_SOLID) { 128 if(verbose) { 129 log_err(interf->dev, 130 "Error when sampling the radiatve path. The trajectory reaches a " 131 "solid/solid interface, whereas this is supposed to be impossible " 132 "(path position: %g, %g, %g).\n", 133 SPLIT3(frag->P)); 134 } 135 res = RES_BAD_OP; 136 goto error; 137 } 138 139 /* Find out which side of the interface the fluid is on */ 140 if(mdm_frt_type == SDIS_FLUID) { 141 fluid_side = SDIS_FRONT; 142 } else if(mdm_bck_type == SDIS_FLUID) { 143 fluid_side = SDIS_BACK; 144 } else { 145 FATAL("Unreachable code\n"); 146 } 147 148 /* Check that the current position is on the correct side of the interface */ 149 if(frag->side != fluid_side) { 150 if(verbose) { 151 log_err(interf->dev, 152 "Inconsistent intersection when sampling the radiative path. " 153 "The path reaches an interface on its solid side, whereas this is " 154 "supposed to be impossible (path position: %g, %g, %g).\n", 155 SPLIT3(frag->P)); 156 } 157 res = RES_BAD_OP; 158 goto error; 159 } 160 161 exit: 162 return res; 163 error: 164 goto exit; 165 } 166 167 #endif /* SDIS_HEAT_PATH_RADIATIVE_XD_H */ 168 169 /******************************************************************************* 170 * Generic helper functions 171 ******************************************************************************/ 172 static INLINE void 173 XD(setup_fragment) 174 (struct sdis_interface_fragment* frag, 175 const double pos[DIM], 176 const double dir[DIM], /* Direction _toward_ the hit position */ 177 const double time, /* Current time */ 178 const double N[DIM],/* Surface normal */ 179 const struct sXd(hit)* hit) 180 { 181 struct sdis_rwalk_vertex vtx = SDIS_RWALK_VERTEX_NULL; 182 enum sdis_side side = SDIS_SIDE_NULL__; 183 ASSERT(frag && pos && dir && N); 184 ASSERT(dX(is_normalized)(N)); 185 186 /* Setup the interface fragment at the intersection position */ 187 dX(set)(vtx.P, pos); 188 vtx.time = time; 189 side = dX(dot)(dir, N) < 0 ? SDIS_FRONT : SDIS_BACK; 190 XD(setup_interface_fragment)(frag, &vtx, hit, side); 191 } 192 193 /******************************************************************************* 194 * Local functions 195 ******************************************************************************/ 196 res_T 197 XD(trace_radiative_path) 198 (struct sdis_scene* scn, 199 const float ray_dir[3], 200 struct rwalk_context* ctx, 201 struct rwalk* rwalk, 202 struct ssp_rng* rng, 203 struct temperature* T) 204 { 205 /* The radiative random walk is always performed in 3D. In 2D, the geometry 206 * are assumed to be extruded to the infinity along the Z dimension. */ 207 double N[3] = {0,0,0}; 208 double dir[3] = {0,0,0}; 209 double pos[3] = {0,0,0}; 210 int branch_id; 211 size_t nbounces = 0; /* For debug */ 212 res_T res = RES_OK; 213 214 ASSERT(scn && ray_dir && ctx && rwalk && rng && T); 215 216 d3_set_f3(dir, ray_dir); 217 d3_normalize(dir, dir); 218 219 /* (int)ctx->nbranchings < 0 <=> Beginning of the realisation */ 220 branch_id = MMAX((int)ctx->nbranchings, 0); 221 222 /* Launch the radiative random walk */ 223 for(;;) { 224 /* BRDF */ 225 struct brdf brdf = BRDF_NULL; 226 struct brdf_sample bounce = BRDF_SAMPLE_NULL; 227 struct brdf_setup_args brdf_setup_args = BRDF_SETUP_ARGS_NULL; 228 229 /* Miscellaneous */ 230 struct sdis_interface_fragment frag = SDIS_INTERFACE_FRAGMENT_NULL; 231 struct sdis_interface* interf = NULL; 232 struct sdis_medium* chk_mdm = NULL; 233 double wi[3] = {0,0,0}; 234 235 d3_set(pos, rwalk->vtx.P); 236 d3_minus(wi, dir); 237 238 res = XD(find_next_fragment)(scn, pos, dir, &rwalk->XD(hit), 239 rwalk->vtx.time, rwalk->enc_id, &rwalk->XD(hit), &interf, &frag); 240 if(res != RES_OK) goto error; 241 242 /* The path reaches the radiative environment */ 243 if(SXD_HIT_NONE(&rwalk->XD(hit))) { 244 res = set_limit_radiative_temperature(scn, ctx, rwalk, dir, branch_id, T); 245 if(res != RES_OK) goto error; 246 247 ASSERT(T->done); 248 break; /* Stop the radiative path */ 249 } 250 251 /* Move the random walk to the hit position, i.e., the next position on the 252 * interface returned as a fragment by the find_next_fragment function. Do 253 * not use the sampled direction and distance to the hit point to 254 * calculate the new position, as the current position may have been slightly 255 * shifted on the starting triangle by the find_next_fragment function in 256 * order to avoid numerical inaccuracy issues, making it impossible to 257 * reconstruct the position actually returned by the function. The starting 258 * point and distance returned are not, in any case, those used by the 259 * function to calculate the new wall position. So simply use the position 260 * returned by this function. */ 261 d3_set(rwalk->vtx.P, frag.P); 262 rwalk->hit_side = frag.side; 263 264 /* Verify that the intersection, although in the same enclosure, touches the 265 * interface of a fluid. We verify this by interface, since a radiative path 266 * can be traced in an enclosure containing several media used to describe a 267 * set of boundary conditions. 268 * 269 * If the enclosure is good but the media type is not, this means that the 270 * radiative path is sampled in the wrong media. This is not a numerical 271 * problem, but a user problem: trying to sample a radiative path in a solid 272 * when semi-transparent solids are not yet supported by Stardis. This error 273 * is therefore fatal for the calculation */ 274 chk_mdm = rwalk->hit_side == SDIS_FRONT 275 ? interf->medium_front 276 : interf->medium_back; 277 if(sdis_medium_get_type(chk_mdm) == SDIS_SOLID) { 278 log_err(scn->dev, 279 "%s: a radiative path cannot evolve in a solid -- pos=(%g, %g, %g)\n", 280 FUNC_NAME, SPLIT3(rwalk->vtx.P)); 281 res = RES_BAD_OP_IRRECOVERABLE; 282 goto error; 283 } 284 285 /* Register the random walk vertex against the heat path */ 286 res = register_heat_vertex(ctx->heat_path, &rwalk->vtx, T->value, 287 SDIS_HEAT_VERTEX_RADIATIVE, branch_id); 288 if(res != RES_OK) goto error; 289 290 /* Retrieve BRDF at current interface position */ 291 brdf_setup_args.interf = interf; 292 brdf_setup_args.frag = &frag; 293 brdf_setup_args.source_id = SDIS_INTERN_SOURCE_ID; 294 res = brdf_setup(scn->dev, &brdf_setup_args, &brdf); 295 if(res != RES_OK) goto error; 296 297 /* Switch in boundary temperature? */ 298 if(ssp_rng_canonical(rng) < brdf.emissivity) { 299 T->func = XD(boundary_path); 300 rwalk->enc_id = ENCLOSURE_ID_NULL; /* Interface between 2 enclosures */ 301 break; 302 } 303 304 /* Normalize the normal of the interface and ensure that it points toward the 305 * current medium */ 306 switch(frag.side) { 307 case SDIS_FRONT: d3_set(N, frag.Ng); break; 308 case SDIS_BACK: d3_minus(N, frag.Ng); break; 309 default: FATAL("Unreachable code\n"); break; 310 } 311 brdf_sample(&brdf, rng, wi, N, &bounce); 312 d3_set(dir, bounce.dir); /* Always in 3D */ 313 314 ++nbounces; 315 } 316 317 exit: 318 return res; 319 error: 320 goto exit; 321 } 322 323 res_T 324 XD(radiative_path) 325 (struct sdis_scene* scn, 326 struct rwalk_context* ctx, 327 struct rwalk* rwalk, 328 struct ssp_rng* rng, 329 struct temperature* T) 330 { 331 /* The radiative random walk is always performed in 3D. In 2D, the geometry 332 * are assumed to be extruded to the infinity along the Z dimension. */ 333 float N[3] = {0, 0, 0}; 334 float dir[3] = {0, 0, 0}; 335 res_T res = RES_OK; 336 337 ASSERT(scn && ctx && rwalk && rng && T); 338 ASSERT(!SXD_HIT_NONE(&rwalk->XD(hit))); 339 340 /* Normalize the normal of the interface and ensure that it points toward the 341 * current medium */ 342 fX(normalize(N, rwalk->XD(hit).normal)); 343 if(rwalk->hit_side == SDIS_BACK) { 344 fX(minus(N, N)); 345 } 346 347 /* Cosine weighted sampling of a direction around the surface normal */ 348 ssp_ran_hemisphere_cos_float(rng, N, dir, NULL); 349 350 /* Launch the radiative random walk */ 351 res = XD(trace_radiative_path)(scn, dir, ctx, rwalk, rng, T); 352 if(res != RES_OK) goto error; 353 354 exit: 355 return res; 356 error: 357 goto exit; 358 } 359 360 void 361 XD(trace_ray) 362 (struct sdis_scene* scn, 363 const double pos[DIM], 364 const double dir[3], 365 const double distance, 366 const unsigned enc_id, 367 const struct sXd(hit)* hit_from, 368 struct sXd(hit)* hit) 369 { 370 struct hit_filter_data filter_data = HIT_FILTER_DATA_NULL; 371 float ray_org[DIM] = {0}; 372 float ray_dir[3] = {0}; 373 float ray_range[2] = {0}; 374 ASSERT(scn && pos && dir && distance >= 0 && hit_from && hit); 375 376 fX_set_dX(ray_org, pos); 377 f3_set_d3(ray_dir, dir); 378 ray_range[0] = 0; 379 ray_range[1] = (float)distance; 380 filter_data.XD(hit) = *hit_from; 381 filter_data.epsilon = 1.e-6; 382 filter_data.scn = scn; /* Enable the filtering wrt the enclosure id */ 383 filter_data.enc_id = enc_id; 384 #if DIM == 2 385 SXD(scene_view_trace_ray_3d 386 (scn->sXd(view), ray_org, ray_dir, ray_range, &filter_data, hit)); 387 #else 388 SXD(scene_view_trace_ray 389 (scn->sXd(view), ray_org, ray_dir, ray_range, &filter_data, hit)); 390 #endif 391 } 392 393 res_T 394 XD(find_next_fragment) 395 (struct sdis_scene* scn, 396 const double in_pos[DIM], 397 const double in_dir[3], /* Always in 3D */ 398 const struct sXd(hit)* in_hit, 399 const double time, 400 const unsigned enc_id, 401 struct sXd(hit)* out_hit, 402 struct sdis_interface** out_interf, 403 struct sdis_interface_fragment* out_frag) 404 { 405 int NATTEMPTS_MAX = 10; 406 int nattempts = 1; 407 408 /* Stardis */ 409 struct sdis_interface_fragment frag = SDIS_INTERFACE_FRAGMENT_NULL; 410 struct sdis_interface* interf = NULL; 411 412 struct sXd(hit) hit = SXD_HIT_NULL; 413 double rt_pos[DIM] = {0}; 414 res_T res = RES_OK; 415 416 ASSERT(scn && in_pos && in_dir && in_hit); 417 ASSERT(out_hit && out_interf && out_frag); 418 419 /* Only one attempt is allowed when the ray does not start from a primitive */ 420 NATTEMPTS_MAX = S3D_HIT_NONE(in_hit) ? 1 : 10; 421 422 dX(set)(rt_pos, in_pos); 423 424 do { 425 struct sdis_rwalk_vertex vtx = SDIS_RWALK_VERTEX_NULL; 426 struct sdis_medium* solid = NULL; 427 double pos[3] = {0}; 428 double vec[3] = {0}; 429 double N[3] = {0}; 430 double delta = 0; 431 432 /* Reset result code. It may have been modified during a previous attempt */ 433 res = RES_OK; 434 435 /* Find the following surface along the direction of propagation */ 436 XD(trace_ray)(scn, rt_pos, in_dir, INF, enc_id, in_hit, &hit); 437 if(SXD_HIT_NONE(&hit)) break; 438 439 /* Retrieve the current position and normal */ 440 dX(add)(pos, rt_pos, dX(muld)(vec, in_dir, hit.distance)); 441 dX_set_fX(N, hit.normal); 442 dX(normalize(N, N)); 443 444 /* Retrieve the current interface properties */ 445 interf = scene_get_interface(scn, hit.prim.prim_id); 446 XD(setup_fragment)(&frag, pos, in_dir, time, N, &hit); 447 448 /* Check that the path reaches a valid interface. 449 * An invalid fragment may mean that the ray position is in a corner and the 450 * traced ray has missed the surface of that corner. To correct this, the 451 * ray position is moved slightly away from the corner before a ray is drawn 452 * in the same direction. This fallback solution is executed a number of 453 * times, after which, if the fragment is still invalid, it is considered 454 * that the numerical error cannot be mitigated. */ 455 res = check_interface(interf, &frag, nattempts == NATTEMPTS_MAX); 456 if(res != RES_OK && nattempts == NATTEMPTS_MAX) goto error; 457 ++nattempts; 458 459 if(res != RES_OK) { /* Mitigate numerical error (see above) */ 460 if(sdis_medium_get_type(interf->medium_front) == SDIS_SOLID) { 461 solid = interf->medium_front; 462 } else { 463 ASSERT(sdis_medium_get_type(interf->medium_back) == SDIS_SOLID); 464 solid = interf->medium_back; 465 } 466 467 /* Retrieves the delta of the solid that surrounds the boundary, as it is 468 * actually the only numerical parameter that says something about the 469 * system. */ 470 vtx.P[0] = pos[0]; 471 vtx.P[1] = pos[1]; 472 vtx.P[2] = pos[2]; 473 vtx.time = time; 474 delta = solid_get_delta(solid, &vtx); 475 476 XD(move_away_primitive_boundaries)(in_hit, delta, rt_pos); 477 } 478 } while(res != RES_OK); 479 480 exit: 481 *out_hit = hit; 482 *out_interf = interf; 483 *out_frag = frag; 484 return res; 485 error: 486 goto exit; 487 } 488 489 #include "sdis_Xd_end.h"