star-enclosures-3d

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

commit d8a52a4bb3bb0cc8653ba75080973c0e51581121
parent 1db74b6f5d469c3ac5f4020b5ded37637c1fe393
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Tue,  9 Jul 2024 11:17:46 +0200

BugFix components grouping stage

When grouping connex components, a radius is used to limit the number of
candidates. This radius is determinated in an initial step.
The bug was that the radius calculation could return a too small radius
that prevented considering some components, resulting in wrong grouping.

Diffstat:
Msrc/senc3d_scene_analyze.c | 280+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
1 file changed, 204 insertions(+), 76 deletions(-)

diff --git a/src/senc3d_scene_analyze.c b/src/senc3d_scene_analyze.c @@ -67,12 +67,14 @@ const struct cc_descriptor CC_DESCRIPTOR_NULL = CC_DESCRIPTOR_NULL__; * If ray.normal < threshold we suspect accuracy could be a problem */ #define DOT_THRESHOLD 0.0001f -struct filter_ctx0 { - component_id_t origin_component; - struct darray_triangle_comp* triangles_comp; +enum ctx_type { + CTX0, + CTX1, + CTX2 }; -struct filter_ctx1 { +struct filter_ctx { + enum ctx_type type; struct senc3d_scene* scn; struct s3d_scene_view* view; component_id_t origin_component; @@ -80,34 +82,14 @@ struct filter_ctx1 { struct darray_ptr_component_descriptor* components; /* Tmp data used across filter calls */ double current_6volume; + int cpt; float s; - /* Result of hit */ + /* Result of CTX1 hit */ component_id_t hit_component; float hit_dir[3], hit_dist; struct s3d_primitive hit_prim; }; -struct filter_ctx2 { - struct darray_triangle_comp* triangles_comp; - int cpt; - component_id_t component; -}; - -enum fctx_type { - FCTX0, - FCTX1, - FCTX2 -}; - -struct filter_ctx { - enum fctx_type type; - union { - struct filter_ctx0 ctx0; - struct filter_ctx1 ctx1; - struct filter_ctx2 ctx2; - } c; -}; - #define HTABLE_NAME overlap #define HTABLE_KEY trg_id_t #define HTABLE_DATA char @@ -129,7 +111,7 @@ static FINLINE int is_component_inside (struct cc_descriptor* cc1, struct cc_descriptor* cc2, - struct filter_ctx1* ctx) + struct filter_ctx* ctx) { int i; side_id_t side; @@ -184,13 +166,13 @@ is_component_inside d3_divd(pt, pt, 3); f3_set_d3(org, pt); /* Trace a ray and count intersections with component c */ - ctx2.type = FCTX2; - ctx2.c.ctx2.triangles_comp = ctx->triangles_comp; - ctx2.c.ctx2.cpt = 0; - ctx2.c.ctx2.component = cc1->cc_id; + ctx2.type = CTX2; + ctx2.triangles_comp = ctx->triangles_comp; + ctx2.cpt = 0; + ctx2.origin_component = cc1->cc_id; S3D(scene_view_trace_ray(ctx->view, org, dir, rg, &ctx2, &hit)); /* cc2 is not inside cc1 if cpt is even */ - if(ctx2.c.ctx2.cpt % 2 == 0) return 0; + if(ctx2.cpt % 2 == 0) return 0; return 1; } @@ -256,68 +238,203 @@ self_hit_filter void* ray_data, void* filter_data) { - struct filter_ctx* fctx_ = ray_data; + struct filter_ctx* ctx = ray_data; (void)ray_org; (void)ray_range; (void)filter_data; - ASSERT(fctx_); + ASSERT(ctx); ASSERT(hit->uv[0] == CLAMP(hit->uv[0], 0, 1)); ASSERT(hit->uv[1] == CLAMP(hit->uv[1], 0, 1)); - switch (fctx_->type) { + switch (ctx->type) { default: FATAL("Invalid"); - case FCTX2: { + case CTX2: { /* The filter is used to count the hits on some component along an * infinite ray */ - struct filter_ctx2* ctx2 = &fctx_->c.ctx2; const struct triangle_comp* trg_comp; const component_id_t* hit_comp; ASSERT(hit->prim.prim_id - < darray_triangle_comp_size_get(ctx2->triangles_comp)); - trg_comp = darray_triangle_comp_cdata_get(ctx2->triangles_comp); + < darray_triangle_comp_size_get(ctx->triangles_comp)); + trg_comp = darray_triangle_comp_cdata_get(ctx->triangles_comp); hit_comp = trg_comp[hit->prim.prim_id].component; - if(hit_comp[SENC3D_FRONT] == ctx2->component - || hit_comp[SENC3D_BACK] == ctx2->component) + if(hit_comp[SENC3D_FRONT] == ctx->origin_component + || hit_comp[SENC3D_BACK] == ctx->origin_component) { - ctx2->cpt++; + ctx->cpt++; } return 1; /* Reject to continue counting */ } - case FCTX0: { + case CTX0: { /* This filter is called from a closest point query from a point belonging * to origin_component. The returned hit is used to determine the search - * radius for FCTX1 main computation. */ - struct filter_ctx0* ctx = &fctx_->c.ctx0; + * radius for CTX1 main computation. */ const struct triangle_comp* trg_comp = darray_triangle_comp_cdata_get(ctx->triangles_comp); const component_id_t* hit_comp = trg_comp[hit->prim.prim_id].component; const component_id_t oc = ctx->origin_component; + vrtx_id_t other_id; + struct cc_descriptor* const* comp_descriptors + = darray_ptr_component_descriptor_cdata_get(ctx->components); + size_t compsz = darray_ptr_component_descriptor_size_get(ctx->components); + const union double3* + vertices = darray_position_cdata_get(&ctx->scn->vertices); + const double org_z = vertices[comp_descriptors[oc]->max_z_vrtx_id].pos.z; + float s = 0, hit_normal[3], rdir[3]; + enum senc3d_side hit_side; + const int log_components = + ctx->scn->convention & SENC3D_LOG_COMPONENTS_INFORMATION; ASSERT(hit->prim.prim_id < darray_triangle_comp_size_get(ctx->triangles_comp)); + ASSERT(hit_comp[SENC3D_FRONT] < compsz); + ASSERT(hit_comp[SENC3D_BACK] < compsz); + /* Hit acceptance must be coherent at CTX0 and FTCX1 stages: + * the search radius as found at stage CTX0 must include the hit that + * stage CTX1 will select using an infinite radius */ if(hit_comp[SENC3D_FRONT] == oc || hit_comp[SENC3D_BACK] == oc) { - /* Self hit */ - return 1; /* Reject */ + return 1; /* Self hit, reject */ } + if(hit->distance == 0) { + /* origin component is in contact with some other components + * We will need further exploration to know if they should be considered + * Accepting hit at distance 0 => radius is definitively 0 */ + int n; + + /* If same component, process only once */ + FOR_EACH(n, 0, (hit_comp[SENC3D_FRONT] == hit_comp[SENC3D_BACK] ? 1 : 2)) { + const enum senc3d_side sides[2] = { SENC3D_FRONT, SENC3D_BACK }; + component_id_t c = hit_comp[sides[n]]; + ASSERT(c < darray_ptr_component_descriptor_size_get(ctx->components)); + if(comp_descriptors[c]->is_outer_border) { + /* The inner component we are trying to link can only be linked to + * an outer component if it is inside */ + if(!is_component_inside(comp_descriptors[c], + comp_descriptors[oc], ctx)) + { + continue; + } - if(hit->distance > 0 && ray_dir[2] <= 0) { - return 1; /* Not upward */ + if(log_components) { + #pragma omp critical + printf("Component #%u: decreasing search radius " + "(R=%g, n=%g,%g,%g, components: %u, %u)\n", + oc, hit->distance, SPLIT3(hit->normal), + hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]); + } + return 0; + } else { + /* c is an inner component */ + vrtx_id_t c_z_id; + /* The inner component we are trying to link can only be linked to + * another inner component if (at least partly) above and not inside */ + c_z_id = comp_descriptors[c]->max_z_vrtx_id; + ASSERT(c_z_id < darray_position_size_get(&ctx->scn->vertices)); + ASSERT(vertices[c_z_id].pos.z >= org_z); + if(vertices[c_z_id].pos.z == org_z) { + continue; /* Not above */ + } + if(is_component_inside(comp_descriptors[c], + comp_descriptors[oc], ctx)) + { + continue; /* Inside */ + } + if(log_components) { + #pragma omp critical + printf("Component #%u: decreasing search radius " + "(R=%g, n=%g,%g,%g, components: %u, %u)\n", + oc, hit->distance, SPLIT3(hit->normal), + hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]); + } + return 0; + } + } + return 1; + } + + ASSERT(hit->distance > 0); + if(hit_comp[SENC3D_FRONT] == hit_comp[SENC3D_BACK]) { + /* Hit component is known, check if above */ + other_id = comp_descriptors[hit_comp[SENC3D_FRONT]]->max_z_vrtx_id; + ASSERT(other_id < darray_position_size_get(&ctx->scn->vertices)); + if(vertices[other_id].pos.z <= org_z) { + return 1; + } + if(log_components) { + #pragma omp critical + printf("Component #%u: decreasing search radius " + "(R=%g, n=%g,%g,%g, components: %u, %u)\n", + oc, hit->distance, SPLIT3(hit->normal), + hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]); + } + return 0; } - return 0; /* Keep*/ + /* Compute hit side */ + /* For s to be comparable, vectors must be normalized */ + f3_normalize(hit_normal, hit->normal); + f3_normalize(rdir, ray_dir); + s = f3_dot(rdir, hit_normal); /* Can be NaN for tiny distances */ + if(isnan(s)) { + /* Try to fix it */ + f3_divf(rdir, ray_dir, hit->distance); + f3_normalize(rdir, rdir); + s = f3_dot(rdir, hit_normal); + ASSERT(!isnan(s)); + } + + if(fabsf(s) < DOT_THRESHOLD) { + /* We cannot know for sure which side to consider */ + vrtx_id_t i1 = comp_descriptors[hit_comp[SENC3D_FRONT]]->max_z_vrtx_id; + vrtx_id_t i2 = comp_descriptors[hit_comp[SENC3D_BACK]]->max_z_vrtx_id; + double possible_z = MMIN(vertices[i1].pos.z, vertices[i2].pos.z); + if(possible_z > org_z) { + /* Both components are above origin component => keep */ + if(log_components) { + #pragma omp critical + printf("Component #%u: decreasing search radius " + "(R=%g, n=%g,%g,%g, components: %u, %u)\n", + oc, hit->distance, SPLIT3(hit->normal), + hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]); + } + return 0; + } + /* Cannot be sure => the safest choice is to reject */ + return 1; + } + /* Determine which side was hit */ + hit_side = + ((s < 0) /* Facing geometrical normal of hit */ + == ((ctx->scn->convention & SENC3D_CONVENTION_NORMAL_FRONT) != 0)) + /* Warning: following Embree 2 convention for geometrical normals, + * the Star3D hit normal is left-handed while star-enclosures-3d uses + * right-handed convention */ + ? SENC3D_BACK : SENC3D_FRONT; + other_id = comp_descriptors[hit_comp[hit_side]]->max_z_vrtx_id; + ASSERT(other_id < darray_position_size_get(&ctx->scn->vertices)); + if(vertices[other_id].pos.z <= org_z) { + return 1; + } + if(log_components) { + #pragma omp critical + printf("Component #%u: decreasing search radius " + "(R=%g, n=%g,%g,%g, components: %u, %u)\n", + oc, hit->distance, SPLIT3(hit->normal), + hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]); + } + return 0; } - case FCTX1: { + case CTX1: { /* This filter is called from a closest point query from a point belonging * to origin_component. The returned hit is used to determine a component * to which origin_component is linked. At a later stage the algorithm * process linked components to determine their relative inclusions. * * This filter is called with a search distance that has been ajusted in - * FCTX0 filter. This distance must be left unchanged to ensure visiting + * CTX0 filter. This distance must be left unchanged to ensure visiting * all the surfaces at the determined distance: allways reject hits to * avoid decreasing search distance. * @@ -330,7 +447,6 @@ self_hit_filter * (greater volume needed), or they can be disjoint, with (at least) ray_org * as a common vertex (they can also partially intersect, but this is invalid * and remains undetected by star enclosures). */ - struct filter_ctx1* ctx = &fctx_->c.ctx1; struct cc_descriptor* const* comp_descriptors = darray_ptr_component_descriptor_cdata_get(ctx->components); const struct triangle_comp* @@ -362,6 +478,9 @@ self_hit_filter hit_comp[SENC3D_FRONT], hit_comp[SENC3D_BACK]); } + /* Has CTX1 filter do not reduce search radius, hits can be processed + * that are at farther distance than the current best candidate: we need + * to reject them here */ if(hit->distance > ctx->hit_dist) { /* No improvement */ if(log_components) { @@ -372,6 +491,9 @@ self_hit_filter return 1; } + /* Hit acceptance must be coherent at CTX0 and FTCX1 stages: + * the search radius as found at stage CTX0 must include the hit that + * stage CTX1 will select using an infinite radius */ if(hit_comp[SENC3D_FRONT] == oc || hit_comp[SENC3D_BACK] == oc) { /* Self hit */ if(log_components) { @@ -425,7 +547,7 @@ self_hit_filter * we are looking for is the smallest one (to manage outer component * inside another outer component). */ if((ctx->hit_component != COMPONENT_NULL__ - && !comp_descriptors[ctx->hit_component]->is_outer_border ) + && !comp_descriptors[ctx->hit_component]->is_outer_border) || v < ctx->current_6volume) { ctx->hit_component = c; ctx->current_6volume = v; @@ -462,7 +584,7 @@ self_hit_filter printf("Component #%u: hit inner component #%u\n", oc, c); } if(ctx->hit_component != COMPONENT_NULL__ - && comp_descriptors[ctx->hit_component]->is_outer_border ) + && comp_descriptors[ctx->hit_component]->is_outer_border) { if(log_components) { #pragma omp critical @@ -528,8 +650,7 @@ self_hit_filter if(vertices[other_id].pos.z <= org_z) { if(log_components) { #pragma omp critical - printf("Component #%u: 2 sides, not (even in part) above: reject\n", - oc); + printf("Component #%u: 2 sides, not above: reject\n", oc); } return 1; } @@ -548,7 +669,7 @@ self_hit_filter } /* Compute hit side */ - /* For s to be comparable, vectors must be normailzed */ + /* For s to be comparable, vectors must be normalized */ f3_normalize(hit_normal, hit->normal); f3_normalize(rdir, ray_dir); s = f3_dot(rdir, hit_normal); /* Can be NaN for tiny distances */ @@ -580,9 +701,10 @@ self_hit_filter vrtx_id_t i2 = comp_descriptors[hit_comp[SENC3D_BACK]]->max_z_vrtx_id; double possible_z = MMAX(vertices[i1].pos.z, vertices[i2].pos.z); if(possible_z <= org_z) { + /* None of the components are above origin component => reject */ if(log_components) { #pragma omp critical - printf("Component #%u: tiny s, not (even in part) above: reject\n", + printf("Component #%u: none of the components above: reject\n", oc); } return 1; @@ -1246,13 +1368,16 @@ group_connex_components scene_cpt++; } - ctx0.type = FCTX0; - ctx0.c.ctx0.triangles_comp = triangles_comp; - ctx1.type = FCTX1; - ctx1.c.ctx1.scn = scn; - ctx1.c.ctx1.view = s3d_view; - ctx1.c.ctx1.triangles_comp = triangles_comp; - ctx1.c.ctx1.components = connex_components; + ctx0.type = CTX0; + ctx0.scn = scn; + ctx0.view = s3d_view; + ctx0.triangles_comp = triangles_comp; + ctx0.components = connex_components; + ctx1.type = CTX1; + ctx1.scn = scn; + ctx1.view = s3d_view; + ctx1.triangles_comp = triangles_comp; + ctx1.components = connex_components; *res = s3d_scene_view_get_aabb(s3d_view, lower, upper); if(*res != RES_OK) goto end; #pragma omp for schedule(dynamic) @@ -1294,8 +1419,11 @@ group_connex_components rrr[i] = MMIN(origin[i] - lower[i], upper[i] - origin[i]); } rrr[2] = upper[2] - origin[2]; - r = f3_len(rrr) + FLT_EPSILON; /* Ensure r > 0 */ - ctx0.c.ctx0.origin_component = cc->cc_id; + r = f3_len(rrr) * (1 + FLT_EPSILON) + FLT_EPSILON; /* Ensure r > 0 */ + ctx0.origin_component = cc->cc_id; + ctx0.current_6volume = DBL_MAX; + ctx0.hit_dist = FLT_MAX; + ctx0.hit_component = COMPONENT_NULL__; tmp_res = s3d_scene_view_closest_point(s3d_view, origin, r, &ctx0, &hit); if(tmp_res != RES_OK) { *res = tmp_res; @@ -1313,10 +1441,10 @@ group_connex_components } /* Second step is to determine which component faces component #c */ - ctx1.c.ctx1.origin_component = cc->cc_id; - ctx1.c.ctx1.current_6volume = DBL_MAX; - ctx1.c.ctx1.hit_dist = FLT_MAX; - ctx1.c.ctx1.hit_component = COMPONENT_NULL__; + ctx1.origin_component = cc->cc_id; + ctx1.current_6volume = DBL_MAX; + ctx1.hit_dist = FLT_MAX; + ctx1.hit_component = COMPONENT_NULL__; /* New search radius is hit.distance + some margin to cope with numerical * issues (and r==0 is an error) */ r = hit.distance * (1 + FLT_EPSILON) + FLT_EPSILON; @@ -1331,10 +1459,10 @@ group_connex_components *res = tmp_res; continue; } - /* As FCTX1 filter rejects any hit, do not rely on hit but use result as + /* As CTX1 filter rejects any hit, do not rely on hit but use result as * stored in ctx1 */ /* If no hit is accepted, the component is facing an infinite medium */ - if(ctx1.c.ctx1.hit_dist == FLT_MAX) { + if(ctx1.hit_dist == FLT_MAX) { cc->cc_group_root = CC_GROUP_ROOT_INFINITE; cc->enclosure_id = 0; if(log_components) { @@ -1343,7 +1471,7 @@ group_connex_components } continue; } - else if(ctx1.c.ctx1.hit_component == COMPONENT_NULL__) { + else if(ctx1.hit_component == COMPONENT_NULL__) { /* The selected triangle was nearly parallel to the line of sight: * FRONT/BACK discrimination was not reliable enough and should be done * differently. */ @@ -1354,7 +1482,7 @@ group_connex_components continue; } else { /* If hit, group this component */ - cc->cc_group_root = ctx1.c.ctx1.hit_component; + cc->cc_group_root = ctx1.hit_component; ASSERT(cc->cc_group_root < cc_count); if(log_components) { #pragma omp critical