star-enclosures-2d

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

commit a0fd5bfdb863f94ca2f86bb4e74b3c16493d4e48
parent eda2d20a855b782ea530bef5b3a3e1a75e237dfe
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Fri, 22 Nov 2019 16:12:14 +0100

Add involved medias as args to the merge callback.

Merge can now decide to continue add_geometry even if media are
incompatible. In this case the client app is supposed to record the
error and stop after the add_geometry. A common use of this could
be to record all the incompatibilities and stop afterwards.

Diffstat:
Mcmake/CMakeLists.txt | 7++++---
Msrc/senc2d.h | 40++++++++++++++++++++++++++++------------
Msrc/senc2d_scene.c | 72++++++++++++++++++++++++++----------------------------------------------
Msrc/senc2d_scene_analyze.c | 2+-
Msrc/senc2d_scene_c.h | 1-
Asrc/test_senc2d_add_n_merge.c | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_senc2d_undefined_medium_attr.c | 12+++++++++++-
7 files changed, 275 insertions(+), 64 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -119,9 +119,7 @@ if(NOT NO_TEST) register_test(${_name} ${_name}) endfunction() - new_test(test_senc2d_square_behind_square) - new_test(test_senc2d_square_in_square) - new_test(test_senc2d_square_on_square) + new_test(test_senc2d_add_n_merge) new_test(test_senc2d_descriptor) new_test(test_senc2d_device) new_test(test_senc2d_enclosure) @@ -130,6 +128,9 @@ if(NOT NO_TEST) new_test(test_senc2d_many_segments) new_test(test_senc2d_sample_enclosure) new_test(test_senc2d_scene) + new_test(test_senc2d_square_behind_square) + new_test(test_senc2d_square_in_square) + new_test(test_senc2d_square_on_square) new_test(test_senc2d_undefined_medium) new_test(test_senc2d_undefined_medium_attr) diff --git a/src/senc2d.h b/src/senc2d.h @@ -171,17 +171,23 @@ senc2d_scene_reserve const unsigned media_count); /* Add a new set of vertices and segments to the scene. - * Vertices can be duplicates and are deduplicated on the fly. - * Segments can be duplicates as long as they constantly define the same - * medium on both sides (or an error will be reported, with the exception - * of SENC_UNDEFINED_MEDIUM that causes no error) and are deduplicated. + * Vertices can be duplicates and are silently deduplicated on the fly. + * Segments can be duplicates as long as media are compatible on both sides. + * Valid segment duplicates are silently deduplicated, invalid duplicates + * trigger an error (add_geometry returns RES_BAD_ARG). * The special value SENC2D_UNDEFINED_MEDIUM denotes an undefined medium. * It can be used to define the 2 sides of a segment at different times. + * Media on duplicate segments are consider compatible if: + * - the merge_segment callback is provided and returns RES_OK, + * - or media are identical or SENC2D_UNDEFINED_MEDIUM. * When deduplicating segments, the first occurence remains (with its - * original index in user world, regardless of deduplication). - * The add_segment and merge_segment callbacks can be used for attributes - * management including segment IDs; they allow the client app to store - * its own data. They can also fail and stop the add_geometry call. */ + * original index in user world, regardless of deduplication); the only + * situation where deduplication changes a previously recorded media is from + * SENC2D_UNDEFINED_MEDIUM to any defined medium. + * The add_segment and merge_segment callbacks can be used for attribute + * management (including segment IDs) and to record media incompatibilities for + * a subsequent report; they allow the client app to store its own data. + * By returning an error, they can also stop the add_geometry call. */ SENC2D_API res_T senc2d_scene_add_geometry (struct senc2d_scene* scene, @@ -198,16 +204,26 @@ senc2d_scene_add_geometry void(*position)(const unsigned ivert, double pos[2], void* context), /* Called for each new segment so that the client app can manage its own * segment data/properties/attributes. - * If return is not RES_OK, add_geometry stops and fails. */ + * If return is not RES_OK, add_geometry stops immediately and returns + * whatever value add_segment returned. */ res_T(*add_segment) /* Can be NULL */ (const unsigned global_id, const unsigned iseg, void* context), /* Called if the iseg_th segment of the current add_geometry is equal to * the global_id_th global segment so that the client app can try to merge - * its own segment data. The reversed_segment arg indicates if the segment - * vertices' order is the same it was when the segment was first added. - * If return is not RES_OK, add_geometry stops and fails. */ + * its own segment data or record a possible media conflict. + * The reversed_segment arg indicates if the segment vertices' order is the + * same it was when the segment was first added. + * segment_media and merge_media contain the involved media. + * If those media are incompatible and merge_segment returns RES_OK the + * process will continue with the next segment and possibly end in success. + * If return is not RES_OK, add_geometry stops immediately and returns + * whatever value merge_segment returned. + * If merge_segment is NULL, a strict media compatibility is required for + * add_geometry to success: add_geometry would stop and return RES_BAD_ARG + * on the first occurence of duplicate segment with incompatible media. */ res_T(*merge_segment) /* Can be NULL */ (const unsigned global_id, const unsigned iseg, const int reversed_segment, + const unsigned segment_media[2], const unsigned merge_media[2], void* context), void* context); diff --git a/src/senc2d_scene.c b/src/senc2d_scene.c @@ -80,7 +80,6 @@ senc2d_scene_create SENC2D(device_ref_get(dev)); scn->dev = dev; scn->convention = conv; - scn->ngeoms = 0; scn->nsegs = 0; scn->nusegs = 0; scn->next_medium_idx = 0; @@ -135,7 +134,8 @@ senc2d_scene_add_geometry const unsigned nverts, void(*position)(const unsigned, double* pos, void*), res_T(*add_segment)(const unsigned, const unsigned, void*), - res_T(*merge_segment)(const unsigned, const unsigned, const int, void*), + res_T(*merge_segment)(const unsigned, const unsigned, const int, + const unsigned*, const unsigned*, void*), void* ctx) { struct darray_vrtx_id unique_vertice_ids; @@ -173,10 +173,6 @@ senc2d_scene_add_geometry p_vrtx = htable_vrtx_find(&scn->unique_vertices, &tmp); if(p_vrtx) { /* Duplicate vertex */ - log_warn(scn->dev, "%s: vertex %lu is a duplicate of unique vertex %lu.\n", - FUNC_NAME, (unsigned long)(scn->nverts + i), (unsigned long)*p_vrtx); - log_warn(scn->dev, "%s: vertex %lu: (%g %g).\n", - FUNC_NAME, (unsigned long)(scn->nverts + i), SPLIT2(tmp.vec)); unique_v = *p_vrtx; } else { /* New vertex */ @@ -237,54 +233,39 @@ senc2d_scene_add_geometry reversed = seg_make_key(&seg_key, tmp.vertice_id); p_seg = htable_seg_find(&scn->unique_segments, &seg_key); if(p_seg) { + /* Duplicate segment. Need to check duplicate validity */ union vrtx_id2 useg_key; char ureversed = seg_make_key(&useg_key, seg[*p_seg].vertice_id); int same = (reversed == ureversed); const medium_id_t* umed; - /* Duplicate segment. Need to check duplicate validity */ ASSERT(seg_key_eq(&seg_key, &useg_key)); if(!same) SWAP(unsigned, tmp.medium[0], tmp.medium[1]); umed = seg[*p_seg].medium; - if(!compatible_medium(umed[0], tmp.medium[0]) - || !compatible_medium(umed[1], tmp.medium[1])) - { - /* Same segments with different media: invalid! */ - const union double2* positions - = darray_position_cdata_get(&scn->vertices); - log_err(scn->dev, "%s: segment %lu is a duplicate" - " of segment %lu with incoherent media.\n", - FUNC_NAME, (unsigned long)tmp.global_id, - (unsigned long)seg[*p_seg].global_id); - log_err(scn->dev, - "Segment %lu:\n (%g %g ) (%g %g)\n", - (unsigned long)seg[*p_seg].global_id, - SPLIT2(positions[seg[*p_seg].vertice_id[0]].vec), - SPLIT2(positions[seg[*p_seg].vertice_id[1]].vec)); - log_err(scn->dev, "Media: (%lu, %lu) VS (%lu, %lu)\n", - (unsigned long)umed[ureversed? 1 : 0], - (unsigned long)umed[ureversed ? 0 : 1], - (unsigned long)tmp.medium[reversed ? 1 : 0], - (unsigned long)tmp.medium[reversed ? 0 : 1]); - res = RES_BAD_ARG; - goto error; - } else { - /* Legit duplicate */ - range_adjust_ptr = darray_segment_in_data_get(&scn->segments_in) + *p_seg; - /* Replace possible undefined media */ + if(merge_segment) { + /* Let the client app rule. */ + unsigned smed[2], mmed[2]; FOR_EACH(j, 0, 2) { - if(range_adjust_ptr->medium[j] == SENC2D_UNDEFINED_MEDIUM - && tmp.medium[j] != SENC2D_UNDEFINED_MEDIUM) { - range_adjust_ptr->medium[j] = tmp.medium[j]; - scn->sides_with_defined_medium_count++; - } + smed[j] = (unsigned)umed[j]; + mmed[j] = (unsigned)tmp.medium[j]; } - if(merge_segment) { - OK(merge_segment(seg[*p_seg].global_id, i, same, ctx)); - } else { - log_warn(scn->dev, - "%s: segment %lu is a duplicate of segment %lu.\n", - FUNC_NAME, (unsigned long)tmp.global_id, - (unsigned long)seg[*p_seg].global_id); + OK(merge_segment(seg[*p_seg].global_id, i, same, smed, mmed, ctx)); + /* If merge_segment returns OK its OK even if media are incompatible. */ + } else { + if(!compatible_medium(umed[0], tmp.medium[0]) + || !compatible_medium(umed[1], tmp.medium[1])) + { + res = RES_BAD_ARG; + goto error; + } + } + /* Legit duplicate (or accepted by merge_segment): replace undef media. */ + range_adjust_ptr = darray_segment_in_data_get(&scn->segments_in) + *p_seg; + /* Replace possible undefined media */ + FOR_EACH(j, 0, 2) { + if(range_adjust_ptr->medium[j] == SENC2D_UNDEFINED_MEDIUM + && tmp.medium[j] != SENC2D_UNDEFINED_MEDIUM) { + range_adjust_ptr->medium[j] = tmp.medium[j]; + scn->sides_with_defined_medium_count++; } } } else { @@ -331,7 +312,6 @@ exit: scn->nsegs += actual_nsegs; ASSERT(scn->nuverts == htable_vrtx_size_get(&scn->unique_vertices)); ASSERT(scn->nusegs == htable_seg_size_get(&scn->unique_segments)); - ++scn->ngeoms; return res; error: goto exit; diff --git a/src/senc2d_scene_analyze.c b/src/senc2d_scene_analyze.c @@ -175,7 +175,7 @@ extract_connex_components /* If there are sides with undefined medium, its like another medium */ undefs = (scn->sides_with_defined_medium_count < 2 * scn->nsegs) ? 1 : 0; -#pragma omp single + #pragma omp single { if(undefs) { /* Range is unknown, process each side */ diff --git a/src/senc2d_scene_c.h b/src/senc2d_scene_c.h @@ -172,7 +172,6 @@ struct senc2d_scene { struct htable_seg unique_segments; /* Keep sizes */ - unsigned ngeoms; /* Not used yet (just counted). */ seg_id_t nsegs, nusegs; /* Segment count, unique segment count */ vrtx_id_t nverts, nuverts; /* Vrtx count, unique vrtx count */ medium_id_t next_medium_idx; diff --git a/src/test_senc2d_add_n_merge.c b/src/test_senc2d_add_n_merge.c @@ -0,0 +1,205 @@ +/* Copyright (C) |Meso|Star> 2016-2018 (contact@meso-star.com) +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "senc2d.h" +#include "test_senc2d_utils.h" + +/* Manage add_geometry behaviour */ +struct add_geom_ctx { + unsigned add_cpt, merge_cpt; + res_T add_res, merge_res; +}; + +static res_T +add_seg + (const unsigned global_id, + const unsigned iseg, + void* context) +{ + struct context* ctx = context; + struct add_geom_ctx* add_geom_ctx; + ASSERT(ctx); (void)global_id; (void)iseg; + add_geom_ctx = ctx->custom; + if(add_geom_ctx->add_res == RES_OK) ++add_geom_ctx->add_cpt; + return add_geom_ctx->add_res; +} + +static res_T +merge_seg + (const unsigned global_id, + const unsigned iseg, + const int reversed_segment, + const unsigned segment_media[2], + const unsigned merge_media[2], + void* context) +{ + struct context* ctx = context; + struct add_geom_ctx* add_geom_ctx; + ASSERT(ctx && segment_media && merge_media); + (void)global_id; (void)iseg; (void)reversed_segment; (void)segment_media; (void)merge_media; + add_geom_ctx = ctx->custom; + if(add_geom_ctx->merge_res == RES_OK) ++add_geom_ctx->merge_cpt; + return add_geom_ctx->merge_res; +} + +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct senc2d_device* dev = NULL; + struct senc2d_scene* scn = NULL; + struct context ctx; + unsigned i; + struct add_geom_ctx add_geom_ctx; + unsigned media[4]; + const int conv = SENC2D_CONVENTION_NORMAL_FRONT | SENC2D_CONVENTION_NORMAL_INSIDE; + const unsigned media_count = sizeof(media) / sizeof(*media); + (void)argc; (void)argv; + + OK(mem_init_proxy_allocator(&allocator, &mem_default_allocator)); + OK(senc2d_device_create(NULL, &allocator, SENC2D_NTHREADS_DEFAULT, 1, &dev)); + OK(senc2d_scene_create(dev, conv, &scn)); + + /* A 2D square. + * 2 enclosures (inside, outside) sharing the same segments, + * but opposite sides */ + ctx.positions = box_vertices; + ctx.indices = box_indices; + ctx.scale = 1; + ctx.reverse_vrtx = 0; + ctx.reverse_med = 0; + d2(ctx.offset, 0, 0); + ctx.front_media = media; + ctx.back_media = medium1; + ctx.custom = &add_geom_ctx; + + add_geom_ctx.add_cpt = add_geom_ctx.merge_cpt = 0; + add_geom_ctx.add_res = add_geom_ctx.merge_res = RES_OK; + + /* Geometry with no media information on both sides */ + for (i = 0; i < media_count; i++) media[i] = SENC2D_UNDEFINED_MEDIUM; + ctx.front_media = media; + ctx.back_media = media; + + /* If add fails, add geometry fails the same way */ + add_geom_ctx.add_res = RES_BAD_ARG; + BA(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, merge_seg, &ctx)); + CHK(add_geom_ctx.add_cpt == 0); + add_geom_ctx.add_res = RES_MEM_ERR; + CHK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, merge_seg, &ctx) == RES_MEM_ERR); + CHK(add_geom_ctx.add_cpt == 0); + + /* Successful add geometry with add callback */ + add_geom_ctx.add_res = RES_OK; + OK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, merge_seg, &ctx)); + CHK(add_geom_ctx.add_cpt == media_count); + CHK(add_geom_ctx.merge_cpt == 0); + + /* Clear scene */ + SENC2D(scene_ref_put(scn)); + OK(senc2d_scene_create(dev, conv, &scn)); + + /* Successful add geometry without add callback */ + add_geom_ctx.add_cpt = 0; + OK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, NULL, merge_seg, &ctx)); + CHK(add_geom_ctx.add_cpt == 0); + CHK(add_geom_ctx.merge_cpt == 0); + + /* If merge fails, add geometry fails the same way */ + add_geom_ctx.merge_res = RES_BAD_ARG; + BA(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, merge_seg, &ctx)); + CHK(add_geom_ctx.merge_cpt == 0); + add_geom_ctx.merge_res = RES_MEM_ERR; + CHK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, merge_seg, &ctx) == RES_MEM_ERR); + CHK(add_geom_ctx.merge_cpt == 0); + + /* Successful add geometry without merge callback */ + OK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, NULL, &ctx)); + CHK(add_geom_ctx.merge_cpt == 0); + + /* Successful add geometry with merge callback */ + add_geom_ctx.merge_res = RES_OK; + OK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, merge_seg, &ctx)); + CHK(add_geom_ctx.merge_cpt == media_count); + add_geom_ctx.merge_cpt = 0; + + /* Geometry with media information on both sides */ + ctx.front_media = medium0; + ctx.back_media = medium1; + + /* Clear scene */ + SENC2D(scene_ref_put(scn)); + OK(senc2d_scene_create(dev, conv, &scn)); + + /* Successful add geometry with add callback */ + add_geom_ctx.add_res = RES_OK; + OK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, merge_seg, &ctx)); + CHK(add_geom_ctx.add_cpt == media_count); + CHK(add_geom_ctx.merge_cpt == 0); + add_geom_ctx.add_cpt = 0; + + /* Clear scene */ + SENC2D(scene_ref_put(scn)); + OK(senc2d_scene_create(dev, conv, &scn)); + + /* Successful add geometry with add callback */ + add_geom_ctx.add_res = RES_OK; + OK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, merge_seg, &ctx)); + CHK(add_geom_ctx.add_cpt == media_count); + CHK(add_geom_ctx.merge_cpt == 0); + add_geom_ctx.add_cpt = 0; + + /* Successful add geometry with merge callback */ + add_geom_ctx.merge_res = RES_OK; + OK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, merge_seg, &ctx)); + CHK(add_geom_ctx.merge_cpt == media_count); + add_geom_ctx.merge_cpt = 0; + + /* Geometry with incompatible media information on both sides */ + ctx.front_media = medium1; + ctx.back_media = medium0; + + /* Unsuccessful add geometry without merge callback */ + OK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, NULL, &ctx)); + CHK(add_geom_ctx.merge_cpt == 0); + + /* Successful add geometry with merge callback */ + add_geom_ctx.merge_res = RES_OK; + OK(senc2d_scene_add_geometry(scn, nsegments, get_indices, NULL, + nvertices, get_position, add_seg, merge_seg, &ctx)); + CHK(add_geom_ctx.merge_cpt == media_count); + add_geom_ctx.merge_cpt = 0; + + SENC2D(scene_ref_put(scn)); + SENC2D(device_ref_put(dev)); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + + return 0; +} diff --git a/src/test_senc2d_undefined_medium_attr.c b/src/test_senc2d_undefined_medium_attr.c @@ -68,6 +68,8 @@ merge_seg (const unsigned global_id, const unsigned iseg, const int reversed_segment, + const unsigned segment_media[2], + const unsigned merge_media[2], void* context) { res_T res = RES_OK; @@ -76,7 +78,15 @@ merge_seg int need_merge; unsigned interf; unsigned* interf_data; - ASSERT(ctx); (void)reversed_segment; + int i, compat; + ASSERT(ctx); (void)reversed_segment; (void)segment_media; (void)merge_media; + /* Check media compatibility */ + compat = 1; + FOR_EACH(i, 0, 2) + compat &= (segment_media[i] == SENC2D_UNDEFINED_MEDIUM + || merge_media[i] == SENC2D_UNDEFINED_MEDIUM + || segment_media[i] == merge_media[i]); + if(!compat) return RES_BAD_ARG; merge_ctx = ctx->custom; res = darray_intface_id_resize(&merge_ctx->global_interf_data, global_id + 1); if(res != RES_OK) return res;