star-enclosures-3d

Extract enclosures from 3D geometry
git clone git://git.meso-star.fr/star-enclosures-3d.git
Log | Files | Refs | README | LICENSE

senc3d_scene_analyze.c (90701B)


      1 /* Copyright (C) 2018-2020, 2023, 2024 |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 #define _POSIX_C_SOURCE 200112L
     17 
     18 #include "senc3d.h"
     19 #include "senc3d_device_c.h"
     20 #include "senc3d_scene_c.h"
     21 #include "senc3d_scene_analyze_c.h"
     22 #include "senc3d_internal_types.h"
     23 
     24 #include <rsys/rsys.h>
     25 #include <rsys/float3.h>
     26 #include <rsys/double33.h>
     27 #include <rsys/mem_allocator.h>
     28 #include <rsys/hash_table.h>
     29 #include <rsys/dynamic_array.h>
     30 #include <rsys/dynamic_array_uint.h>
     31 #include <rsys/dynamic_array_uchar.h>
     32 #include <rsys/clock_time.h>
     33 #include <rsys/str.h>
     34 
     35 #include <star/s3d.h>
     36 
     37 #include <omp.h>
     38 #include <math.h>
     39 #include <limits.h>
     40 #include <stdlib.h>
     41 
     42 #define CC_DESCRIPTOR_NULL__ {\
     43    0, 0, {{DBL_MAX,-DBL_MAX}, {DBL_MAX,-DBL_MAX}, {DBL_MAX,-DBL_MAX}}, NULL,\
     44    0, INT_MAX, VRTX_NULL__, 0,\
     45    CC_ID_NONE, CC_GROUP_ROOT_NONE, ENCLOSURE_NULL__,\
     46    { TRG_NULL__, 0}\
     47 }
     48 #ifdef COMPILER_GCC
     49   #pragma GCC diagnostic push
     50   #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
     51 #endif
     52 const struct cc_descriptor CC_DESCRIPTOR_NULL = CC_DESCRIPTOR_NULL__;
     53 #ifdef COMPILER_GCC
     54   #pragma GCC diagnostic pop
     55 #endif
     56 
     57 #define DARRAY_NAME component_id
     58 #define DARRAY_DATA component_id_t
     59 #include <rsys/dynamic_array.h>
     60 
     61 #define HTABLE_NAME component_id
     62 #define HTABLE_KEY component_id_t
     63 #define HTABLE_DATA char
     64 #include <rsys/hash_table.h>
     65 
     66 /* A threshold for dot products:
     67  * If ray.normal < threshold we suspect accuracy could be a problem */
     68 #define DOT_THRESHOLD 0.0001f
     69 
     70 enum ctx_type {
     71   CTX0,
     72   CTX1,
     73   CTX2
     74 };
     75 
     76 struct filter_ctx {
     77   enum ctx_type type;
     78   struct senc3d_scene* scn;
     79   struct s3d_scene_view* view;
     80   component_id_t origin_component;
     81   struct darray_triangle_comp* triangles_comp;
     82   struct darray_ptr_component_descriptor* components;
     83   /* Tmp data used across filter calls */
     84   double current_6volume;
     85   int cpt;
     86   float s;
     87   /* Result of CTX1 hit */
     88   component_id_t hit_component;
     89   float hit_dir[3], hit_dist;
     90   struct s3d_primitive hit_prim;
     91 };
     92 
     93 #define HTABLE_NAME overlap
     94 #define HTABLE_KEY trg_id_t
     95 #define HTABLE_DATA char
     96 #include <rsys/hash_table.h>
     97 
     98 /******************************************************************************
     99  * Helper function
    100  *****************************************************************************/
    101 static INLINE int
    102 neighbour_cmp(const void* w1, const void* w2)
    103 {
    104   const double a1 = ((struct neighbour_info*)w1)->angle;
    105   const double a2 = ((struct neighbour_info*)w2)->angle;
    106   return (a1 > a2) - (a1 < a2);
    107 }
    108 
    109 /* Returns 1 if cc2 is inside cc1, 0 otherwise */
    110 static FINLINE int
    111 is_component_inside
    112   (struct cc_descriptor* cc1,
    113    struct cc_descriptor* cc2,
    114    struct filter_ctx* ctx)
    115 {
    116   int i;
    117   side_id_t side;
    118   const struct triangle_in* trg = NULL;
    119   double pt[3] = { 0, 0, 0 };
    120   float org[3], dir[3] = { 0, 0, 1 }, rg[2] = { 0, FLT_MAX };
    121   struct filter_ctx ctx2;
    122   struct s3d_hit hit = S3D_HIT_NULL;
    123   const struct triangle_comp*
    124     trg_comp = darray_triangle_comp_cdata_get(ctx->triangles_comp);
    125   const struct triangle_in*
    126     triangles = darray_triangle_in_cdata_get(&ctx->scn->triangles_in);
    127   const union double3* vertices = darray_position_cdata_get(&ctx->scn->vertices);
    128   ASSERT(cc1 && cc2 && ctx);
    129   /* Volume must be compatible */
    130   if(fabs(cc2->_6volume) >= fabs(cc1->_6volume))
    131     return 0;
    132   /* Bbox must be compatible */
    133   for(i = 0; i < 3; i++) {
    134     if(cc2->bbox[i][0] < cc1->bbox[i][0] || cc2->bbox[i][1] > cc1->bbox[i][1])
    135       return 0;
    136   }
    137   /* Check if component cc2 is inside component cc1.
    138    * We already know that bbox and volume allow cc2 to fit inside component
    139    * ccc1, but it is not enough.
    140    * The method is to cast a ray from cc2 and count the number of times it
    141    * crosses component cc1; as components must not overlap, testing from a
    142    * single point is OK, as long as the point is not on cc1 boundary (it is on
    143    * cc2 boundary, though). */
    144   for(side = cc2->side_range.first; side <= cc2->side_range.last; side++) {
    145     /* Find a triangle on cc2 boundary that is not on cc1 boundary (it exists,
    146      * as the 2 components cannot share all their triangles) */
    147     trg_id_t t = TRGSIDE_2_TRG(side);
    148     const component_id_t* candidate_comp = trg_comp[t].component;
    149     if(candidate_comp[SENC3D_FRONT] != cc2->cc_id
    150         && candidate_comp[SENC3D_BACK] != cc2->cc_id)
    151       continue;
    152     if(candidate_comp[SENC3D_FRONT] == cc1->cc_id
    153         || candidate_comp[SENC3D_BACK] == cc1->cc_id)
    154       continue;
    155     /* This triangle is OK */
    156     trg = triangles + t;
    157     break;
    158   }
    159   ASSERT(trg != NULL);
    160   /* Any point on trg can do the trick: use the barycenter */
    161   FOR_EACH(i, 0, 3) {
    162     vrtx_id_t v = trg->vertice_id[i];
    163     ASSERT(v < darray_position_size_get(&ctx->scn->vertices));
    164     d3_add(pt, pt, vertices[v].vec);
    165   }
    166   d3_divd(pt, pt, 3);
    167   f3_set_d3(org, pt);
    168   /* Trace a ray and count intersections with component c */
    169   ctx2.type = CTX2;
    170   ctx2.triangles_comp = ctx->triangles_comp;
    171   ctx2.cpt = 0;
    172   ctx2.origin_component = cc1->cc_id;
    173   S3D(scene_view_trace_ray(ctx->view, org, dir, rg, &ctx2, &hit));
    174   /* cc2 is not inside cc1 if cpt is even */
    175   if(ctx2.cpt % 2 == 0) return 0;
    176   return 1;
    177 }
    178 
    179 static side_id_t
    180 get_side_not_in_connex_component
    181   (const side_id_t last_side,
    182    const struct trgside* trgsides,
    183    const uchar* processed,
    184    side_id_t* first_side_not_in_component,
    185    const medium_id_t medium)
    186 {
    187   ASSERT(trgsides && processed && first_side_not_in_component);
    188   {
    189     side_id_t i = *first_side_not_in_component;
    190     while(i <= last_side
    191       && (trgsides[i].medium != medium
    192         || (processed[TRGSIDE_2_TRG(i)] & TRGSIDE_2_SIDEFLAG(i))))
    193       ++i;
    194 
    195     *first_side_not_in_component = i + 1;
    196     if(i > last_side) return SIDE_NULL__;
    197     return i;
    198   }
    199 }
    200 
    201 /* Here unsigned are required by s3d API */
    202 static void
    203 get_scn_indices
    204   (const unsigned itri,
    205    unsigned ids[3],
    206    void* ctx)
    207 {
    208   int i;
    209   const struct senc3d_scene* scene = ctx;
    210   const struct triangle_in* trg =
    211     darray_triangle_in_cdata_get(&scene->triangles_in) + itri;
    212   FOR_EACH(i, 0, 3) {
    213     ASSERT(trg->vertice_id[i] < scene->nverts);
    214     ASSERT(trg->vertice_id[i] <= UINT_MAX);
    215     ids[i] = (unsigned)trg->vertice_id[i]; /* Back to s3d API type */
    216   }
    217 }
    218 
    219 /* Here unsigned are required by s3d API */
    220 static void
    221 get_scn_position
    222   (const unsigned ivert,
    223    float pos[3],
    224    void* ctx)
    225 {
    226   const struct senc3d_scene* scene = ctx;
    227   const union double3* pt =
    228     darray_position_cdata_get(&scene->vertices) + ivert;
    229   f3_set_d3(pos, pt->vec);
    230 }
    231 
    232 static int
    233 self_hit_filter
    234   (const struct s3d_hit* hit,
    235    const float ray_org[3],
    236    const float ray_dir[3],
    237    const float ray_range[2],
    238    void* ray_data,
    239    void* filter_data)
    240 {
    241   struct filter_ctx* ctx = ray_data;
    242 
    243   (void)ray_org; (void)ray_range; (void)filter_data;
    244   ASSERT(ctx);
    245   ASSERT(hit->uv[0] == CLAMP(hit->uv[0], 0, 1));
    246   ASSERT(hit->uv[1] == CLAMP(hit->uv[1], 0, 1));
    247 
    248   switch (ctx->type) {
    249     default: FATAL("Invalid");
    250 
    251     case CTX2: {
    252       /* The filter is used to count the hits on some component along an
    253        * infinite ray */
    254       const struct triangle_comp* trg_comp;
    255       const component_id_t* hit_comp;
    256       ASSERT(hit->prim.prim_id
    257         < darray_triangle_comp_size_get(ctx->triangles_comp));
    258       trg_comp = darray_triangle_comp_cdata_get(ctx->triangles_comp);
    259       hit_comp = trg_comp[hit->prim.prim_id].component;
    260       if(hit_comp[SENC3D_FRONT] == ctx->origin_component
    261         || hit_comp[SENC3D_BACK] == ctx->origin_component)
    262       {
    263         ctx->cpt++;
    264       }
    265       return 1; /* Reject to continue counting */
    266     }
    267 
    268     case CTX0: {
    269       /* This filter is called from a closest point query from a point belonging
    270        * to origin_component. The returned hit is used to determine the search
    271        * radius for CTX1 main computation. */
    272       const struct triangle_comp*
    273         trg_comp = darray_triangle_comp_cdata_get(ctx->triangles_comp);
    274       const component_id_t*
    275         hit_comp = trg_comp[hit->prim.prim_id].component;
    276       const component_id_t oc = ctx->origin_component;
    277       vrtx_id_t other_id;
    278       struct cc_descriptor* const* comp_descriptors
    279         = darray_ptr_component_descriptor_cdata_get(ctx->components);
    280       size_t compsz = darray_ptr_component_descriptor_size_get(ctx->components);
    281       const union double3*
    282         vertices = darray_position_cdata_get(&ctx->scn->vertices);
    283       const double org_z = vertices[comp_descriptors[oc]->max_z_vrtx_id].pos.z;
    284       float s = 0, hit_normal[3], rdir[3];
    285       enum senc3d_side hit_side;
    286       const int log_components =
    287         ctx->scn->convention & SENC3D_LOG_COMPONENTS_INFORMATION;
    288 
    289       ASSERT(hit->prim.prim_id
    290         < darray_triangle_comp_size_get(ctx->triangles_comp));
    291       ASSERT(hit_comp[SENC3D_FRONT] < compsz);
    292       ASSERT(hit_comp[SENC3D_BACK] < compsz);
    293       (void)compsz; /* Avoid "unused variable" warning */
    294 
    295       /* Hit acceptance must be coherent at CTX0 and FTCX1 stages:
    296        * the search radius as found at stage CTX0 must include the hit that
    297        * stage CTX1 will select using an infinite radius */
    298       if(hit_comp[SENC3D_FRONT] == oc || hit_comp[SENC3D_BACK] == oc) {
    299         return 1; /* Self hit, reject */
    300       }
    301       if(hit->distance == 0) {
    302         /* origin component is in contact with some other components
    303          * We will need further exploration to know if they should be considered
    304          * Accepting hit at distance 0 => radius is definitively 0 */
    305         int n;
    306 
    307         /* If same component, process only once */
    308         FOR_EACH(n, 0, (hit_comp[SENC3D_FRONT] == hit_comp[SENC3D_BACK] ? 1 : 2)) {
    309           const enum senc3d_side sides[2] = { SENC3D_FRONT, SENC3D_BACK };
    310           component_id_t c = hit_comp[sides[n]];
    311           ASSERT(c < darray_ptr_component_descriptor_size_get(ctx->components));
    312           if(comp_descriptors[c]->is_outer_border) {
    313             /* The inner component we are trying to link can only be linked to
    314              * an outer component if it is inside */
    315             if(!is_component_inside(comp_descriptors[c],
    316                   comp_descriptors[oc], ctx))
    317             {
    318               continue;
    319             }
    320 
    321             if(log_components) {
    322               #pragma omp critical
    323               printf("Component #%u: decreasing search radius "
    324                   "(R=%g, n=%g,%g,%g, components: %u, %u)\n",
    325                   oc, hit->distance, SPLIT3(hit->normal),
    326                   hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]);
    327             }
    328             return 0;
    329           } else {
    330             /* c is an inner component */
    331             vrtx_id_t c_z_id;
    332             /* The inner component we are trying to link can only be linked to
    333              * another inner component if (at least partly) above and not inside */
    334             c_z_id = comp_descriptors[c]->max_z_vrtx_id;
    335             ASSERT(c_z_id < darray_position_size_get(&ctx->scn->vertices));
    336             ASSERT(vertices[c_z_id].pos.z >= org_z);
    337             if(vertices[c_z_id].pos.z == org_z) {
    338               continue; /* Not above */
    339             }
    340             if(is_component_inside(comp_descriptors[c],
    341                   comp_descriptors[oc], ctx))
    342             {
    343               continue; /* Inside */
    344             }
    345             if(log_components) {
    346               #pragma omp critical
    347               printf("Component #%u: decreasing search radius "
    348                   "(R=%g, n=%g,%g,%g, components: %u, %u)\n",
    349                   oc, hit->distance, SPLIT3(hit->normal),
    350                   hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]);
    351             }
    352             return 0;
    353           }
    354         }
    355         return 1;
    356       }
    357 
    358       ASSERT(hit->distance > 0);
    359       if(hit_comp[SENC3D_FRONT] == hit_comp[SENC3D_BACK]) {
    360         /* Hit component is known, check if above */
    361         other_id = comp_descriptors[hit_comp[SENC3D_FRONT]]->max_z_vrtx_id;
    362         ASSERT(other_id < darray_position_size_get(&ctx->scn->vertices));
    363         if(vertices[other_id].pos.z <= org_z) {
    364           return 1;
    365         }
    366         if(log_components) {
    367           #pragma omp critical
    368           printf("Component #%u: decreasing search radius "
    369               "(R=%g, n=%g,%g,%g, components: %u, %u)\n",
    370               oc, hit->distance, SPLIT3(hit->normal),
    371               hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]);
    372         }
    373         return 0;
    374       }
    375 
    376       /* Compute hit side */
    377       /* For s to be comparable, vectors must be normalized */
    378       f3_normalize(hit_normal, hit->normal);
    379       f3_normalize(rdir, ray_dir);
    380       s = f3_dot(rdir, hit_normal); /* Can be NaN for tiny distances */
    381       if(isnan(s)) {
    382         /* Try to fix it */
    383         f3_divf(rdir, ray_dir, hit->distance);
    384         f3_normalize(rdir, rdir);
    385         s = f3_dot(rdir, hit_normal);
    386         ASSERT(!isnan(s));
    387       }
    388 
    389       if(fabsf(s) < DOT_THRESHOLD) {
    390         /* We cannot know for sure which side to consider */
    391         vrtx_id_t i1 = comp_descriptors[hit_comp[SENC3D_FRONT]]->max_z_vrtx_id;
    392         vrtx_id_t i2 = comp_descriptors[hit_comp[SENC3D_BACK]]->max_z_vrtx_id;
    393         double possible_z = MMIN(vertices[i1].pos.z, vertices[i2].pos.z);
    394         if(possible_z > org_z) {
    395           /* Both components are above origin component => keep */
    396           if(log_components) {
    397             #pragma omp critical
    398             printf("Component #%u: decreasing search radius "
    399                 "(R=%g, n=%g,%g,%g, components: %u, %u)\n",
    400                 oc, hit->distance, SPLIT3(hit->normal),
    401                 hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]);
    402           }
    403           return 0;
    404         }
    405         /* Cannot be sure => the safest choice is to reject */
    406         return 1;
    407       }
    408       /* Determine which side was hit */
    409       hit_side =
    410         ((s < 0) /* Facing geometrical normal of hit */
    411          == ((ctx->scn->convention & SENC3D_CONVENTION_NORMAL_FRONT) != 0))
    412         /* Warning: following Embree 2 convention for geometrical normals,
    413          * the Star3D hit normal is left-handed while star-enclosures-3d uses
    414          * right-handed convention */
    415         ? SENC3D_BACK : SENC3D_FRONT;
    416       other_id = comp_descriptors[hit_comp[hit_side]]->max_z_vrtx_id;
    417       ASSERT(other_id < darray_position_size_get(&ctx->scn->vertices));
    418       if(vertices[other_id].pos.z <= org_z) {
    419         return 1;
    420       }
    421       if(log_components) {
    422         #pragma omp critical
    423         printf("Component #%u: decreasing search radius "
    424             "(R=%g, n=%g,%g,%g, components: %u, %u)\n",
    425             oc, hit->distance, SPLIT3(hit->normal),
    426             hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]);
    427       }
    428       return 0;
    429     }
    430 
    431     case CTX1: {
    432       /* This filter is called from a closest point query from a point belonging
    433        * to origin_component. The returned hit is used to determine a component
    434        * to which origin_component is linked. At a later stage the algorithm
    435        * process linked components to determine their relative inclusions.
    436        *
    437        * This filter is called with a search distance that has been ajusted in
    438        * CTX0 filter. This distance must be left unchanged to ensure visiting
    439        * all the surfaces at the determined distance: allways reject hits to
    440        * avoid decreasing search distance.
    441        *
    442        * For each hit, the filter computes if the hit is on a component above
    443        * origin_component (that is with >= Z).
    444        * If the hit is distant (dist>0), we just keep the hit as a valid candidate,
    445        * but things get more tricky when dist==0 (ray_org is a vertex where some
    446        * other components can be in contact with origin_component).
    447        * In this case, one of the other components can include the origin_component
    448        * (greater volume needed), or they can be disjoint, with (at least) ray_org
    449        * as a common vertex (they can also partially intersect, but this is invalid
    450        * and remains undetected by star enclosures). */
    451       struct cc_descriptor* const* comp_descriptors
    452         = darray_ptr_component_descriptor_cdata_get(ctx->components);
    453       const struct triangle_comp*
    454         trg_comp = darray_triangle_comp_cdata_get(ctx->triangles_comp);
    455       const component_id_t*
    456         hit_comp = trg_comp[hit->prim.prim_id].component;
    457       const union double3*
    458         vertices = darray_position_cdata_get(&ctx->scn->vertices);
    459       enum senc3d_side hit_side;
    460       float s = 0, hit_normal[3], rdir[3];
    461       const int log_components =
    462         ctx->scn->convention & SENC3D_LOG_COMPONENTS_INFORMATION;
    463       const component_id_t oc = ctx->origin_component;
    464       /* Links must be upward to avoid creating loops
    465        * Instead of comparing hit.z VS origin.z, compare max_Z of components
    466        * Here we keep max_z of the origin component that will be used for these
    467        * comparisons */
    468       const double org_z = vertices[comp_descriptors[oc]->max_z_vrtx_id].pos.z;
    469       vrtx_id_t other_id;
    470 
    471       ASSERT(hit->prim.prim_id
    472         < darray_triangle_comp_size_get(ctx->triangles_comp));
    473 
    474       if(log_components) {
    475         #pragma omp critical
    476         printf("Component #%u: investigating hit "
    477             "(d=%g, n=%g,%g,%g, components: %u, %u)\n",
    478             oc, hit->distance, SPLIT3(hit->normal),
    479             hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]);
    480       }
    481 
    482       /* Has CTX1 filter do not reduce search radius, hits can be processed
    483        * that are at farther distance than the current best candidate: we need
    484        * to reject them here */
    485       if(hit->distance > ctx->hit_dist) {
    486         /* No improvement */
    487         if(log_components) {
    488           #pragma omp critical
    489           printf("Component #%u: further away (%g): reject\n",
    490               oc, ctx->hit_dist);
    491         }
    492         return 1;
    493       }
    494 
    495       /* Hit acceptance must be coherent at CTX0 and FTCX1 stages:
    496        * the search radius as found at stage CTX0 must include the hit that
    497        * stage CTX1 will select using an infinite radius */
    498       if(hit_comp[SENC3D_FRONT] == oc || hit_comp[SENC3D_BACK] == oc) {
    499         /* Self hit */
    500         if(log_components) {
    501           #pragma omp critical
    502           printf("Component #%u: self hit: reject\n", oc);
    503         }
    504         return 1;
    505       }
    506 
    507       if(hit->distance == 0) {
    508         /* origin_component is in contact with some other components
    509          * We will need further exploration to know if they should be considered */
    510         int n;
    511 
    512         /* If same component, process only once */
    513         FOR_EACH(n, 0, (hit_comp[SENC3D_FRONT] == hit_comp[SENC3D_BACK] ? 1 : 2)) {
    514           const enum senc3d_side sides[2] = { SENC3D_FRONT, SENC3D_BACK };
    515           component_id_t c = hit_comp[sides[n]];
    516           ASSERT(c < darray_ptr_component_descriptor_size_get(ctx->components));
    517           if(c == ctx->hit_component) {
    518             /* Cannot change ctx->hit_component */
    519             if(log_components) {
    520               #pragma omp critical
    521               printf("Component #%u: hit component #%u and already linked to it:"
    522                  " reject\n",
    523                  oc, c);
    524             }
    525             continue;
    526           }
    527           if(comp_descriptors[c]->is_outer_border) {
    528             double v;
    529             /* The inner component we are trying to link can only be linked to
    530              * an outer component if it is inside */
    531             if(log_components) {
    532               #pragma omp critical
    533               printf("Component #%u: hit outer component #%u\n", oc, c);
    534             }
    535             if(!is_component_inside(comp_descriptors[c],
    536                   comp_descriptors[oc], ctx))
    537             {
    538               if(log_components) {
    539                 #pragma omp critical
    540                 printf("Component #%u: not inside: reject\n", oc);
    541               }
    542               continue;
    543             }
    544             v = fabs(comp_descriptors[c]->_6volume);
    545             /* If already linked to an inner component, prefer an outer one
    546              * regardless of their respective volumes.
    547              * If origin_component is inside several outer components, the one
    548              * we are looking for is the smallest one (to manage outer component
    549              * inside another outer component). */
    550             if((ctx->hit_component != COMPONENT_NULL__
    551                   && !comp_descriptors[ctx->hit_component]->is_outer_border)
    552                 || v < ctx->current_6volume) {
    553               ctx->hit_component = c;
    554               ctx->current_6volume = v;
    555               ctx->hit_dist = 0;
    556               ctx->hit_prim = hit->prim;
    557               if(log_components) {
    558                 if(v < ctx->current_6volume) {
    559                   #pragma omp critical
    560                   printf("Component #%u: currently the smaller one: "
    561                       "keep component #%u\n",
    562                       oc, ctx->hit_component);
    563                 } else {
    564                   #pragma omp critical
    565                   printf("Component #%u: change from inner to outer: "
    566                       "keep component #%u\n",
    567                       oc, ctx->hit_component);
    568                 }
    569               }
    570             } else {
    571               if(log_components) {
    572                 #pragma omp critical
    573                 printf("Component #%u: not the smaller one: reject\n", oc);
    574               }
    575               continue;
    576             }
    577           } else {
    578             /* c is an inner component */
    579             vrtx_id_t c_z_id;
    580             double v;
    581             /* If we've already found a valid outer component, inner components
    582              * should not be considered anymore */
    583             if(log_components) {
    584               #pragma omp critical
    585               printf("Component #%u: hit inner component #%u\n", oc, c);
    586             }
    587             if(ctx->hit_component != COMPONENT_NULL__
    588                 && comp_descriptors[ctx->hit_component]->is_outer_border)
    589             {
    590               if(log_components) {
    591                 #pragma omp critical
    592                 printf("Component #%u: already in an outer component: reject\n",
    593                     oc);
    594               }
    595               continue;
    596             }
    597             /* The inner component we are trying to link can only be linked to
    598              * another inner component if (at least partly) above it and not
    599              * inside */
    600             c_z_id = comp_descriptors[c]->max_z_vrtx_id;
    601             ASSERT(c_z_id < darray_position_size_get(&ctx->scn->vertices));
    602             ASSERT(vertices[c_z_id].pos.z >= org_z);
    603             if(vertices[c_z_id].pos.z == org_z) {
    604               if(log_components) {
    605                 #pragma omp critical
    606                 printf("Component #%u: not (even in part) above: reject\n", oc);
    607               }
    608               continue; /* Not above */
    609             }
    610             if(is_component_inside(comp_descriptors[c],
    611                   comp_descriptors[oc], ctx))
    612             {
    613               if(log_components) {
    614                 #pragma omp critical
    615                 printf("Component #%u: not outside: reject\n", oc);
    616               }
    617               continue; /* Inside */
    618             }
    619             v = fabs(comp_descriptors[c]->_6volume);
    620             /* If origin_component is facing several inner components, the one
    621              * we are looking for is the largest one (to manage inner component
    622              * inside another inner component) */
    623             if(ctx->current_6volume == DBL_MAX || v > ctx->current_6volume) {
    624               ctx->hit_component = c;
    625               ctx->current_6volume = v;
    626               ctx->hit_dist = 0;
    627               ctx->hit_prim = hit->prim;
    628               if(log_components) {
    629                 #pragma omp critical
    630                 printf("Component #%u: currently the bigger one: "
    631                     "keep component #%u\n",
    632                     oc, ctx->hit_component);
    633               }
    634             } else {
    635               if(log_components) {
    636                 #pragma omp critical
    637                 printf("Component #%u: not the bigger one: reject\n", oc);
    638               }
    639               continue;
    640             }
    641           }
    642         }
    643         return 1;
    644       }
    645 
    646       ASSERT(hit->distance > 0);
    647       if(hit_comp[SENC3D_FRONT] == hit_comp[SENC3D_BACK]) {
    648         /* Easy case and hit component is known */
    649         other_id = comp_descriptors[hit_comp[SENC3D_FRONT]]->max_z_vrtx_id;
    650         ASSERT(other_id < darray_position_size_get(&ctx->scn->vertices));
    651         if(vertices[other_id].pos.z <= org_z) {
    652           if(log_components) {
    653             #pragma omp critical
    654             printf("Component #%u: 2 sides, not above: reject\n", oc);
    655           }
    656           return 1;
    657         }
    658         ctx->hit_component = hit_comp[SENC3D_FRONT];
    659         ctx->s = 1;
    660         f3_set(ctx->hit_dir, ray_dir);
    661         ctx->hit_dist = hit->distance;
    662         ctx->hit_prim = hit->prim;
    663         if(log_components) {
    664           #pragma omp critical
    665           printf("Component #%u: 2 sides with same component: "
    666               "keep component #%u\n",
    667               oc, ctx->hit_component);
    668         }
    669         return 1;
    670       }
    671 
    672       /* Compute hit side */
    673       /* For s to be comparable, vectors must be normalized */
    674       f3_normalize(hit_normal, hit->normal);
    675       f3_normalize(rdir, ray_dir);
    676       s = f3_dot(rdir, hit_normal); /* Can be NaN for tiny distances */
    677       if(isnan(s)) {
    678         /* Try to fix it */
    679         f3_divf(rdir, ray_dir, hit->distance);
    680         f3_normalize(rdir, rdir);
    681         s = f3_dot(rdir, hit_normal);
    682         ASSERT(!isnan(s));
    683         if(log_components) {
    684           #pragma omp critical
    685           printf("Component #%u: had to fix s (was NaN)\n", oc);
    686         }
    687       }
    688 
    689       if(ctx->hit_dist == hit->distance && fabsf(ctx->s) >= fabsf(s)) {
    690         /* Same distance with no s improvement: keep the previous hit */
    691         if(log_components) {
    692           #pragma omp critical
    693           printf("Component #%u: not improving s (%g VS %g): reject\n",
    694               oc, s, ctx->s);
    695         }
    696         return 1;
    697       }
    698 
    699       if(fabsf(s) < DOT_THRESHOLD) {
    700         /* We cannot know for sure which side to consider */
    701         vrtx_id_t i1 = comp_descriptors[hit_comp[SENC3D_FRONT]]->max_z_vrtx_id;
    702         vrtx_id_t i2 = comp_descriptors[hit_comp[SENC3D_BACK]]->max_z_vrtx_id;
    703         double possible_z = MMAX(vertices[i1].pos.z, vertices[i2].pos.z);
    704         if(possible_z <= org_z) {
    705           /* None of the components are above origin component => reject */
    706           if(log_components) {
    707             #pragma omp critical
    708             printf("Component #%u: none of the components above: reject\n",
    709                 oc);
    710           }
    711           return 1;
    712         }
    713         ctx->hit_component = COMPONENT_NULL__;
    714         ctx->s = s;
    715         f3_set(ctx->hit_dir, ray_dir);
    716         ctx->hit_dist = hit->distance;
    717         ctx->hit_prim = hit->prim;
    718         if(log_components) {
    719           #pragma omp critical
    720           printf("Component #%u: tiny s (%g): "
    721               "keep but don't know the component\n",
    722               oc, s);
    723         }
    724         return 1;
    725       }
    726       /* Determine which side was hit */
    727       hit_side =
    728         ((s < 0) /* Facing geometrical normal of hit */
    729          == ((ctx->scn->convention & SENC3D_CONVENTION_NORMAL_FRONT) != 0))
    730         /* Warning: following Embree 2 convention for geometrical normals,
    731          * the Star3D hit normal is left-handed while star-enclosures-3d uses
    732          * right-handed convention */
    733         ? SENC3D_BACK : SENC3D_FRONT;
    734       other_id = comp_descriptors[hit_comp[hit_side]]->max_z_vrtx_id;
    735       ASSERT(other_id < darray_position_size_get(&ctx->scn->vertices));
    736       if(vertices[other_id].pos.z <= org_z) {
    737         if(log_components) {
    738           #pragma omp critical
    739           printf("Component #%u: standard s, not (even in part) above: reject\n",
    740               oc);
    741         }
    742         return 1;
    743       }
    744       ctx->hit_component = hit_comp[hit_side];
    745       ctx->s = s;
    746       f3_set(ctx->hit_dir, ray_dir);
    747       ctx->hit_dist = hit->distance;
    748       ctx->hit_prim = hit->prim;
    749       if(log_components) {
    750         #pragma omp critical
    751         printf("Component #%u: standard s, (%g): keep component #%u\n",
    752             oc, s, ctx->hit_component);
    753       }
    754       return 1;
    755     }
    756   }
    757 }
    758 
    759 static void
    760 extract_connex_components
    761   (struct senc3d_scene* scn,
    762    struct trgside* trgsides,
    763    struct darray_ptr_component_descriptor* connex_components,
    764    const struct darray_triangle_tmp* triangles_tmp_array,
    765    struct darray_triangle_comp* triangles_comp_array,
    766    struct s3d_scene_view** s3d_view,
    767    ATOMIC* component_count,
    768    /* Shared error status.
    769     * We accept to overwrite an error with a different error */
    770    res_T* p_res)
    771 {
    772   /* This function is called from an omp parallel block and executed
    773    * concurrently. */
    774   struct mem_allocator* alloc;
    775   int64_t m_idx; /* OpenMP requires a signed type for the for loop variable */
    776   struct darray_side_id stack;
    777   const union double3* positions;
    778   const struct triangle_tmp* triangles_tmp;
    779   struct triangle_comp* triangles_comp;
    780   /* An array to flag sides when processed */
    781   uchar* processed;
    782   /* An array to store the component being processed */
    783   struct darray_side_id current_component;
    784   /* A bool array to store media of the component being processed */
    785   uchar* current_media = NULL;
    786   size_t sz, ii;
    787 
    788   ASSERT(scn && trgsides && connex_components && triangles_tmp_array
    789     && triangles_comp_array && s3d_view && component_count && p_res);
    790   alloc = scn->dev->allocator;
    791   positions = darray_position_cdata_get(&scn->vertices);
    792   triangles_tmp = darray_triangle_tmp_cdata_get(triangles_tmp_array);
    793   triangles_comp = darray_triangle_comp_data_get(triangles_comp_array);
    794   darray_side_id_init(alloc, &stack);
    795   darray_side_id_init(alloc, &current_component);
    796   processed = MEM_CALLOC(alloc, scn->ntris, sizeof(uchar));
    797   if(!processed) {
    798     *p_res = RES_MEM_ERR;
    799     return;
    800   }
    801 
    802 #ifndef NDEBUG
    803   #pragma omp single
    804   {
    805     trg_id_t t_;
    806     int s;
    807     ASSERT(darray_ptr_component_descriptor_size_get(connex_components) == 0);
    808     FOR_EACH(t_, 0, scn->ntris) {
    809       const struct triangle_in* trg_in =
    810         darray_triangle_in_cdata_get(&scn->triangles_in) + t_;
    811       const struct side_range* media_use
    812         = darray_side_range_cdata_get(&scn->media_use);
    813       FOR_EACH(s, 0, 2) {
    814         const side_id_t side = TRGIDxSIDE_2_TRGSIDE(t_, (enum senc3d_side)s);
    815         medium_id_t medium = trg_in->medium[s];
    816         m_idx = medium_id_2_medium_idx(medium);
    817         ASSERT(media_use[m_idx].first <= side && side
    818           <= media_use[m_idx].last);
    819       }
    820     }
    821   } /* Implicit barrier here */
    822 #endif
    823 
    824   /* We loop on sides to build connex components. */
    825   ASSERT(darray_side_range_size_get(&scn->media_use) <= INT64_MAX);
    826   #pragma omp for schedule(dynamic) nowait
    827   /* Process all media, including unspecified */
    828   for(m_idx = 0; m_idx < (int64_t)darray_side_range_size_get(&scn->media_use);
    829     m_idx++)
    830   {
    831     const medium_id_t medium = medium_idx_2_medium_id(m_idx);
    832     /* media_use 0 is for SENC3D_UNSPECIFIED_MEDIUM, n+1 is for n */
    833     const struct side_range* media_use =
    834       darray_side_range_cdata_get(&scn->media_use) + m_idx;
    835     /* Any not-already-used side is used as a starting point */
    836     side_id_t first_side_not_in_component = media_use->first;
    837     double max_nz;
    838     side_id_t max_nz_side_id;
    839     const side_id_t last_side = media_use->last;
    840     int component_canceled = 0, max_z_is_2sided = 0, fst_nz = 1;
    841     res_T tmp_res = RES_OK;
    842     ATOMIC id;
    843 
    844     if(*p_res != RES_OK) continue;
    845     if(first_side_not_in_component == SIDE_NULL__)
    846       continue; /* Unused medium */
    847     ASSERT(first_side_not_in_component < 2 * scn->ntris);
    848     ASSERT(darray_side_id_size_get(&stack) == 0);
    849     ASSERT(darray_side_id_size_get(&current_component) == 0);
    850     for(;;) {
    851       /* Process all components for this medium
    852        * Here we start from a side of the currently processed medium that is
    853        * not member of any component yet; by exploring neighbourhood the
    854        * process can harvest sides with different media */
    855       side_id_t crt_side_id = get_side_not_in_connex_component
    856         (last_side, trgsides, processed, &first_side_not_in_component, medium);
    857       side_id_t cc_start_side_id = crt_side_id;
    858       side_id_t cc_last_side_id = crt_side_id;
    859       vrtx_id_t max_z_vrtx_id = VRTX_NULL__;
    860       struct cc_descriptor *cc;
    861       unsigned media_count;
    862       double max_z = -DBL_MAX;
    863       component_canceled = 0;
    864       ASSERT(crt_side_id == SIDE_NULL__ || crt_side_id < 2 * scn->ntris);
    865       darray_side_id_clear(&current_component);
    866 
    867       if(*p_res != RES_OK) break;
    868       if(crt_side_id == SIDE_NULL__)
    869         break; /* start_side_id=SIDE_NULL__ => component done! */
    870 
    871 #ifndef NDEBUG
    872       {
    873         trg_id_t tid = TRGSIDE_2_TRG(crt_side_id);
    874         enum senc3d_side s = TRGSIDE_2_SIDE(crt_side_id);
    875         medium_id_t side_med
    876           = darray_triangle_in_data_get(&scn->triangles_in)[tid].medium[s];
    877         ASSERT(side_med == medium);
    878       }
    879 #endif
    880 
    881       /* Reuse array if possible, or create a new one  */
    882       if(current_media) {
    883         /* current_media 0 is for SENC3D_UNSPECIFIED_MEDIUM, n+1 is for n */
    884         memset(current_media, 0, darray_side_range_size_get(&scn->media_use));
    885       } else {
    886         current_media = MEM_CALLOC(alloc,
    887           darray_side_range_size_get(&scn->media_use), sizeof(*current_media));
    888         if(!current_media) {
    889           *p_res = RES_MEM_ERR;
    890           continue;
    891         }
    892       }
    893       current_media[m_idx] = 1;
    894       media_count = 1;
    895       for(;;) { /* Process all sides of this component */
    896         int i;
    897         enum side_flag crt_side_flag = TRGSIDE_2_SIDEFLAG(crt_side_id);
    898         struct trgside* crt_side = trgsides + crt_side_id;
    899         const trg_id_t crt_trg_id = TRGSIDE_2_TRG(crt_side_id);
    900         uchar* trg_used = processed + crt_trg_id;
    901         const struct triangle_tmp* const trg_tmp = triangles_tmp + crt_trg_id;
    902         ASSERT(crt_trg_id < scn->ntris);
    903 
    904         if(*p_res != RES_OK) break;
    905 
    906         /* Record max_z information
    907          * Keep track of the appropriate vertex of the component in order to
    908          * start upwards closest point query at the component grouping step of
    909          * the algorithm. The most appropriate vertex is (the) one with the
    910          * greater Z coordinate. */
    911         if(max_z < trg_tmp->max_z) {
    912           /* New best vertex */
    913           const struct triangle_in* trg_in =
    914             darray_triangle_in_cdata_get(&scn->triangles_in) + crt_trg_id;
    915           max_z = trg_tmp->max_z;
    916           /* Select a vertex with z == max_z */
    917           FOR_EACH(i, 0, 3) {
    918             if(max_z == positions[trg_in->vertice_id[i]].pos.z) {
    919               max_z_vrtx_id = trg_in->vertice_id[i];
    920               break;
    921             }
    922           }
    923           ASSERT(i < 3); /* Found one */
    924         }
    925 
    926         /* Record crt_side both as component and triangle level */
    927         if((*trg_used & crt_side_flag) == 0) {
    928           OK2(darray_side_id_push_back(&current_component, &crt_side_id));
    929           *trg_used = *trg_used | (uchar)crt_side_flag;
    930         }
    931 
    932         /* Store neighbour's sides in a waiting stack */
    933         FOR_EACH(i, 0, 3) {
    934           side_id_t neighbour_id = crt_side->facing_side_id[i];
    935           trg_id_t nbour_trg_id = TRGSIDE_2_TRG(neighbour_id);
    936           enum side_flag nbour_side_id = TRGSIDE_2_SIDEFLAG(neighbour_id);
    937           uchar* nbour_used = processed + nbour_trg_id;
    938           const struct trgside* neighbour = trgsides + neighbour_id;
    939           medium_id_t nbour_med_idx = medium_id_2_medium_idx(neighbour->medium);
    940           if((int64_t)nbour_med_idx < m_idx
    941             || (*nbour_used & SIDE_CANCELED_FLAG(nbour_side_id)))
    942           {
    943             /* 1) Not the same medium.
    944              * Neighbour's medium idx is less than current medium: the whole
    945              * component is to be processed by another thread (possibly the one
    946              * associated with neighbour's medium).
    947              * 2) Neighbour was canceled: no need to replay the component
    948              * again as it will eventually rediscover the side with low medium
    949              * id and recancel all the work in progress */
    950             component_canceled = 1;
    951             darray_side_id_clear(&stack);
    952             /* Don't cancel used flags as all these sides will get us back to
    953              * (at least) the neighbour side we have just discovered, that will
    954              * cancel them again and again */
    955             sz = darray_side_id_size_get(&current_component);
    956             FOR_EACH(ii, 0, sz) {
    957               side_id_t used_side
    958                 = darray_side_id_cdata_get(&current_component)[ii];
    959               trg_id_t used_trg_id = TRGSIDE_2_TRG(used_side);
    960               enum side_flag used_side_flag = TRGSIDE_2_SIDEFLAG(used_side);
    961               uchar* used = processed + used_trg_id;
    962               ASSERT(*used & (uchar)used_side_flag);
    963               /* Set the used flag for sides in cancelled component as leading
    964                * to further cancellations */
    965               *used |= SIDE_CANCELED_FLAG(used_side_flag);
    966             }
    967             goto canceled;
    968           }
    969           if(*nbour_used & nbour_side_id) continue; /* Already processed */
    970           /* Mark neighbour as processed and stack it */
    971           *nbour_used |= (uchar)nbour_side_id;
    972           OK2(darray_side_id_push_back(&stack, &neighbour_id));
    973           OK2(darray_side_id_push_back(&current_component, &neighbour_id));
    974           if(!current_media[nbour_med_idx]) {
    975             current_media[nbour_med_idx] = 1;
    976             media_count++;
    977           }
    978         }
    979         sz = darray_side_id_size_get(&stack);
    980         if(sz == 0) break; /* Empty stack => component is done! */
    981         crt_side_id = darray_side_id_cdata_get(&stack)[sz - 1];
    982         darray_side_id_pop_back(&stack);
    983         cc_start_side_id = MMIN(cc_start_side_id, crt_side_id);
    984         cc_last_side_id = MMAX(cc_last_side_id, crt_side_id);
    985       }
    986     canceled:
    987       if(component_canceled) continue;
    988 
    989       /* Register the new component and get it initialized */
    990       cc = MEM_ALLOC(alloc, sizeof(struct cc_descriptor));
    991       if(!cc) *p_res = RES_MEM_ERR;
    992       if(*p_res != RES_OK) break;
    993 
    994       ASSERT(max_z_vrtx_id != VRTX_NULL__);
    995       cc_descriptor_init(alloc, cc);
    996       id = ATOMIC_INCR(component_count) - 1;
    997       ASSERT(id <= COMPONENT_MAX__);
    998       cc->cc_id = (component_id_t)id;
    999       sz = darray_side_id_size_get(&current_component);
   1000       ASSERT(sz > 0 && sz <= SIDE_MAX__);
   1001       cc->side_count = (side_id_t)sz;
   1002       cc->side_range.first = cc_start_side_id;
   1003       cc->side_range.last = cc_last_side_id;
   1004       cc->max_z_vrtx_id = max_z_vrtx_id;
   1005       /* Tranfer ownership of the array to component */
   1006       ASSERT(!cc->media && current_media);
   1007       cc->media = current_media;
   1008       cc->media_count = media_count;
   1009       current_media = NULL;
   1010 
   1011       /* Write component membership in the global structure
   1012        * No need for sync here as an unique thread writes a given side */
   1013       {STATIC_ASSERT(sizeof(cc->cc_id) >= 4, Cannot_write_IDs_sync_free);}
   1014       ASSERT(IS_ALIGNED(triangles_comp->component, sizeof(cc->cc_id)));
   1015       FOR_EACH(ii, 0, sz) {
   1016         const side_id_t s_id = darray_side_id_cdata_get(&current_component)[ii];
   1017         trg_id_t t_id = TRGSIDE_2_TRG(s_id);
   1018         enum senc3d_side side = TRGSIDE_2_SIDE(s_id);
   1019         component_id_t* cmp = triangles_comp[t_id].component;
   1020         ASSERT(cmp[side] == COMPONENT_NULL__);
   1021         ASSERT(medium_id_2_medium_idx(trgsides[s_id].medium)
   1022           >= medium_id_2_medium_idx(medium));
   1023         cmp[side] = cc->cc_id;
   1024       }
   1025       /* Compute component's bbox, area and volume, and record information on
   1026        * the max_z side of the component to help find out if the component is
   1027        * inner or outer */
   1028       fst_nz = 1;
   1029       max_nz = 0;
   1030       max_nz_side_id = SIDE_NULL__;
   1031       FOR_EACH(ii, 0, sz) {
   1032         const side_id_t s_id = darray_side_id_cdata_get(&current_component)[ii];
   1033         trg_id_t t_id = TRGSIDE_2_TRG(s_id);
   1034         enum senc3d_side side = TRGSIDE_2_SIDE(s_id);
   1035         struct triangle_comp* trg_comp = triangles_comp + t_id;
   1036         const struct triangle_tmp* const trg_tmp = triangles_tmp + t_id;
   1037         const struct triangle_in* trg_in =
   1038           darray_triangle_in_cdata_get(&scn->triangles_in) + t_id;
   1039         const union double3* vertices =
   1040           darray_position_cdata_get(&scn->vertices);
   1041         double edge0[3], edge1[3], normal[3], norm;
   1042         const double* v0 = vertices[trg_in->vertice_id[0]].vec;
   1043         const double* v1 = vertices[trg_in->vertice_id[1]].vec;
   1044         const double* v2 = vertices[trg_in->vertice_id[2]].vec;
   1045         int n, is_2sided = (trg_comp->component[SENC3D_FRONT]
   1046           == trg_comp->component[SENC3D_BACK]);
   1047 
   1048         /* Compute component's bbox, area and volume */
   1049         for(n = 0; n < 3; n++) {
   1050           cc->bbox[n][0] = MMIN(cc->bbox[n][0], v0[n]);
   1051           cc->bbox[n][0] = MMIN(cc->bbox[n][0], v1[n]);
   1052           cc->bbox[n][0] = MMIN(cc->bbox[n][0], v2[n]);
   1053           cc->bbox[n][1] = MMAX(cc->bbox[n][1], v0[n]);
   1054           cc->bbox[n][1] = MMAX(cc->bbox[n][1], v1[n]);
   1055           cc->bbox[n][1] = MMAX(cc->bbox[n][1], v2[n]);
   1056         }
   1057         d3_sub(edge0, v1, v0);
   1058         d3_sub(edge1, v2, v0);
   1059         d3_cross(normal, edge0, edge1);
   1060         norm = d3_normalize(normal, normal);
   1061         ASSERT(norm);
   1062         cc->_2area += norm;
   1063 
   1064         if(!is_2sided) {
   1065           /* Build a tetrahedron whose base is the triangle and whose apex is
   1066            * the coordinate system's origin. If the 2 sides of the triangle
   1067            * are part of the component, the contribution of the triangle
   1068            * must be 0. To achieve this, we just skip both sides */
   1069           double _2base = norm; /* 2 * base area */
   1070           double h = -d3_dot(normal, v0); /* Height of the tetrahedron */
   1071           if((trg_comp->component[SENC3D_FRONT] == cc->cc_id)
   1072             == (scn->convention & SENC3D_CONVENTION_NORMAL_FRONT))
   1073             cc->_6volume += (h * _2base);
   1074           else
   1075             cc->_6volume -= (h * _2base);
   1076         }
   1077 
   1078         ASSERT(trg_comp->component[side] != COMPONENT_NULL__); (void)side;
   1079         if(trg_tmp->max_z == max_z) {
   1080           int i;
   1081           /* Candidate to define max_nz (triangle using max_z_vrtx) */
   1082           FOR_EACH(i, 0, 3) {
   1083             if(cc->max_z_vrtx_id == trg_in->vertice_id[i]) {
   1084               if(fst_nz || fabs(normal[2]) > fabs(max_nz)) {
   1085                 max_nz_side_id = s_id;
   1086                 max_nz = normal[2];
   1087                 max_z_is_2sided = is_2sided;
   1088                 fst_nz = 0;
   1089               }
   1090               break;
   1091             }
   1092           }
   1093         }
   1094       }
   1095       ASSERT(!fst_nz);
   1096       /* Determine if this component can be an inner part inside another
   1097        * component (substracting a volume) as only these components will need
   1098        * to search for their possible outer component when grouping
   1099        * components to create enclosures.
   1100        * The inner/outer property comes from the normal orientation of the
   1101        * triangle on top of the component, this triangle being the one whose
   1102        * |Nz| is maximal. If this triangle has its 2 sides in the component,
   1103        * the component is inner */
   1104       if(max_nz == 0 || max_z_is_2sided) cc->is_outer_border = 0;
   1105       else {
   1106         ASSERT(max_nz_side_id != SIDE_NULL__);
   1107         if(TRGSIDE_IS_FRONT(max_nz_side_id)
   1108           == ((scn->convention & SENC3D_CONVENTION_NORMAL_FRONT) != 0)) {
   1109           /* Geom normal points towards the component */
   1110           cc->is_outer_border = (max_nz < 0);
   1111         } else {
   1112           /* Geom normal points away from the component */
   1113           cc->is_outer_border = (max_nz > 0);
   1114         }
   1115       }
   1116 
   1117       /* Need to synchronize connex_components growth as this global structure
   1118        * is accessed by multipe threads */
   1119       #pragma omp critical
   1120       {
   1121         struct cc_descriptor** components;
   1122         sz = darray_ptr_component_descriptor_size_get(connex_components);
   1123         if(sz <= cc->cc_id) {
   1124           tmp_res = darray_ptr_component_descriptor_resize(connex_components,
   1125               1 + cc->cc_id);
   1126           if(tmp_res != RES_OK) *p_res = tmp_res;
   1127         }
   1128         if(*p_res == RES_OK) {
   1129           /* Don't set the pointer before resize as this can lead to move data */
   1130           components =
   1131             darray_ptr_component_descriptor_data_get(connex_components);
   1132           ASSERT(components[cc->cc_id] == NULL);
   1133           components[cc->cc_id] = cc;
   1134         }
   1135       }
   1136     }
   1137   tmp_error:
   1138     if(tmp_res != RES_OK) *p_res = tmp_res;
   1139   } /* No barrier here */
   1140 
   1141   MEM_RM(alloc, processed);
   1142   MEM_RM(alloc, current_media);
   1143   darray_side_id_release(&current_component);
   1144   darray_side_id_release(&stack);
   1145 
   1146   /* The first thread here creates the s3d view */
   1147   #pragma omp single nowait
   1148   if(*p_res == RES_OK) {
   1149     res_T res = RES_OK;
   1150     struct s3d_device* s3d = NULL;
   1151     struct s3d_scene* s3d_scn = NULL;
   1152     struct s3d_shape* s3d_shp = NULL;
   1153     struct s3d_vertex_data attribs;
   1154 
   1155     attribs.type = S3D_FLOAT3;
   1156     attribs.usage = S3D_POSITION;
   1157     attribs.get = get_scn_position;
   1158 
   1159     /* Put geometry in a 3D view */
   1160     OK(s3d_device_create(scn->dev->logger, alloc, 0, &s3d));
   1161     OK(s3d_scene_create(s3d, &s3d_scn));
   1162     OK(s3d_shape_create_mesh(s3d, &s3d_shp));
   1163 
   1164     /* Back to s3d API type for ntris and nverts */
   1165     ASSERT(scn->ntris <= UINT_MAX);
   1166     ASSERT(scn->nverts <= UINT_MAX);
   1167     OK(s3d_mesh_setup_indexed_vertices(s3d_shp,
   1168       (unsigned)scn->ntris, get_scn_indices,
   1169       (unsigned)scn->nverts, &attribs, 1, scn));
   1170     OK(s3d_mesh_set_hit_filter_function(s3d_shp, self_hit_filter, NULL));
   1171     OK(s3d_scene_attach_shape(s3d_scn, s3d_shp));
   1172     OK(s3d_scene_view_create(s3d_scn, S3D_TRACE, s3d_view));
   1173   error:
   1174     if(res != RES_OK) *p_res = res;
   1175     if(s3d) S3D(device_ref_put(s3d));
   1176     if(s3d_scn) S3D(scene_ref_put(s3d_scn));
   1177     if(s3d_shp) S3D(shape_ref_put(s3d_shp));
   1178   }
   1179 
   1180 #ifndef NDEBUG
   1181   /* Need to wait for all threads done to be able to check stuff */
   1182   #pragma omp barrier
   1183   if(*p_res != RES_OK) return;
   1184   #pragma omp single
   1185   {
   1186     trg_id_t t_;
   1187     component_id_t c;
   1188     ASSERT(ATOMIC_GET(component_count) ==
   1189       (int)darray_ptr_component_descriptor_size_get(connex_components));
   1190     FOR_EACH(t_, 0, scn->ntris) {
   1191       struct triangle_comp* trg_comp =
   1192         darray_triangle_comp_data_get(triangles_comp_array) + t_;
   1193       ASSERT(trg_comp->component[SENC3D_FRONT] != COMPONENT_NULL__);
   1194       ASSERT(trg_comp->component[SENC3D_BACK] != COMPONENT_NULL__);
   1195     }
   1196     FOR_EACH(c, 0, (component_id_t)ATOMIC_GET(component_count)) {
   1197       struct cc_descriptor** components =
   1198         darray_ptr_component_descriptor_data_get(connex_components);
   1199       ASSERT(components[c] != NULL && components[c]->cc_id == c);
   1200     }
   1201   } /* Implicit barrier here */
   1202 #endif
   1203 }
   1204 
   1205 #ifndef NDEBUG
   1206 static int
   1207 compare_components
   1208   (const void* ptr1, const void* ptr2)
   1209 {
   1210   const struct cc_descriptor* const* cc1 = ptr1;
   1211   const struct cc_descriptor* const* cc2 = ptr2;
   1212   ASSERT((*cc1)->side_range.first != (*cc2)->side_range.first);
   1213   return (int)(*cc1)->side_range.first - (int)(*cc2)->side_range.first;
   1214 }
   1215 
   1216 static res_T
   1217 reorder_components
   1218   (struct senc3d_scene* scn,
   1219    struct darray_ptr_component_descriptor* connex_components,
   1220    struct darray_triangle_comp* triangles_comp_array)
   1221 {
   1222   struct cc_descriptor** cc_descriptors;
   1223   struct triangle_comp* triangles_comp;
   1224   component_id_t c;
   1225   component_id_t* new_ids = NULL;
   1226   size_t t, cc_count;
   1227   res_T res = RES_OK;
   1228 
   1229   ASSERT(connex_components && triangles_comp_array);
   1230 
   1231   cc_count = darray_ptr_component_descriptor_size_get(connex_components);
   1232   ASSERT(cc_count <= COMPONENT_MAX__);
   1233   cc_descriptors = darray_ptr_component_descriptor_data_get(connex_components);
   1234 
   1235   /* Single thread code: can allocate here */
   1236   new_ids = MEM_ALLOC(scn->dev->allocator, sizeof(*new_ids) * cc_count);
   1237   if(!new_ids) {
   1238     res = RES_MEM_ERR;
   1239     goto error;
   1240   }
   1241 
   1242   FOR_EACH(c, 0, cc_count) ASSERT(cc_descriptors[c]->cc_id == c);
   1243   /* Sort components by first side */
   1244   qsort(cc_descriptors, cc_count, sizeof(*cc_descriptors), compare_components);
   1245   /* Make conversion table */
   1246   FOR_EACH(c, 0, cc_count) {
   1247     component_id_t rank = cc_descriptors[c]->cc_id;
   1248     new_ids[rank] = c;
   1249   }
   1250   triangles_comp = darray_triangle_comp_data_get(triangles_comp_array);
   1251   FOR_EACH(c, 0, cc_count) {
   1252     ASSERT(new_ids[cc_descriptors[c]->cc_id] == c);
   1253     /* Update the enclosure ID in cc_descriptor */
   1254     cc_descriptors[c]->cc_id = c;
   1255   }
   1256   FOR_EACH(t, 0, scn->ntris) {
   1257     struct triangle_comp* comp = triangles_comp + t;
   1258     component_id_t fc = comp->component[SENC3D_FRONT];
   1259     component_id_t bc = comp->component[SENC3D_BACK];
   1260     ASSERT(new_ids[fc] < cc_count);
   1261     comp->component[SENC3D_FRONT] = new_ids[fc];
   1262     ASSERT(new_ids[bc] < cc_count);
   1263     comp->component[SENC3D_BACK] = new_ids[bc];
   1264   }
   1265 
   1266 error:
   1267   if(new_ids) MEM_RM(scn->dev->allocator, new_ids);
   1268   return res;
   1269 }
   1270 #endif
   1271 
   1272 static void
   1273 group_connex_components
   1274   (struct senc3d_scene* scn,
   1275    struct darray_triangle_comp* triangles_comp,
   1276    struct darray_ptr_component_descriptor* connex_components,
   1277    struct s3d_scene_view* s3d_view,
   1278    ATOMIC* next_enclosure_id,
   1279    /* Shared error status.
   1280     * We accept to overwrite an error with a different error */
   1281    res_T* res)
   1282 {
   1283   /* This function is called from an omp parallel block and executed
   1284    * concurrently. */
   1285   struct cc_descriptor** descriptors;
   1286   const union double3* positions;
   1287   size_t tmp;
   1288   component_id_t cc_count;
   1289   int64_t ccc;
   1290   struct filter_ctx ctx0, ctx1;
   1291   float lower[3], upper[3];
   1292   const int log_components = scn->convention & SENC3D_LOG_COMPONENTS_INFORMATION;
   1293   const int dump_components = scn->convention & SENC3D_DUMP_COMPONENTS_STL;
   1294 
   1295   ASSERT(scn && triangles_comp && connex_components
   1296     && s3d_view && next_enclosure_id && res);
   1297   ASSERT(scn->analyze.enclosures_count == 1);
   1298 
   1299 #ifndef NDEBUG
   1300   #pragma omp single
   1301   {
   1302     /* Ensure reproducible cc_ids for debugging purpose */
   1303     *res = reorder_components(scn, connex_components, triangles_comp);
   1304   }
   1305   if(*res != RES_OK) return;
   1306 #endif
   1307   descriptors = darray_ptr_component_descriptor_data_get(connex_components);
   1308   tmp = darray_ptr_component_descriptor_size_get(connex_components);
   1309   ASSERT(tmp <= COMPONENT_MAX__);
   1310   cc_count = (component_id_t)tmp;
   1311   positions = darray_position_cdata_get(&scn->vertices);
   1312 
   1313   #pragma omp single
   1314   if(dump_components) {
   1315     /* Do it now before any other problem can occur (fingers crossed).
   1316      * Do it sequential and not optimized as it is debug code.
   1317      * Don't throw errors, just skip to the next component. */
   1318     static unsigned scene_cpt = 0;
   1319     struct str name;
   1320     res_T tmp_res;
   1321     const struct triangle_comp* tc =
   1322       darray_triangle_comp_cdata_get(triangles_comp);
   1323     const struct triangle_in* tin = darray_triangle_in_cdata_get(&scn->triangles_in);
   1324     const int output_normal_in =
   1325       (scn->convention & SENC3D_CONVENTION_NORMAL_INSIDE) != 0;
   1326     str_init(scn->dev->allocator, &name);
   1327     printf("Dumping components for scene #%u\n", scene_cpt);
   1328     for(ccc = 0; ccc < (int64_t)cc_count; ccc++) {
   1329       FILE* f;
   1330       trg_id_t t;
   1331       component_id_t c = (component_id_t)ccc;
   1332       tmp_res = str_printf(&name, "scn_%u_comp_%u.stl", scene_cpt, c);
   1333       if(tmp_res != RES_OK) continue;
   1334       f = fopen(str_cget(&name), "w");
   1335       if(!f) continue;
   1336       fprintf(f, "solid %s\n", str_cget(&name));
   1337       for(t = 0; t < scn->ntris; t++) {
   1338         const component_id_t cf_id = tc[t].component[SENC3D_FRONT];
   1339         const component_id_t cb_id = tc[t].component[SENC3D_BACK];
   1340         if(cf_id == c || cb_id == c) {
   1341           const vrtx_id_t* vertice_id = tin[t].vertice_id;
   1342           double n[3], e1[3], e2[3];
   1343           const int input_normal_in = (cf_id == c);
   1344           const int revert_triangle = (input_normal_in != output_normal_in);
   1345           const vrtx_id_t i0 = vertice_id[0];
   1346           const vrtx_id_t i1 = vertice_id[revert_triangle ? 2 : 1];
   1347           const vrtx_id_t i2 = vertice_id[revert_triangle ? 1 : 2];
   1348           /* This triangle is in component #c */
   1349           if(cf_id == cb_id) { /* Both sides in component */
   1350             /* Could add some log */
   1351           }
   1352           d3_sub(e1, positions[i1].vec, positions[i0].vec);
   1353           d3_sub(e2, positions[i2].vec, positions[i0].vec);
   1354           d3_normalize(n, d3_cross(n, e1, e2));
   1355           fprintf(f, "  facet normal %16g %16g %16g\n", SPLIT3(n));
   1356           fprintf(f, "    outer loop\n");
   1357           fprintf(f, "      vertex %16g %16g %16g\n", SPLIT3(positions[i0].vec));
   1358           fprintf(f, "      vertex %16g %16g %16g\n", SPLIT3(positions[i1].vec));
   1359           fprintf(f, "      vertex %16g %16g %16g\n", SPLIT3(positions[i2].vec));
   1360           fprintf(f, "    endloop\n");
   1361           fprintf(f, "  endfacet\n");
   1362         }
   1363       }
   1364         printf("Dumped component #%u in file %s\n", c, str_cget(&name));
   1365       fprintf(f, "endsolid %s\n", str_cget(&name));
   1366       fclose(f);
   1367     }
   1368     str_release(&name);
   1369     scene_cpt++;
   1370   }
   1371 
   1372   ctx0.type = CTX0;
   1373   ctx0.scn = scn;
   1374   ctx0.view = s3d_view;
   1375   ctx0.triangles_comp = triangles_comp;
   1376   ctx0.components = connex_components;
   1377   ctx1.type = CTX1;
   1378   ctx1.scn = scn;
   1379   ctx1.view = s3d_view;
   1380   ctx1.triangles_comp = triangles_comp;
   1381   ctx1.components = connex_components;
   1382   *res = s3d_scene_view_get_aabb(s3d_view, lower, upper);
   1383   if(*res != RES_OK) goto end;
   1384   #pragma omp for schedule(dynamic)
   1385   for(ccc = 0; ccc < (int64_t)cc_count; ccc++) {
   1386     res_T tmp_res = RES_OK;
   1387     component_id_t c = (component_id_t)ccc;
   1388     struct s3d_hit hit = S3D_HIT_NULL;
   1389     float origin[3], rrr[3], r;
   1390     struct cc_descriptor* const cc = descriptors[c];
   1391     const double* max_vrtx;
   1392     int i;
   1393 
   1394     if(*res != RES_OK) continue;
   1395     ASSERT(cc->cc_id == c);
   1396     ASSERT(cc->cc_group_root == CC_GROUP_ID_NONE);
   1397     ASSERT(cc->max_z_vrtx_id < scn->nverts);
   1398 
   1399     if(cc->is_outer_border) {
   1400       ATOMIC id;
   1401       /* No need to create a link from this CC: inner CC are doing the job */
   1402       cc->cc_group_root = cc->cc_id; /* New group with self as root */
   1403       id = ATOMIC_INCR(next_enclosure_id) - 1;
   1404       ASSERT(id <= ENCLOSURE_MAX__);
   1405       cc->enclosure_id = (enclosure_id_t)id;
   1406       if(log_components) {
   1407         #pragma omp critical
   1408         printf("Component #%u: is outer, not processed\n", c);
   1409       }
   1410       continue;
   1411     }
   1412 
   1413     /* First step is to determine the distance of the closest upward geometry */
   1414     max_vrtx = positions[cc->max_z_vrtx_id].vec;
   1415     f3_set_d3(origin, max_vrtx);
   1416     /* Limit search radius. Only upwards moves (+Z) will be considered.
   1417      * Use a radius allowing to reach the closest top vertex of scene's AABB */
   1418     FOR_EACH(i, 0, 2) {
   1419       ASSERT(lower[i] <= origin[i] && origin[i] <= upper[i]);
   1420       rrr[i] = MMIN(origin[i] - lower[i], upper[i] - origin[i]);
   1421     }
   1422     rrr[2] = upper[2] - origin[2];
   1423     r = f3_len(rrr) * (1 + FLT_EPSILON) + FLT_EPSILON; /* Ensure r > 0 */
   1424     ctx0.origin_component = cc->cc_id;
   1425     ctx0.current_6volume = DBL_MAX;
   1426     ctx0.hit_dist = FLT_MAX;
   1427     ctx0.hit_component = COMPONENT_NULL__;
   1428     tmp_res = s3d_scene_view_closest_point(s3d_view, origin, r, &ctx0, &hit);
   1429     if(tmp_res != RES_OK) {
   1430       *res = tmp_res;
   1431       continue;
   1432     }
   1433     /* If no hit, the component is facing an infinite medium */
   1434     if(S3D_HIT_NONE(&hit)) {
   1435       cc->cc_group_root = CC_GROUP_ROOT_INFINITE;
   1436       cc->enclosure_id = 0;
   1437       if(log_components) {
   1438         #pragma omp critical
   1439         printf("Component #%u: is part of enclosure #0\n", c);
   1440       }
   1441       continue;
   1442     }
   1443 
   1444     /* Second step is to determine which component faces component #c */
   1445     ctx1.origin_component = cc->cc_id;
   1446     ctx1.current_6volume = DBL_MAX;
   1447     ctx1.hit_dist = FLT_MAX;
   1448     ctx1.hit_component = COMPONENT_NULL__;
   1449     /* New search radius is hit.distance + some margin to cope with numerical
   1450      * issues (and r==0 is an error) */
   1451     r = hit.distance * (1 + FLT_EPSILON) + FLT_EPSILON;
   1452     if(log_components) {
   1453       #pragma omp critical
   1454       printf("Component #%u: starting search for components (R=%g) from %g %g %g\n",
   1455           c, r, SPLIT3(origin));
   1456     }
   1457     /* Cast a sphere to find links between connex components */
   1458     tmp_res = s3d_scene_view_closest_point(s3d_view, origin, r, &ctx1, &hit);
   1459     if(tmp_res != RES_OK) {
   1460       *res = tmp_res;
   1461       continue;
   1462     }
   1463     /* As CTX1 filter rejects any hit, do not rely on hit but use result as
   1464      * stored in ctx1 */
   1465     /* If no hit is accepted, the component is facing an infinite medium */
   1466     if(ctx1.hit_dist == FLT_MAX) {
   1467       cc->cc_group_root = CC_GROUP_ROOT_INFINITE;
   1468       cc->enclosure_id = 0;
   1469       if(log_components) {
   1470         #pragma omp critical
   1471         printf("Component #%u: is part of enclosure #0\n", c);
   1472       }
   1473       continue;
   1474     }
   1475     else if(ctx1.hit_component == COMPONENT_NULL__) {
   1476       /* The selected triangle was nearly parallel to the line of sight:
   1477        * FRONT/BACK discrimination was not reliable enough and should be done
   1478        * differently. */
   1479       /* Could try something; now just report a failure */
   1480       log_err(scn->dev, LIB_NAME": %s: %d: search failed\n", FUNC_NAME,
   1481           __LINE__ );
   1482       *res = RES_BAD_OP;
   1483       continue;
   1484     } else {
   1485       /* If hit, group this component */
   1486       cc->cc_group_root = ctx1.hit_component;
   1487       ASSERT(cc->cc_group_root < cc_count);
   1488       if(log_components) {
   1489         #pragma omp critical
   1490         printf("Component #%u: linked to component #%u\n", c, cc->cc_group_root);
   1491       }
   1492     }
   1493   }
   1494   /* Implicit barrier here */
   1495   if(*res != RES_OK) goto end;
   1496 
   1497   /* One thread post-processes links to group connex components */
   1498   #pragma omp single
   1499   {
   1500     res_T tmp_res = RES_OK;
   1501     size_t ec = (size_t)ATOMIC_GET(next_enclosure_id);
   1502     ASSERT(ec <= ENCLOSURE_MAX__);
   1503     scn->analyze.enclosures_count = (enclosure_id_t)ec;
   1504     tmp_res = darray_enclosure_resize(&scn->analyze.enclosures,
   1505       scn->analyze.enclosures_count);
   1506     if(tmp_res != RES_OK) {
   1507       *res = tmp_res;
   1508     } else {
   1509       struct enclosure_data* enclosures
   1510         = darray_enclosure_data_get(&scn->analyze.enclosures);
   1511       FOR_EACH(ccc, 0, (int64_t)cc_count) {
   1512         component_id_t c = (component_id_t)ccc;
   1513         struct cc_descriptor* const cc = descriptors[c];
   1514         const struct cc_descriptor* other_desc = cc;
   1515         struct enclosure_data* enc;
   1516 #ifndef NDEBUG
   1517         component_id_t cc_cpt = 0;
   1518 #endif
   1519 
   1520         while(other_desc->enclosure_id == CC_GROUP_ID_NONE) {
   1521           ASSERT(other_desc->cc_group_root < cc_count);
   1522           other_desc = descriptors[other_desc->cc_group_root];
   1523           /* Cannot have more components in cc than cc_count! */
   1524           ASSERT(++cc_cpt <= cc_count);
   1525         }
   1526         ASSERT(other_desc->cc_group_root != CC_GROUP_ROOT_NONE);
   1527         ASSERT(other_desc->enclosure_id != CC_GROUP_ID_NONE);
   1528         cc->cc_group_root = other_desc->cc_group_root;
   1529         cc->enclosure_id = other_desc->enclosure_id;
   1530         enc = enclosures + cc->enclosure_id;
   1531         ++enc->cc_count;
   1532         /* Linked list of componnents */
   1533         enc->first_component = cc->cc_id;
   1534         enc->side_range.first = MMIN(enc->side_range.first, cc->side_range.first);
   1535         enc->side_range.last = MMAX(enc->side_range.last, cc->side_range.last);
   1536         enc->side_count += cc->side_count;
   1537         tmp_res = bool_array_of_media_merge(&enc->tmp_enclosed_media, cc->media,
   1538           cc->media_count, darray_side_range_size_get(&scn->media_use));
   1539         if(tmp_res != RES_OK) {
   1540           *res = tmp_res;
   1541           break;
   1542         }
   1543       }
   1544     }
   1545   }
   1546   /* Implicit barrier here */
   1547 end:
   1548   return;
   1549 }
   1550 
   1551 static void
   1552 collect_and_link_neighbours
   1553   (struct senc3d_scene* scn,
   1554    struct trgside* trgsides,
   1555    struct darray_triangle_tmp* triangles_tmp_array,
   1556    struct darray_frontier_edge* frontiers,
   1557    struct htable_overlap* overlaps,
   1558    /* Shared error status.
   1559     * We accept to overwrite an error with a different error */
   1560    res_T* res)
   1561 {
   1562   /* This function is called from an omp parallel block and executed
   1563    * concurrently. */
   1564   const struct triangle_in* triangles_in;
   1565   struct triangle_tmp* triangles_tmp;
   1566   const union double3* vertices;
   1567   const int thread_count = omp_get_num_threads();
   1568   const int rank = omp_get_thread_num();
   1569   const int front = ((scn->convention & SENC3D_CONVENTION_NORMAL_FRONT) != 0);
   1570   /* Htable used to give an id to edges */
   1571   struct htable_edge_id edge_ids;
   1572   /* Array to keep neighbourhood of edges
   1573    * Resize/Push operations on neighbourhood_by_edge are valid in the
   1574    * openmp block because a given neighbourhood is only processed
   1575    * by a single thread */
   1576   struct darray_neighbourhood neighbourhood_by_edge;
   1577   edge_id_t edge_count;
   1578   edge_id_t nbedges_guess;
   1579   edge_id_t e;
   1580   trg_id_t t;
   1581   size_t sz;
   1582   res_T tmp_res;
   1583 
   1584   ASSERT(scn && trgsides && triangles_tmp_array && frontiers && res);
   1585   ASSERT((size_t)scn->nverts + (size_t)scn->ntris + 2 <= EDGE_MAX__);
   1586 
   1587   htable_edge_id_init(scn->dev->allocator, &edge_ids);
   1588   darray_neighbourhood_init(scn->dev->allocator, &neighbourhood_by_edge);
   1589 
   1590   triangles_in = darray_triangle_in_cdata_get(&scn->triangles_in);
   1591   triangles_tmp = darray_triangle_tmp_data_get(triangles_tmp_array);
   1592   vertices = darray_position_cdata_get(&scn->vertices);
   1593 
   1594   ASSERT(scn->ntris == darray_triangle_tmp_size_get(triangles_tmp_array));
   1595 
   1596   /* Make some room for edges. */
   1597   nbedges_guess = 4 + (thread_count == 1
   1598     ? (edge_id_t)(scn->nverts + scn->ntris)
   1599     : (edge_id_t)((scn->nverts + scn->ntris) / (0.75 * thread_count)));
   1600   OK2(darray_neighbourhood_reserve(&neighbourhood_by_edge, nbedges_guess));
   1601   OK2(htable_edge_id_reserve(&edge_ids, nbedges_guess));
   1602 
   1603   /* Loop on triangles to register edges.
   1604    * All threads considering all the edges and processing some */
   1605   FOR_EACH(t, 0, scn->ntris) {
   1606     struct trg_edge edge;
   1607     uchar ee;
   1608     FOR_EACH(ee, 0, 3) {
   1609       edge_id_t* p_id;
   1610       size_t n_sz;
   1611       struct edge_neighbourhood* neighbourhood;
   1612       struct neighbour_info* info;
   1613       const vrtx_id_t v0 = triangles_in[t].vertice_id[ee];
   1614       const vrtx_id_t v1 = triangles_in[t].vertice_id[(ee + 1) % 3];
   1615       /* Process only "my" edges! */
   1616       const int64_t h =
   1617         /* v0,v1 and v1,v0 must give the same hash!!! */
   1618         v0 + v1 + (int64_t)MMIN(v0, v1);
   1619       if(h % thread_count != rank) continue;
   1620       /* Create edge. */
   1621       set_edge(v0, v1, &edge, &triangles_tmp[t].reversed_edge[ee]);
   1622       /* Find edge id; create it if not already done. */
   1623       p_id = htable_edge_id_find(&edge_ids, &edge);
   1624       if(p_id) {
   1625         neighbourhood =
   1626           darray_neighbourhood_data_get(&neighbourhood_by_edge) + *p_id;
   1627         ASSERT(neighbourhood->edge.vrtx0 == edge.vrtx0
   1628           && neighbourhood->edge.vrtx1 == edge.vrtx1);
   1629       } else {
   1630         /* Create id */
   1631         edge_id_t id;
   1632         sz = htable_edge_id_size_get(&edge_ids);
   1633         ASSERT(sz <= EDGE_MAX__);
   1634         id = (edge_id_t)sz;
   1635         ASSERT(htable_edge_id_size_get(&edge_ids)
   1636           == darray_neighbourhood_size_get(&neighbourhood_by_edge));
   1637         OK2(htable_edge_id_set(&edge_ids, &edge, &id));
   1638         OK2(darray_neighbourhood_resize(&neighbourhood_by_edge, 1 + sz));
   1639         neighbourhood = darray_neighbourhood_data_get(&neighbourhood_by_edge) + sz;
   1640         /* Add neighbour info to a newly created edge's neighbour list */
   1641         neighbourhood->edge = edge;
   1642         ASSERT(darray_neighbour_size_get(&neighbourhood->neighbours) == 0);
   1643         /* Just a guess: few edges will have less than 2 neighbours  */
   1644         OK2(darray_neighbour_reserve(&neighbourhood->neighbours, 2));
   1645       }
   1646       /* Add neighbour info to neighbourhood */
   1647       n_sz = darray_neighbour_size_get(&neighbourhood->neighbours);
   1648       OK2(darray_neighbour_resize(&neighbourhood->neighbours, 1 + n_sz));
   1649       info = darray_neighbour_data_get(&neighbourhood->neighbours) + n_sz;
   1650       info->trg_id = t;
   1651       info->common_edge_rank = ee;
   1652     }
   1653   } /* No barrier here. */
   1654 
   1655   /* Loop on collected edges.
   1656    * For each edge sort triangle sides by rotation angle
   1657    * and connect neighbours. */
   1658   sz = darray_neighbourhood_size_get(&neighbourhood_by_edge);
   1659   ASSERT(sz <= EDGE_MAX__);
   1660   edge_count = (edge_id_t)sz;
   1661   FOR_EACH(e, 0, edge_count) {
   1662     double edge[3], common_edge[3], basis[9], norm, max_z, maxz_edge, a;
   1663     vrtx_id_t v0, v1, v2;
   1664     struct edge_neighbourhood* neighbourhood
   1665       = darray_neighbourhood_data_get(&neighbourhood_by_edge) + e;
   1666     struct darray_neighbour* neighbour_list = &neighbourhood->neighbours;
   1667     side_id_t i, neighbour_count;
   1668     sz = darray_neighbour_size_get(neighbour_list);
   1669     ASSERT(sz > 0 && sz <= SIDE_MAX__);
   1670     neighbour_count = (side_id_t)sz;
   1671     ASSERT(neighbour_count);
   1672     v0 = neighbourhood->edge.vrtx0;
   1673     v1 = neighbourhood->edge.vrtx1;
   1674     d3_sub(common_edge, vertices[v1].vec, vertices[v0].vec);
   1675     maxz_edge = MMAX(vertices[v0].pos.z, vertices[v1].pos.z);
   1676     norm = d3_normalize(common_edge, common_edge);
   1677     ASSERT(norm); (void)norm;
   1678     d33_basis(basis, common_edge);
   1679     d33_inverse(basis, basis);
   1680     FOR_EACH(i, 0, neighbour_count) {
   1681       struct neighbour_info* neighbour_info
   1682         = darray_neighbour_data_get(neighbour_list) + i;
   1683       const trg_id_t crt_id = neighbour_info->trg_id;
   1684       const uchar crt_edge = neighbour_info->common_edge_rank;
   1685       const struct triangle_in* trg_in = triangles_in + crt_id;
   1686       struct triangle_tmp* neighbour = triangles_tmp + crt_id;
   1687       union double3 n; /* Geometrical normal to neighbour triangle */
   1688       const int is_reversed = neighbour->reversed_edge[crt_edge];
   1689       v2 = trg_in->vertice_id[(crt_edge + 2) % 3];
   1690       max_z = MMAX(vertices[v2].pos.z, maxz_edge);
   1691       ASSERT(neighbour->max_z <= max_z);
   1692       neighbour->max_z = max_z;
   1693       /* Compute rotation angle around common edge */
   1694       d3_sub(edge, vertices[v2].vec, vertices[v0].vec);
   1695       d33_muld3(edge, basis, edge);
   1696       ASSERT(d3_len(edge) != 0 && (edge[0] != 0 || edge[1] != 0));
   1697       neighbour_info->angle = atan2(edge[1], edge[0]); /* in ]-pi + pi]*/
   1698       if(is_reversed)
   1699         d3(n.vec, +edge[1], -edge[0], 0);
   1700       else
   1701         d3(n.vec, -edge[1], +edge[0], 0);
   1702 
   1703       /* Normal orientation calculation. */
   1704       if(neighbour_info->angle > 3 * PI / 4) {
   1705         ASSERT(n.pos.y);
   1706         neighbour_info->normal_toward_next_neighbour = (n.pos.y < 0);
   1707       } else if(neighbour_info->angle > PI / 4) {
   1708         ASSERT(n.pos.x);
   1709         neighbour_info->normal_toward_next_neighbour = (n.pos.x < 0);
   1710       } else if(neighbour_info->angle > -PI / 4) {
   1711         ASSERT(n.pos.y);
   1712         neighbour_info->normal_toward_next_neighbour = (n.pos.y > 0);
   1713       } else if(neighbour_info->angle > -3 * PI / 4) {
   1714         ASSERT(n.pos.x);
   1715         neighbour_info->normal_toward_next_neighbour = (n.pos.x > 0);
   1716       } else {
   1717         ASSERT(n.pos.y);
   1718         neighbour_info->normal_toward_next_neighbour = (n.pos.y < 0);
   1719       }
   1720     }
   1721     /* Sort triangles by rotation angle */
   1722     qsort(darray_neighbour_data_get(neighbour_list), neighbour_count,
   1723       sizeof(struct neighbour_info), neighbour_cmp);
   1724     /* Link sides.
   1725      * Create cycles of sides by neighbourhood around common edge. */
   1726     a = -DBL_MAX;
   1727     FOR_EACH(i, 0, neighbour_count) {
   1728       /* Neighbourhood info for current pair of triangles */
   1729       const struct neighbour_info* current
   1730         = darray_neighbour_cdata_get(neighbour_list) + i;
   1731       const struct neighbour_info* ccw_neighbour
   1732         = darray_neighbour_cdata_get(neighbour_list) + (i + 1) % neighbour_count;
   1733       /* Rank of the edge of interest in triangles */
   1734       const uchar crt_edge = current->common_edge_rank;
   1735       /* Here ccw refers to the rotation around the common edge
   1736        * and has nothing to do with vertices order in triangle definition
   1737        * nor Front/Back side convention */
   1738       const uchar ccw_edge = ccw_neighbour->common_edge_rank;
   1739       /* User id of current triangles */
   1740       const trg_id_t crt_id = current->trg_id;
   1741       const trg_id_t ccw_id = ccw_neighbour->trg_id;
   1742       /* Facing sides of triangles */
   1743       const enum senc3d_side crt_side
   1744         = (current->normal_toward_next_neighbour == front)
   1745           ? SENC3D_FRONT : SENC3D_BACK;
   1746       const enum senc3d_side ccw_side
   1747         = (ccw_neighbour->normal_toward_next_neighbour == front)
   1748           ? SENC3D_BACK : SENC3D_FRONT;
   1749       /* Index of sides in trgsides */
   1750       const side_id_t crt_side_idx = TRGIDxSIDE_2_TRGSIDE(crt_id, crt_side);
   1751       const side_id_t ccw_side_idx = TRGIDxSIDE_2_TRGSIDE(ccw_id, ccw_side);
   1752       /* Side ptrs */
   1753       struct trgside* const p_crt_side = trgsides + crt_side_idx;
   1754       struct trgside* const p_ccw_side = trgsides + ccw_side_idx;
   1755       /* Check that angle is a discriminant property */
   1756       ASSERT(a <= current->angle); /* Is sorted */
   1757       if(a == current->angle) {
   1758         /* Two consecutive triangles with same angle! Store them */
   1759         const struct neighbour_info* previous;
   1760         trg_id_t prev_id;
   1761         previous = darray_neighbour_cdata_get(neighbour_list) + i - 1;
   1762         prev_id = previous->trg_id;
   1763         #pragma omp critical
   1764         {
   1765           char one = 1;
   1766           tmp_res = htable_overlap_set(overlaps, &crt_id, &one);
   1767           if(tmp_res == RES_OK)
   1768             tmp_res = htable_overlap_set(overlaps, &prev_id, &one);
   1769         }
   1770         if(tmp_res != RES_OK) goto tmp_error;
   1771       }
   1772       a = current->angle;
   1773       /* Link sides */
   1774       ASSERT(p_crt_side->facing_side_id[crt_edge] == SIDE_NULL__);
   1775       ASSERT(p_ccw_side->facing_side_id[ccw_edge] == SIDE_NULL__);
   1776       p_crt_side->facing_side_id[crt_edge] = ccw_side_idx;
   1777       p_ccw_side->facing_side_id[ccw_edge] = crt_side_idx;
   1778       /* Record media  */
   1779       ASSERT(p_crt_side->medium == MEDIUM_NULL__
   1780         || p_crt_side->medium == triangles_in[crt_id].medium[crt_side]);
   1781       ASSERT(p_ccw_side->medium == MEDIUM_NULL__
   1782         || p_ccw_side->medium == triangles_in[ccw_id].medium[ccw_side]);
   1783       p_crt_side->medium = triangles_in[crt_id].medium[crt_side];
   1784       p_ccw_side->medium = triangles_in[ccw_id].medium[ccw_side];
   1785       ASSERT(p_crt_side->medium == SENC3D_UNSPECIFIED_MEDIUM
   1786         || p_crt_side->medium < darray_side_range_size_get(&scn->media_use) - 1);
   1787       ASSERT(p_ccw_side->medium == SENC3D_UNSPECIFIED_MEDIUM
   1788         || p_ccw_side->medium < darray_side_range_size_get(&scn->media_use) - 1);
   1789       /* Detect triangles that could surround a hole:
   1790        * - single triangle on (one of) its edge
   1791        * - different media on its sides */
   1792       if(neighbour_count == 1
   1793         && p_crt_side->medium != p_ccw_side->medium)
   1794       #pragma omp critical
   1795       {
   1796         struct frontier_edge frontier_edge;
   1797         frontier_edge.trg = crt_id;
   1798         frontier_edge.vrtx0 = v0;
   1799         frontier_edge.vrtx1 = v1;
   1800         darray_frontier_edge_push_back(frontiers, &frontier_edge);
   1801       }
   1802     }
   1803   }
   1804 
   1805 tmp_error:
   1806   if(tmp_res != RES_OK) *res = tmp_res;
   1807   /* Threads are allowed to return whitout sync. */
   1808   htable_edge_id_release(&edge_ids);
   1809   darray_neighbourhood_release(&neighbourhood_by_edge);
   1810 }
   1811 
   1812 static int
   1813 compare_enclosures
   1814   (const void* ptr1, const void* ptr2)
   1815 {
   1816   const struct enclosure_data* e1 = ptr1;
   1817   const struct enclosure_data* e2 = ptr2;
   1818   ASSERT(e1->side_range.first != e2->side_range.first);
   1819   return (int)e1->side_range.first - (int)e2->side_range.first;
   1820 }
   1821 
   1822 static res_T
   1823 reorder_enclosures
   1824   (struct senc3d_scene* scn,
   1825    const struct darray_ptr_component_descriptor* connex_components)
   1826 {
   1827   struct enclosure_data* enclosures;
   1828   struct cc_descriptor* const* cc_descriptors;
   1829   enclosure_id_t* new_ids;
   1830   enclosure_id_t e;
   1831   size_t c, cc_count;
   1832   res_T res = RES_OK;
   1833 
   1834   /* Single thread code: can allocate here */
   1835   new_ids = MEM_ALLOC(scn->dev->allocator,
   1836     sizeof(*new_ids) * scn->analyze.enclosures_count);
   1837   if(!new_ids) {
   1838     res = RES_MEM_ERR;
   1839     goto error;
   1840   }
   1841   cc_count = darray_ptr_component_descriptor_size_get(connex_components);
   1842   ASSERT(cc_count <= COMPONENT_MAX__);
   1843   cc_descriptors = darray_ptr_component_descriptor_cdata_get(connex_components);
   1844   enclosures = darray_enclosure_data_get(&scn->analyze.enclosures);
   1845   /* Store initial enclosure order */
   1846   FOR_EACH(e, 0, scn->analyze.enclosures_count)
   1847     enclosures[e].header.enclosure_id = e;
   1848   /* Sort enclosures by first side while keeping enclosure 0
   1849    * at rank 0 (its a convention) */
   1850   qsort(enclosures + 1, scn->analyze.enclosures_count - 1, sizeof(*enclosures),
   1851     compare_enclosures);
   1852   /* Make conversion table */
   1853   FOR_EACH(e, 0, scn->analyze.enclosures_count) {
   1854     enclosure_id_t rank = enclosures[e].header.enclosure_id;
   1855     new_ids[rank] = e;
   1856   }
   1857   FOR_EACH(e, 0, scn->analyze.enclosures_count) {
   1858     ASSERT(new_ids[enclosures[e].header.enclosure_id] == e);
   1859     enclosures[e].header.enclosure_id = e;
   1860   }
   1861   FOR_EACH(c, 0, cc_count) {
   1862     /* Update the enclosure ID in cc_descriptor */
   1863     enclosure_id_t new_id = new_ids[cc_descriptors[c]->enclosure_id];
   1864     ASSERT(new_id < scn->analyze.enclosures_count);
   1865     cc_descriptors[c]->enclosure_id = new_id;
   1866   }
   1867 error:
   1868   if(new_ids) MEM_RM(scn->dev->allocator, new_ids);
   1869   return res;
   1870 }
   1871 
   1872 static void
   1873 build_result
   1874   (struct senc3d_scene* scn,
   1875    const struct darray_ptr_component_descriptor* connex_components,
   1876    const struct darray_triangle_comp* triangles_comp_array,
   1877    struct darray_frontier_edge* frontiers,
   1878    /* Shared error status.
   1879     * We accept to overwrite an error with a different error */
   1880    res_T* res)
   1881 {
   1882   /* This function is called from an omp parallel block and executed
   1883    * concurrently. */
   1884   struct mem_allocator* alloc;
   1885   struct cc_descriptor* const* cc_descriptors;
   1886   struct enclosure_data* enclosures;
   1887   const struct triangle_in* triangles_in;
   1888   struct triangle_enc* triangles_enc;
   1889   const struct triangle_comp* triangles_comp;
   1890   struct htable_vrtx_id vtable;
   1891   int output_normal_in, normals_front, normals_back;
   1892   size_t cc_count;
   1893   int64_t tt;
   1894   int64_t ee;
   1895 
   1896   ASSERT(scn && connex_components && triangles_comp_array && frontiers && res);
   1897 
   1898   alloc = scn->dev->allocator;
   1899   output_normal_in = (scn->convention & SENC3D_CONVENTION_NORMAL_INSIDE) != 0;
   1900   normals_front = (scn->convention & SENC3D_CONVENTION_NORMAL_FRONT) != 0;
   1901   normals_back = (scn->convention & SENC3D_CONVENTION_NORMAL_BACK) != 0;
   1902   ASSERT(normals_back != normals_front);
   1903   ASSERT(output_normal_in
   1904     != ((scn->convention & SENC3D_CONVENTION_NORMAL_OUTSIDE) != 0));
   1905   cc_count = darray_ptr_component_descriptor_size_get(connex_components);
   1906   ASSERT(cc_count <= COMPONENT_MAX__);
   1907   cc_descriptors = darray_ptr_component_descriptor_cdata_get(connex_components);
   1908   enclosures = darray_enclosure_data_get(&scn->analyze.enclosures);
   1909   triangles_in = darray_triangle_in_cdata_get(&scn->triangles_in);
   1910   triangles_comp = darray_triangle_comp_cdata_get(triangles_comp_array);
   1911 
   1912   #pragma omp single
   1913   {
   1914     size_t c;
   1915     enclosure_id_t e;
   1916     res_T tmp_res =
   1917       darray_triangle_enc_resize(&scn->analyze.triangles_enc, scn->ntris);
   1918     if(tmp_res != RES_OK)
   1919       *res = tmp_res;
   1920     /* Sort enclosures by first side to ensure reproducible results */
   1921     else *res = reorder_enclosures(scn, connex_components);
   1922     if(*res != RES_OK) goto single_err;
   1923     /* Compute area and volume */
   1924     FOR_EACH(c, 0, cc_count) {
   1925       struct enclosure_data* enc;
   1926       ASSERT(cc_descriptors[c]->enclosure_id < scn->analyze.enclosures_count);
   1927       enc = enclosures + cc_descriptors[c]->enclosure_id;
   1928       /* Sum up areas and volumes of components int enclosures */
   1929       enc->header.area += cc_descriptors[c]->_2area;
   1930       enc->header.volume += cc_descriptors[c]->_6volume;
   1931     }
   1932     FOR_EACH(e, 0, scn->analyze.enclosures_count) {
   1933       struct enclosure_data* enc = enclosures + e;
   1934       enc->header.area /= 2;
   1935       enc->header.volume /= 6;
   1936     }
   1937   single_err: (void)0;
   1938   }/* Implicit barrier here. */
   1939   if(*res != RES_OK) goto exit;
   1940   triangles_enc = darray_triangle_enc_data_get(&scn->analyze.triangles_enc);
   1941 
   1942   /* Build global enclosure information */
   1943   #pragma omp for
   1944   for(tt = 0; tt < (int64_t)scn->ntris; tt++) {
   1945     trg_id_t t = (trg_id_t)tt;
   1946     const component_id_t cf_id = triangles_comp[t].component[SENC3D_FRONT];
   1947     const component_id_t cb_id = triangles_comp[t].component[SENC3D_BACK];
   1948     const struct cc_descriptor* cf = cc_descriptors[cf_id];
   1949     const struct cc_descriptor* cb = cc_descriptors[cb_id];
   1950     const enclosure_id_t ef_id = cf->enclosure_id;
   1951     const enclosure_id_t eb_id = cb->enclosure_id;
   1952     ASSERT(triangles_enc[t].enclosure[SENC3D_FRONT] == ENCLOSURE_NULL__);
   1953     triangles_enc[t].enclosure[SENC3D_FRONT] = ef_id;
   1954     ASSERT(triangles_enc[t].enclosure[SENC3D_BACK] == ENCLOSURE_NULL__);
   1955     triangles_enc[t].enclosure[SENC3D_BACK] = eb_id;
   1956   }
   1957   /* Implicit barrier here */
   1958 
   1959   /* Resize/push operations on enclosure's fields are valid in the
   1960    * openmp block as a given enclosure is processed by a single thread */
   1961   htable_vrtx_id_init(alloc, &vtable);
   1962 
   1963   ASSERT(scn->analyze.enclosures_count <= ENCLOSURE_MAX__);
   1964   #pragma omp for schedule(dynamic) nowait
   1965   for(ee = 0; ee < (int64_t)scn->analyze.enclosures_count; ee++) {
   1966     const enclosure_id_t e = (enclosure_id_t)ee;
   1967     struct enclosure_data* enc = enclosures + ee;
   1968     trg_id_t fst_idx = 0;
   1969     trg_id_t sgd_idx = enc->side_count;
   1970     trg_id_t t;
   1971     medium_id_t m;
   1972     res_T tmp_res = RES_OK;
   1973     ASSERT(enc->first_component < cc_count);
   1974     ASSERT(cc_descriptors[enc->first_component]->cc_id
   1975       == enc->first_component);
   1976 
   1977     if(*res != RES_OK) continue;
   1978     ASSERT(e <= ENCLOSURE_MAX__);
   1979     enc->header.enclosure_id = (unsigned)ee; /* Back to API type */
   1980     ASSERT(cc_descriptors[enc->first_component]->enclosure_id
   1981       == enc->header.enclosure_id);
   1982     enc->header.is_infinite = (e == 0);
   1983 
   1984     ASSERT(enc->header.enclosed_media_count
   1985       < darray_side_range_size_get(&scn->media_use));
   1986     OK2(bool_array_of_media_to_darray_media(&enc->enclosed_media,
   1987       &enc->tmp_enclosed_media, darray_side_range_size_get(&scn->media_use)));
   1988     ASSERT(darray_media_size_get(&enc->enclosed_media) <= MEDIUM_MAX__);
   1989     enc->header.enclosed_media_count
   1990       = (medium_id_t)darray_media_size_get(&enc->enclosed_media);
   1991     darray_uchar_purge(&enc->tmp_enclosed_media);
   1992 
   1993     /* Add this enclosure in relevant by-medium lists */
   1994     FOR_EACH(m, 0, enc->header.enclosed_media_count) {
   1995       medium_id_t medium = darray_media_cdata_get(&enc->enclosed_media)[m];
   1996       size_t m_idx = medium_id_2_medium_idx(medium);
   1997       struct darray_enc_id* enc_ids_array_by_medium;
   1998       ASSERT(medium == SENC3D_UNSPECIFIED_MEDIUM
   1999         || medium < darray_side_range_size_get(&scn->media_use) - 1);
   2000       ASSERT(darray_enc_ids_array_size_get(&scn->analyze.enc_ids_array_by_medium)
   2001         == darray_side_range_size_get(&scn->media_use));
   2002       enc_ids_array_by_medium =
   2003         darray_enc_ids_array_data_get(&scn->analyze.enc_ids_array_by_medium)
   2004         + m_idx;
   2005       #pragma omp critical
   2006       {
   2007         tmp_res = darray_enc_id_push_back(enc_ids_array_by_medium, &e);
   2008       }
   2009       if(tmp_res != RES_OK) {
   2010         *res = tmp_res;
   2011         break;
   2012       }
   2013     }
   2014     if(*res != RES_OK) continue;
   2015 
   2016     /* Build side and vertex lists. */
   2017     OK2(darray_sides_enc_resize(&enc->sides, enc->side_count));
   2018     /* Size is just a hint */
   2019     OK2(darray_vrtx_id_reserve(&enc->vertices,
   2020       (size_t)(enc->side_count * 0.6)));
   2021     /* New vertex numbering scheme local to the enclosure */
   2022     htable_vrtx_id_clear(&vtable);
   2023     ASSERT(scn->ntris == darray_triangle_in_size_get(&scn->triangles_in));
   2024     /* Put at the end the back-faces of triangles that also have their
   2025      * front-face in the list. */
   2026     for(t = TRGSIDE_2_TRG(enc->side_range.first);
   2027       t <= TRGSIDE_2_TRG(enc->side_range.last);
   2028       t++)
   2029     {
   2030       const struct triangle_in* trg_in = triangles_in + t;
   2031       struct side_enc* side_enc;
   2032       vrtx_id_t vertice_id[3];
   2033       int i;
   2034       if(triangles_enc[t].enclosure[SENC3D_FRONT] != e
   2035         && triangles_enc[t].enclosure[SENC3D_BACK] != e)
   2036         continue;
   2037       ++enc->header.unique_primitives_count;
   2038 
   2039       FOR_EACH(i, 0, 3) {
   2040         vrtx_id_t* p_id = htable_vrtx_id_find(&vtable, trg_in->vertice_id + i);
   2041         if(p_id) {
   2042           vertice_id[i] = *p_id; /* Known vertex */
   2043         } else {
   2044           /* Create new association */
   2045           size_t tmp = htable_vrtx_id_size_get(&vtable);
   2046           ASSERT(tmp == darray_vrtx_id_size_get(&enc->vertices));
   2047           ASSERT(tmp < VRTX_MAX__);
   2048           vertice_id[i] = (vrtx_id_t)tmp;
   2049           OK2(htable_vrtx_id_set(&vtable, trg_in->vertice_id + i,
   2050             vertice_id + i));
   2051           OK2(darray_vrtx_id_push_back(&enc->vertices, trg_in->vertice_id + i));
   2052           ++enc->header.vertices_count;
   2053         }
   2054       }
   2055       ASSERT(triangles_enc[t].enclosure[SENC3D_FRONT] == e
   2056         || triangles_enc[t].enclosure[SENC3D_BACK] == e);
   2057       if(triangles_enc[t].enclosure[SENC3D_FRONT] == e) {
   2058         /* Front side of the original triangle is member of the enclosure */
   2059         int input_normal_in = normals_front;
   2060         int revert_triangle = (input_normal_in != output_normal_in);
   2061         ++enc->header.primitives_count;
   2062         side_enc = darray_sides_enc_data_get(&enc->sides) + fst_idx++;
   2063         FOR_EACH(i, 0, 3) {
   2064           int ii = revert_triangle ? 2 - i : i;
   2065           side_enc->vertice_id[i] = vertice_id[ii];
   2066         }
   2067         side_enc->side_id = TRGIDxSIDE_2_TRGSIDE(t, SENC3D_FRONT);
   2068       }
   2069       if(triangles_enc[t].enclosure[SENC3D_BACK] == e) {
   2070         /* Back side of the original triangle is member of the enclosure */
   2071         int input_normal_in = normals_back;
   2072         int revert_triangle = (input_normal_in != output_normal_in);
   2073         ++enc->header.primitives_count;
   2074         /* If both sides are in the enclosure, put the second side at the end */
   2075         side_enc = darray_sides_enc_data_get(&enc->sides) +
   2076           ((triangles_enc[t].enclosure[SENC3D_FRONT] == e) ? --sgd_idx : fst_idx++);
   2077         FOR_EACH(i, 0, 3) {
   2078           int ii = revert_triangle ? 2 - i : i;
   2079           side_enc->vertice_id[i] = vertice_id[ii];
   2080         }
   2081         side_enc->side_id = TRGIDxSIDE_2_TRGSIDE(t, SENC3D_BACK);
   2082       }
   2083       if(fst_idx == sgd_idx) break;
   2084     }
   2085     continue;
   2086   tmp_error:
   2087     ASSERT(tmp_res != RES_OK);
   2088     *res = tmp_res;
   2089   } /* No barrier here */
   2090   htable_vrtx_id_release(&vtable);
   2091   /* The first thread here copies frontiers into descriptor */
   2092 #pragma omp single nowait
   2093   darray_frontier_edge_copy_and_clear(&scn->analyze.frontiers, frontiers);
   2094   /* No barrier here */
   2095 exit:
   2096   return;
   2097 }
   2098 
   2099 /******************************************************************************
   2100  * Exported functions
   2101  *****************************************************************************/
   2102 res_T
   2103 scene_analyze
   2104   (struct senc3d_scene* scn)
   2105 {
   2106   /* By triangle tmp data */
   2107   struct darray_triangle_tmp triangles_tmp;
   2108   char triangles_tmp_initialized = 0;
   2109   /* Array of connex components.
   2110    * They are refered to by arrays of ids.  */
   2111   struct darray_ptr_component_descriptor connex_components;
   2112   char connex_components_initialized = 0;
   2113   /* Array of frontiers edges */
   2114   struct darray_frontier_edge frontiers;
   2115   char frontiers_initialized = 0;
   2116   /* Htable used to store overlapping segments */
   2117   struct htable_overlap overlaps;
   2118   char overlaps_initialized = 0;
   2119   /* Store by-triangle components */
   2120   struct darray_triangle_comp triangles_comp;
   2121   char triangles_comp_initialized = 0;
   2122   /* Array of triangle sides. */
   2123   struct trgside* trgsides = NULL;
   2124   struct s3d_scene_view* s3d_view = NULL;
   2125   /* Atomic counters to share beetwen threads */
   2126   ATOMIC component_count = 0;
   2127   ATOMIC next_enclosure_id = 1;
   2128   res_T res = RES_OK;
   2129   res_T res2 = RES_OK;
   2130 
   2131   if(!scn) return RES_BAD_ARG;
   2132 
   2133   if(!scn->ntris) goto exit;
   2134 
   2135   darray_triangle_tmp_init(scn->dev->allocator, &triangles_tmp);
   2136   triangles_tmp_initialized = 1;
   2137   darray_frontier_edge_init(scn->dev->allocator, &frontiers);
   2138   frontiers_initialized = 1;
   2139   htable_overlap_init(scn->dev->allocator, &overlaps);
   2140   overlaps_initialized = 1;
   2141 
   2142   OK(darray_triangle_tmp_resize(&triangles_tmp, scn->ntris));
   2143   trgsides
   2144     = MEM_CALLOC(scn->dev->allocator, 2 * scn->ntris, sizeof(struct trgside));
   2145   if(!trgsides) {
   2146     res = RES_MEM_ERR;
   2147     goto error;
   2148   }
   2149 #ifndef NDEBUG
   2150   else {
   2151     /* Initialise trgsides to allow assert code */
   2152     size_t i;
   2153     FOR_EACH(i, 0, 2 * scn->ntris)
   2154       init_trgside(scn->dev->allocator, trgsides + i);
   2155   }
   2156 #endif
   2157 
   2158   /* The end of the analyze is multithreaded */
   2159   ASSERT(scn->dev->nthreads > 0);
   2160   #pragma omp parallel num_threads(scn->dev->nthreads)
   2161   {
   2162     /* Step 1: build neighbourhoods */
   2163     collect_and_link_neighbours(scn, trgsides, &triangles_tmp, &frontiers,
   2164       &overlaps, &res);
   2165     /* No barrier at the end of step 1: data used in step 1 cannot be
   2166      * released / data produced by step 1 cannot be used
   2167      * until next sync point */
   2168 
   2169     /* The first thread here allocates some data.
   2170      * Barrier needed at the end to ensure data created before any use. */
   2171     #pragma omp single
   2172     {
   2173       res_T tmp_res = RES_OK;
   2174       darray_ptr_component_descriptor_init(scn->dev->allocator,
   2175         &connex_components);
   2176       connex_components_initialized = 1;
   2177       /* Just a hint; to limit contention */
   2178       OK2(darray_ptr_component_descriptor_reserve(&connex_components,
   2179         2 * darray_side_range_size_get(&scn->media_use)));
   2180       darray_triangle_comp_init(scn->dev->allocator, &triangles_comp);
   2181       triangles_comp_initialized = 1;
   2182       OK2(darray_triangle_comp_resize(&triangles_comp, scn->ntris));
   2183     tmp_error:
   2184       if(tmp_res != RES_OK) res2 = tmp_res;
   2185     }
   2186     /* Implicit barrier here: constraints on step 1 data are now met */
   2187 
   2188 #pragma omp single
   2189     {
   2190       /* Save all the overlapping segments in a darray */
   2191       res_T tmp_res = RES_OK;
   2192       struct htable_overlap_iterator it, end;
   2193       ASSERT(overlaps_initialized);
   2194       htable_overlap_begin(&overlaps, &it);
   2195       htable_overlap_end(&overlaps, &end);
   2196       tmp_res = darray_trg_id_reserve(&scn->analyze.overlapping_ids,
   2197         htable_overlap_size_get(&overlaps));
   2198       if(tmp_res != RES_OK) goto tmp_error2;
   2199       while(!htable_overlap_iterator_eq(&it, &end)) {
   2200         tmp_res = darray_trg_id_push_back(&scn->analyze.overlapping_ids,
   2201           htable_overlap_iterator_key_get(&it));
   2202         if(tmp_res != RES_OK) goto tmp_error2;
   2203         htable_overlap_iterator_next(&it);
   2204       }
   2205       /* Sort overlapping triangle ids */
   2206       qsort(darray_trg_id_data_get(&scn->analyze.overlapping_ids),
   2207         darray_trg_id_size_get(&scn->analyze.overlapping_ids),
   2208         sizeof(*darray_trg_id_cdata_get(&scn->analyze.overlapping_ids)),
   2209         cmp_trg_id);
   2210       htable_overlap_release(&overlaps);
   2211       overlaps_initialized = 0;
   2212     tmp_error2:
   2213       if(tmp_res != RES_OK) res2 = tmp_res;
   2214     }
   2215 
   2216     if(darray_trg_id_size_get(&scn->analyze.overlapping_ids)) {
   2217       /* Stop analysis here as the model is ill-formed */
   2218       goto end_;
   2219     }
   2220 
   2221     if(res != RES_OK || res2 != RES_OK) {
   2222       #pragma omp single nowait
   2223       {
   2224         if(res != RES_OK) {
   2225           log_err(scn->dev,
   2226             LIB_NAME":%s: could not build neighbourhoods from scene\n",
   2227             FUNC_NAME);
   2228         } else {
   2229           res = res2;
   2230         }
   2231       }
   2232       goto error_;
   2233     }
   2234 
   2235     /* Step 2: extract triangle connex components */
   2236     extract_connex_components(scn, trgsides, &connex_components,
   2237       &triangles_tmp, &triangles_comp, &s3d_view, &component_count, &res);
   2238     /* No barrier at the end of step 2: data used in step 2 cannot be
   2239      * released / data produced by step 2 cannot be used
   2240      * until next sync point */
   2241 
   2242     #pragma omp barrier
   2243     /* Constraints on step 2 data are now met */
   2244 
   2245     if(res != RES_OK) {
   2246       #pragma omp single nowait
   2247       {
   2248         log_err(scn->dev,
   2249           LIB_NAME":%s: could not extract connex components from scene\n",
   2250           FUNC_NAME);
   2251       } /* No barrier here */
   2252       goto error_;
   2253     }
   2254 
   2255     /* One thread releases some data before going to step 3,
   2256      * the others go to step 3 without sync */
   2257     #pragma omp single nowait
   2258     {
   2259       darray_triangle_tmp_release(&triangles_tmp);
   2260       triangles_tmp_initialized = 0;
   2261     } /* No barrier here */
   2262 
   2263     /* Step 3: group components */
   2264     group_connex_components(scn, &triangles_comp, &connex_components, s3d_view,
   2265       &next_enclosure_id, &res);
   2266     /* Barrier at the end of step 3: data used in step 3 can be released /
   2267      * data produced by step 3 can be used */
   2268 
   2269     if(res != RES_OK) {
   2270       #pragma omp single nowait
   2271       {
   2272         log_err(scn->dev,
   2273           LIB_NAME":%s: could not group connex components from scene\n",
   2274           FUNC_NAME);
   2275       }
   2276       goto error_;
   2277     }
   2278 
   2279     /* One thread releases some data and allocate other data before going to
   2280      * step 4, the others waiting for alloced data */
   2281     #pragma omp single
   2282     {
   2283       if(s3d_view) S3D(scene_view_ref_put(s3d_view));
   2284       s3d_view = NULL;
   2285     } /* Implicit barrier here */
   2286     if(res != RES_OK) goto error_;
   2287 
   2288     /* Step 4: Build result */
   2289     build_result(scn, &connex_components, &triangles_comp, &frontiers, &res);
   2290     /* No barrier at the end of step 4: data used in step 4 cannot be
   2291      * released / data produced by step 4 cannot be used
   2292      * until next sync point */
   2293 
   2294     #pragma omp barrier
   2295     /* Constraints on step 4 data are now met */
   2296 
   2297     if(res != RES_OK) {
   2298       #pragma omp single nowait
   2299       {
   2300         log_err(scn->dev,
   2301           LIB_NAME":%s: could not build result\n", FUNC_NAME);
   2302       }
   2303       goto error_;
   2304     }
   2305 
   2306     /* Some threads release data */
   2307     #pragma omp sections nowait
   2308     {
   2309       #pragma omp section
   2310       {
   2311         custom_darray_ptr_component_descriptor_release(&connex_components);
   2312         connex_components_initialized = 0;
   2313       }
   2314       #pragma omp section
   2315       {
   2316         darray_triangle_comp_release(&triangles_comp);
   2317         triangles_comp_initialized = 0;
   2318       }
   2319     } /* No barrier here */
   2320 
   2321 end_:
   2322 error_:
   2323     ;
   2324   } /* Implicit barrier here */
   2325 
   2326   if(res != RES_OK) goto error;
   2327 exit:
   2328   if(connex_components_initialized)
   2329     custom_darray_ptr_component_descriptor_release(&connex_components);
   2330   if(s3d_view) S3D(scene_view_ref_put(s3d_view));
   2331   if(triangles_tmp_initialized) darray_triangle_tmp_release(&triangles_tmp);
   2332   if(triangles_comp_initialized) darray_triangle_comp_release(&triangles_comp);
   2333   if(frontiers_initialized) darray_frontier_edge_release(&frontiers);
   2334   if(overlaps_initialized) htable_overlap_release(&overlaps);
   2335   if(trgsides) MEM_RM(scn->dev->allocator, trgsides);
   2336 
   2337   return res;
   2338 error:
   2339   goto exit;
   2340 }