sdis_scene.c (17766B)
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.h" 17 #include "sdis_interface_c.h" 18 #include "sdis_scene_c.h" 19 #include "sdis_source_c.h" 20 21 #include <float.h> 22 #include <limits.h> 23 24 /* Generate the Generic functions of the scene */ 25 #define SDIS_XD_DIMENSION 2 26 #include "sdis_scene_Xd.h" 27 #define SDIS_XD_DIMENSION 3 28 #include "sdis_scene_Xd.h" 29 30 /******************************************************************************* 31 * Helper function 32 ******************************************************************************/ 33 static void 34 project_position 35 (const double V0[3], 36 const double E0[3], 37 const double N[3], 38 const double NxE1[3], 39 const double rcp_det, 40 const double pos[3], 41 double uvw[3]) 42 { 43 double T[3], Q[3], k; 44 ASSERT(V0 && E0 && N && NxE1 && pos && uvw); 45 46 /* Use Moller/Trumbore intersection test the compute the parametric 47 * coordinates of the intersection between the triangle and the ray 48 * `r = pos + N*d' */ 49 d3_sub(T, pos, V0); 50 uvw[0] = d3_dot(T, NxE1) * rcp_det; 51 d3_cross(Q, T, E0); 52 uvw[1] = d3_dot(Q, N) * rcp_det; 53 uvw[2] = 1.0 - uvw[0] - uvw[1]; 54 55 if(uvw[0] >= 0 && uvw[1] >= 0 && uvw[2] >= 0) {/* The ray hits the triangle */ 56 ASSERT(eq_eps(uvw[0] + uvw[1] + uvw[2], 1.0, 1.e-6)); 57 return; 58 } 59 60 /* Clamp barycentric coordinates to triangle edges */ 61 if(uvw[0] >= 0) { 62 if(uvw[1] >= 0) { 63 k = 1.0 / (uvw[0] + uvw[1]); 64 uvw[0] *= k; 65 uvw[1] *= k; 66 uvw[2] = 0; 67 } else if( uvw[2] >= 0) { 68 k = 1.0 / (uvw[0] + uvw[2]); 69 uvw[0] *= k; 70 uvw[1] = 0; 71 uvw[2] *= k; 72 } else { 73 ASSERT(uvw[0] >= 1.f); 74 d3(uvw, 1, 0, 0); 75 } 76 } else if(uvw[1] >= 0) { 77 if(uvw[2] >= 0) { 78 k = 1.0 / (uvw[1] + uvw[2]); 79 uvw[0] = 0; 80 uvw[1] *= k; 81 uvw[2] *= k; 82 } else { 83 ASSERT(uvw[1] >= 1); 84 d3(uvw, 0, 1, 0); 85 } 86 } else { 87 ASSERT(uvw[2] >= 1); 88 d3(uvw, 0, 0, 1); 89 } 90 } 91 92 static void 93 scene_release(ref_T * ref) 94 { 95 struct sdis_device* dev = NULL; 96 struct sdis_scene* scn = NULL; 97 ASSERT(ref); 98 scn = CONTAINER_OF(ref, struct sdis_scene, ref); 99 dev = scn->dev; 100 clear_properties(scn); 101 darray_interf_release(&scn->interfaces); 102 darray_medium_release(&scn->media); 103 darray_prim_prop_release(&scn->prim_props); 104 htable_enclosure_release(&scn->enclosures); 105 htable_d_release(&scn->tmp_hc_ub); 106 htable_key2prim2d_release(&scn->key2prim2d); 107 htable_key2prim3d_release(&scn->key2prim3d); 108 if(scn->s2d_view) S2D(scene_view_ref_put(scn->s2d_view)); 109 if(scn->s3d_view) S3D(scene_view_ref_put(scn->s3d_view)); 110 if(scn->senc2d_scn) SENC2D(scene_ref_put(scn->senc2d_scn)); 111 if(scn->senc3d_scn) SENC3D(scene_ref_put(scn->senc3d_scn)); 112 if(scn->source) SDIS(source_ref_put(scn->source)); 113 if(scn->radenv) SDIS(radiative_env_ref_put(scn->radenv)); 114 MEM_RM(dev->allocator, scn); 115 SDIS(device_ref_put(dev)); 116 } 117 118 /******************************************************************************* 119 * Exported functions 120 ******************************************************************************/ 121 res_T 122 sdis_scene_create 123 (struct sdis_device* dev, 124 const struct sdis_scene_create_args* args, 125 struct sdis_scene** out_scn) 126 { 127 return scene_create_3d(dev, args, out_scn); 128 } 129 130 res_T 131 sdis_scene_2d_create 132 (struct sdis_device* dev, 133 const struct sdis_scene_create_args* args, 134 struct sdis_scene** out_scn) 135 { 136 return scene_create_2d(dev, args, out_scn); 137 } 138 139 res_T 140 sdis_scene_ref_get(struct sdis_scene* scn) 141 { 142 if(!scn) return RES_BAD_ARG; 143 ref_get(&scn->ref); 144 return RES_OK; 145 } 146 147 res_T 148 sdis_scene_ref_put(struct sdis_scene* scn) 149 { 150 if(!scn) return RES_BAD_ARG; 151 ref_put(&scn->ref, scene_release); 152 return RES_OK; 153 } 154 155 res_T 156 sdis_scene_get_aabb 157 (const struct sdis_scene* scn, 158 double lower[], 159 double upper[]) 160 { 161 float low[3], upp[3]; 162 res_T res = RES_OK; 163 if(!scn || !lower || !upper) return RES_BAD_ARG; 164 165 if(scene_is_2d(scn)) { 166 res = s2d_scene_view_get_aabb(scn->s2d_view, low, upp); 167 if(res != RES_OK) return res; 168 d2_set_f2(lower, low); 169 d2_set_f2(upper, upp); 170 } else { 171 res = s3d_scene_view_get_aabb(scn->s3d_view, low, upp); 172 if(res != RES_OK) return res; 173 d3_set_f3(lower, low); 174 d3_set_f3(upper, upp); 175 } 176 return RES_OK; 177 } 178 179 res_T 180 sdis_scene_get_fp_to_meter 181 (const struct sdis_scene* scn, 182 double* fp_to_meter) 183 { 184 if(!scn || !fp_to_meter) return RES_BAD_ARG; 185 *fp_to_meter = scn->fp_to_meter; 186 return RES_OK; 187 } 188 189 res_T 190 sdis_scene_set_fp_to_meter 191 (struct sdis_scene* scn, 192 const double fp_to_meter) 193 { 194 if(!scn || fp_to_meter <= 0) return RES_BAD_ARG; 195 scn->fp_to_meter = fp_to_meter; 196 return RES_OK; 197 } 198 199 res_T 200 sdis_scene_get_temperature_range 201 (const struct sdis_scene* scn, 202 double t_range[2]) 203 { 204 if(!scn || !t_range) return RES_BAD_ARG; 205 t_range[0] = scn->tmin; 206 t_range[1] = scn->tmax; 207 return RES_OK; 208 } 209 210 res_T 211 sdis_scene_set_temperature_range 212 (struct sdis_scene* scn, 213 const double t_range[2]) 214 { 215 if(!scn || !t_range) return RES_BAD_ARG; 216 scn->tmin = t_range[0]; 217 scn->tmax = t_range[1]; 218 return RES_OK; 219 } 220 221 res_T 222 sdis_scene_find_closest_point 223 (const struct sdis_scene* scn, 224 const struct sdis_scene_find_closest_point_args* args, 225 size_t* iprim, 226 double uv[]) 227 { 228 if(!scn) return RES_BAD_ARG; 229 if(scene_is_2d(scn)) { 230 return scene_find_closest_point_2d(scn, args, iprim, uv); 231 } else { 232 return scene_find_closest_point_3d(scn, args, iprim, uv); 233 } 234 } 235 236 res_T 237 sdis_scene_get_boundary_position 238 (const struct sdis_scene* scn, 239 const size_t iprim, 240 const double uv[], 241 double pos[]) 242 { 243 if(!scn || !uv || !pos) return RES_BAD_ARG; 244 if(iprim >= scene_get_primitives_count(scn)) return RES_BAD_ARG; 245 246 if(scene_is_2d(scn)) { 247 struct s2d_primitive prim; 248 struct s2d_attrib attr; 249 float s = (float)uv[0]; 250 251 S2D(scene_view_get_primitive(scn->s2d_view, (unsigned int)iprim, &prim)); 252 S2D(primitive_get_attrib(&prim, S2D_POSITION, s, &attr)); 253 d2_set_f2(pos, attr.value); 254 } else { 255 struct s3d_primitive prim; 256 struct s3d_attrib attr; 257 float st[2]; 258 259 f2_set_d2(st, uv); 260 S3D(scene_view_get_primitive(scn->s3d_view, (unsigned int)iprim, &prim)); 261 S3D(primitive_get_attrib(&prim, S3D_POSITION, st, &attr)); 262 d3_set_f3(pos, attr.value); 263 } 264 return RES_OK; 265 } 266 267 res_T 268 sdis_scene_boundary_project_position 269 (const struct sdis_scene* scn, 270 const size_t iprim, 271 const double pos[], 272 double uv[]) 273 { 274 if(!scn || !pos || !uv) return RES_BAD_ARG; 275 if(iprim >= scene_get_primitives_count(scn)) return RES_BAD_ARG; 276 277 if(scene_is_2d(scn)) { 278 struct s2d_primitive prim; 279 struct s2d_attrib a; 280 double V[2][2]; /* Vertices */ 281 double E[2][3]; /* V0->V1 and V0->pos */ 282 double proj; 283 284 /* Retrieve the segment vertices */ 285 S2D(scene_view_get_primitive(scn->s2d_view, (unsigned int)iprim, &prim)); 286 S2D(segment_get_vertex_attrib(&prim, 0, S2D_POSITION, &a)); d2_set_f2(V[0], a.value); 287 S2D(segment_get_vertex_attrib(&prim, 1, S2D_POSITION, &a)); d2_set_f2(V[1], a.value); 288 289 /* Compute the parametric coordinate of the project of `pos' onto the 290 * segment.*/ 291 d2_sub(E[0], V[1], V[0]); 292 d2_normalize(E[0], E[0]); 293 d2_sub(E[1], pos, V[0]); 294 proj = d2_dot(E[0], E[1]); 295 296 uv[0] = CLAMP(proj, 0, 1); /* Clamp the parametric coordinate in [0, 1] */ 297 298 } else { 299 struct s3d_primitive prim; 300 struct s3d_attrib a; 301 double V[3][3]; /* Vertices */ 302 double E[2][3]; /* V0->V1 and V0->V2 edges */ 303 double N[3]; /* Normal */ 304 double NxE1[3], rcp_det; /* Muller/Trumboer triangle parameters */ 305 double uvw[3]; 306 307 S3D(scene_view_get_primitive(scn->s3d_view, (unsigned int)iprim, &prim)); 308 S3D(triangle_get_vertex_attrib(&prim, 0, S3D_POSITION, &a)); d3_set_f3(V[0], a.value); 309 S3D(triangle_get_vertex_attrib(&prim, 1, S3D_POSITION, &a)); d3_set_f3(V[1], a.value); 310 S3D(triangle_get_vertex_attrib(&prim, 2, S3D_POSITION, &a)); d3_set_f3(V[2], a.value); 311 d3_sub(E[0], V[1], V[0]); 312 d3_sub(E[1], V[2], V[0]); 313 d3_cross(N, E[0], E[1]); 314 315 /* Muller/Trumbore triangle parameters */ 316 d3_cross(NxE1, N, E[1]); 317 rcp_det = 1.0 / d3_dot(NxE1, E[0]); 318 319 /* Use the Muller/Trumbore intersection test to project `pos' onto the 320 * triangle and to retrieve the parametric coordinates of the projection 321 * point */ 322 project_position(V[0], E[0], N, NxE1, rcp_det, pos, uvw); 323 324 uv[0] = uvw[2]; 325 uv[1] = uvw[0]; 326 } 327 return RES_OK; 328 } 329 330 res_T 331 sdis_scene_get_senc2d_scene 332 (struct sdis_scene* scn, 333 struct senc2d_scene** senc2d_scn) 334 { 335 if(!scn || !senc2d_scn) return RES_BAD_ARG; 336 if(!scn->senc2d_scn) return RES_BAD_ARG; /* Scene is 3D */ 337 *senc2d_scn = scn->senc2d_scn; 338 return RES_OK; 339 } 340 341 res_T 342 sdis_scene_get_senc3d_scene 343 (struct sdis_scene* scn, 344 struct senc3d_scene** senc3d_scn) 345 { 346 if(!scn || !senc3d_scn) return RES_BAD_ARG; 347 if(!scn->senc3d_scn) return RES_BAD_ARG; /* Scene is 2D */ 348 *senc3d_scn = scn->senc3d_scn; 349 return RES_OK; 350 } 351 352 res_T 353 sdis_scene_get_s2d_scene_view 354 (struct sdis_scene* scn, 355 struct s2d_scene_view** s2d_view) 356 { 357 if(!scn || !s2d_view) return RES_BAD_ARG; 358 if(!scn->s2d_view) return RES_BAD_ARG; /* Scene is 3D */ 359 *s2d_view = scn->s2d_view; 360 return RES_OK; 361 } 362 363 res_T 364 sdis_scene_get_s3d_scene_view 365 (struct sdis_scene* scn, 366 struct s3d_scene_view** s3d_view) 367 { 368 if(!scn || !s3d_view) return RES_BAD_ARG; 369 if(!scn->s3d_view) return RES_BAD_ARG; /* Scene is 2D */ 370 *s3d_view = scn->s3d_view; 371 return RES_OK; 372 } 373 374 res_T 375 sdis_scene_get_dimension 376 (const struct sdis_scene* scn, enum sdis_scene_dimension* dim) 377 { 378 if(!scn || !dim) return RES_BAD_ARG; 379 *dim = scene_is_2d(scn) ? SDIS_SCENE_2D : SDIS_SCENE_3D; 380 return RES_OK; 381 } 382 383 res_T 384 sdis_scene_get_medium_spread 385 (struct sdis_scene* scn, 386 const struct sdis_medium* mdm, 387 double* out_spread) 388 { 389 struct htable_enclosure_iterator it, end; 390 double spread = 0; 391 res_T res = RES_OK; 392 393 if(!scn || !mdm || !out_spread) { 394 res = RES_BAD_ARG; 395 goto error; 396 } 397 398 htable_enclosure_begin(&scn->enclosures, &it); 399 htable_enclosure_end(&scn->enclosures, &end); 400 while(!htable_enclosure_iterator_eq(&it, &end)) { 401 const struct enclosure* enc = htable_enclosure_iterator_data_get(&it); 402 htable_enclosure_iterator_next(&it); 403 if(sdis_medium_get_id(mdm) == enc->medium_id) { 404 spread += enc->V; 405 } 406 } 407 *out_spread = spread; 408 409 exit: 410 return res; 411 error: 412 goto exit; 413 } 414 415 res_T 416 sdis_scene_get_device(struct sdis_scene* scn, struct sdis_device** device) 417 { 418 if(!scn || !device) return RES_BAD_ARG; 419 *device = scn->dev; 420 return RES_OK; 421 } 422 423 res_T 424 sdis_scene_get_source(struct sdis_scene* scn, struct sdis_source** source) 425 { 426 if(!scn || !source) return RES_BAD_ARG; 427 *source = scn->source; 428 return RES_OK; 429 } 430 431 res_T 432 sdis_scene_get_radiative_env 433 (struct sdis_scene* scn, 434 struct sdis_radiative_env** radenv) 435 { 436 if(!scn || !radenv) return RES_BAD_ARG; 437 *radenv = scn->radenv; 438 return RES_OK; 439 } 440 441 res_T 442 sdis_scene_get_s2d_primitive 443 (struct sdis_scene* scn, 444 const struct sdis_primkey* key, 445 struct s2d_primitive* out_prim) 446 { 447 struct s2d_primitive* prim = NULL; 448 449 if(!scn || !key || !out_prim || !scene_is_2d(scn)) return RES_BAD_ARG; 450 451 if((prim = htable_key2prim2d_find(&scn->key2prim2d, key)) == NULL) 452 return RES_BAD_ARG; 453 *out_prim = *prim; 454 return RES_OK; 455 } 456 457 res_T 458 sdis_scene_get_s3d_primitive 459 (struct sdis_scene* scn, 460 const struct sdis_primkey* key, 461 struct s3d_primitive* out_prim) 462 { 463 struct s3d_primitive* prim = NULL; 464 465 if(!scn || !key || !out_prim || scene_is_2d(scn)) return RES_BAD_ARG; 466 467 if((prim = htable_key2prim3d_find(&scn->key2prim3d, key)) == NULL) 468 return RES_BAD_ARG; 469 *out_prim = *prim; 470 return RES_OK; 471 } 472 473 /******************************************************************************* 474 * Local miscellaneous function 475 ******************************************************************************/ 476 struct sdis_interface* 477 scene_get_interface(const struct sdis_scene* scn, const unsigned iprim) 478 { 479 ASSERT(scn && iprim < darray_prim_prop_size_get(&scn->prim_props)); 480 return darray_prim_prop_cdata_get(&scn->prim_props)[iprim].interf; 481 } 482 483 res_T 484 scene_get_enclosure_id 485 (struct sdis_scene* scn, 486 const double pos[], 487 unsigned* enc_id) 488 { 489 return scene_is_2d(scn) 490 ? scene_get_enclosure_id_2d(scn, pos, enc_id) 491 : scene_get_enclosure_id_3d(scn, pos, enc_id); 492 } 493 494 res_T 495 scene_get_enclosure_id_in_closed_boundaries 496 (struct sdis_scene* scn, 497 const double pos[], 498 unsigned* enc_id) 499 { 500 return scene_is_2d(scn) 501 ? scene_get_enclosure_id_in_closed_boundaries_2d(scn, pos, enc_id) 502 : scene_get_enclosure_id_in_closed_boundaries_3d(scn, pos, enc_id); 503 } 504 505 res_T 506 scene_get_enclosure_medium 507 (struct sdis_scene* scn, 508 const struct enclosure* enc, 509 struct sdis_medium** out_mdm) 510 { 511 struct sdis_medium* mdm = NULL; 512 res_T res = RES_OK; 513 514 ASSERT(scn && enc && out_mdm); 515 516 /* Check that the enclosure doesn't surround multiple media */ 517 if(enc->medium_id == MEDIUM_ID_MULTI) { 518 log_warn(scn->dev, 519 "%s: invalid medium request. The enclosure includes several media.\n", 520 FUNC_NAME); 521 res = RES_BAD_OP; 522 goto error; 523 } 524 525 /* Obtain enclosure medium */ 526 ASSERT(enc->medium_id < darray_medium_size_get(&scn->media)); 527 mdm = darray_medium_data_get(&scn->media)[enc->medium_id]; 528 529 error: 530 *out_mdm = mdm; 531 goto exit; 532 exit: 533 mdm = NULL; 534 return res; 535 } 536 537 res_T 538 scene_compute_hash(const struct sdis_scene* scn, hash256_T hash) 539 { 540 struct sha256_ctx sha256_ctx; 541 size_t iprim, nprims; 542 int has_radenv = 0; 543 res_T res = RES_OK; 544 ASSERT(scn && hash); 545 546 sha256_ctx_init(&sha256_ctx); 547 548 if(scene_is_2d(scn)) { 549 S2D(scene_view_primitives_count(scn->s2d_view, &nprims)); 550 } else { 551 S3D(scene_view_primitives_count(scn->s3d_view, &nprims)); 552 } 553 #define SHA256_UPD(Var, Nb) \ 554 sha256_ctx_update(&sha256_ctx, (const char*)(Var), sizeof(*Var)*(Nb)) 555 556 has_radenv = scn->radenv != NULL; 557 558 SHA256_UPD(&has_radenv, 1); 559 SHA256_UPD(&scn->tmax, 1); 560 SHA256_UPD(&scn->fp_to_meter, 1); 561 562 if(scn->source) { 563 hash256_T src_hash; 564 source_compute_signature(scn->source, src_hash); 565 sha256_ctx_update(&sha256_ctx, src_hash, sizeof(hash256_T)); 566 } 567 568 FOR_EACH(iprim, 0, nprims) { 569 struct sdis_interface* interf = NULL; 570 size_t ivert; 571 572 if(scene_is_2d(scn)) { 573 struct s2d_primitive prim; 574 S2D(scene_view_get_primitive(scn->s2d_view, (unsigned)iprim, &prim)); 575 FOR_EACH(ivert, 0, 2) { 576 struct s2d_attrib attr; 577 S2D(segment_get_vertex_attrib(&prim, ivert, S2D_POSITION, &attr)); 578 SHA256_UPD(attr.value, 2); 579 } 580 } else { 581 struct s3d_primitive prim; 582 S3D(scene_view_get_primitive(scn->s3d_view, (unsigned)iprim, &prim)); 583 FOR_EACH(ivert, 0, 3) { 584 struct s3d_attrib attr; 585 S3D(triangle_get_vertex_attrib(&prim, ivert, S3D_POSITION, &attr)); 586 SHA256_UPD(attr.value, 3); 587 } 588 } 589 590 interf = scene_get_interface(scn, (unsigned)iprim); 591 SHA256_UPD(&interf->medium_front->type, 1); 592 SHA256_UPD(&interf->medium_front->id, 1); 593 SHA256_UPD(&interf->medium_back->type, 1); 594 SHA256_UPD(&interf->medium_back->id, 1); 595 } 596 #undef SHA256_UPD 597 sha256_ctx_finalize(&sha256_ctx, hash); 598 599 return res; 600 } 601 602 res_T 603 scene_check_primitive_index(const struct sdis_scene* scn, const size_t iprim) 604 { 605 res_T res = RES_OK; 606 ASSERT(scn); 607 608 if(iprim >= scene_get_primitives_count(scn)) { 609 log_err(scn->dev, 610 "%s: invalid primitive identifier `%lu'. " 611 "It must be in the [0 %lu] range.\n", 612 FUNC_NAME, 613 (unsigned long)iprim, 614 (unsigned long)scene_get_primitives_count(scn)-1); 615 res = RES_BAD_ARG; 616 goto error; 617 } 618 619 exit: 620 return res; 621 error: 622 goto exit; 623 } 624 625 res_T 626 scene_check_dimensionality_2d(const struct sdis_scene* scn) 627 { 628 res_T res = RES_OK; 629 ASSERT(scn); 630 if(scene_is_2d(scn) == 0) { 631 log_err(scn->dev, 632 "%s: expects a 2D scene while the input scene is 3D.\n", 633 FUNC_NAME); 634 res = RES_BAD_ARG; 635 goto error; 636 } 637 exit: 638 return res; 639 error: 640 goto exit; 641 } 642 643 res_T 644 scene_check_dimensionality_3d(const struct sdis_scene* scn) 645 { 646 res_T res = RES_OK; 647 ASSERT(scn); 648 if(scene_is_2d(scn) != 0) { 649 log_err(scn->dev, 650 "%s: expects a 3D scene while the input scene is 2D.\n", 651 FUNC_NAME); 652 res = RES_BAD_ARG; 653 goto error; 654 } 655 exit: 656 return res; 657 error: 658 goto exit; 659 } 660 661 res_T 662 scene_check_temperature_range(const struct sdis_scene* scn) 663 { 664 res_T res = RES_OK; 665 ASSERT(scn); 666 667 if(SDIS_TEMPERATURE_IS_UNKNOWN(scn->tmin)) { 668 log_err(scn->dev, 669 "%s the defined minimum temperature is unknown " 670 "when it is expected to be known.\n", 671 FUNC_NAME); 672 res = RES_BAD_ARG; 673 goto error; 674 } 675 676 if(SDIS_TEMPERATURE_IS_UNKNOWN(scn->tmax)) { 677 log_err(scn->dev, 678 "%s the defined maximum temperature is unknown " 679 "when it is expected to be known.\n", 680 FUNC_NAME); 681 res = RES_BAD_ARG; 682 goto error; 683 } 684 685 if(scn->tmin > scn->tmax) { 686 log_err(scn->dev, 687 "%s: defined temperature range degenerated -- [%g, %g] K\n", 688 FUNC_NAME, scn->tmin, scn->tmax); 689 res = RES_BAD_ARG; 690 goto error; 691 } 692 693 exit: 694 return res; 695 error: 696 goto exit; 697 }