star-cpr

Clip 2D meshes with 2D polygons
git clone git://git.meso-star.fr/star-cpr.git
Log | Files | Refs | README | LICENSE

commit efaad32a88ee0b8c4d239117cc6d871e3abd0507
parent 7e38ca7227aa5f65400773b9c59df11f62924421
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Tue, 28 Feb 2023 12:06:14 +0100

Merge remote-tracking branch 'origin/feature_check_intersections' into develop

Diffstat:
Mcmake/CMakeLists.txt | 4+++-
Msrc/scpr.h | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/scpr_c.h | 199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Asrc/scpr_intersector.c | 802+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/scpr_mesh.c | 72++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/scpr_polygon.c | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Asrc/test_scpr_intersector.c | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_scpr_polygon.c | 21+++++++++++++++++++++
8 files changed, 1438 insertions(+), 41 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -29,7 +29,7 @@ set(Clipper2_DIR ${_current_source_dir}/) find_package(RCMake REQUIRED) find_package(RSys 0.6 REQUIRED) -find_package(Clipper2 1.1 REQUIRED) +find_package(Clipper2 1.2 REQUIRED) find_package(Polygon 0.0.5 REQUIRED) include_directories( @@ -53,6 +53,7 @@ set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) set(SCPR_FILES_SRC scpr_device.c + scpr_intersector.c scpr_mesh.c scpr_polygon.c) set(SCPR_FILES_INC_API scpr.h) @@ -97,6 +98,7 @@ if(NOT NO_TEST) endfunction() new_test(test_scpr_clip) new_test(test_scpr_device) + new_test(test_scpr_intersector) new_test(test_scpr_offset) new_test(test_scpr_mesh) new_test(test_scpr_polygon) diff --git a/src/scpr.h b/src/scpr.h @@ -60,6 +60,7 @@ struct mem_allocator; struct scpr_device; struct scpr_polygon; struct scpr_mesh; +struct scpr_intersector; /* Input arguments of the scpr_device_create function */ struct scpr_device_create_args { @@ -74,6 +75,32 @@ struct scpr_device_create_args { static const struct scpr_device_create_args SCPR_DEVICE_CREATE_ARGS_DEFAULT = SCPR_DEVICE_CREATE_ARGS_DEFAULT__; +/* A struct to represent segments when calling user callbacks. */ +struct scpr_callback_segment { + struct scpr_polygon* polygon; + size_t component; + size_t first_vertex; + size_t last_vertex; +}; + +/* A struct holding callbacks that are called when carying an intersection check + * on a bunch of segments. + * If a callbacks is NULL, the corresponding events are unreported, but at least + * 1 callback must be non NULL. + * If a callback call returns non-zero, the whole scpr_intersector_check call + * exits whith a RES_BAD_ARG error status without further processing. */ +struct scpr_intersector_check_callbacks { + int(*simple_intersection) + (struct scpr_callback_segment* segment1, + struct scpr_callback_segment* segment2, + void* ctx); + int(*collinear_segments) + (struct scpr_callback_segment* segment1, + struct scpr_callback_segment* segment2, + void* ctx); +}; +#define SCPR_INTERSECTOR_CHECK_CALLBACKS_NULL__ { NULL, NULL } + BEGIN_DECLS /******************************************************************************* @@ -211,6 +238,17 @@ scpr_polygon_eq const struct scpr_polygon* polygon2, int* is_eq); +SCPR_API res_T +scpr_polygon_dump_to_obj + (struct scpr_polygon* polygon, + FILE* stream); + +SCPR_API res_T +scpr_polygon_dump_component_to_obj + (struct scpr_polygon* polygon, + const size_t icomponent, + FILE* stream); + /******************************************************************************* * Type of meshes, as manipulated by star-cpr. * Meshes can be made of any number of triangles and are subject to a range @@ -267,6 +305,41 @@ scpr_mesh_get_position const size_t ivert, double position[2]); +/******************************************************************************* + * An intersector provide a way to check for polygon intersections, either + * self intersections or intersections between polygons. + ******************************************************************************/ +SCPR_API res_T +scpr_intersector_create + (struct scpr_device* dev, + struct scpr_intersector** intersector); + +SCPR_API res_T +scpr_intersector_ref_get + (struct scpr_intersector* intersector); + +SCPR_API res_T +scpr_intersector_ref_put + (struct scpr_intersector* intersector); + +/* Register a polygon's a polygon or a component for further analysis */ +SCPR_API res_T +scpr_intersector_register_polygon + (struct scpr_intersector* intersector, + struct scpr_polygon* polygon); + +SCPR_API res_T +scpr_intersector_register_component + (struct scpr_intersector* intersector, + struct scpr_polygon* polygon, + const size_t icomponent); + +SCPR_API res_T +scpr_intersector_check + (struct scpr_intersector* intersector, + struct scpr_intersector_check_callbacks* callbacks, + void* data); /* Client data set as the last param of the callbacks */ + END_DECLS #endif /* SCPR_H */ diff --git a/src/scpr_c.h b/src/scpr_c.h @@ -18,11 +18,11 @@ #include "scpr.h" +#include <cstddef> #include <rsys/rsys.h> #include <rsys/ref_count.h> #include <rsys/double2.h> #include <rsys/dynamic_array.h> -#include <rsys/dynamic_array_size_t.h> #include <rsys/hash_table.h> #include <math.h> @@ -30,6 +30,10 @@ #undef PI #include <clipper2/clipper.h> +#define DARRAY_NAME uint32 +#define DARRAY_DATA uint32_t +#include <rsys/dynamic_array.h> + #define DARRAY_NAME int64 #define DARRAY_DATA int64_t #include <rsys/dynamic_array.h> @@ -58,7 +62,7 @@ vertex_eq(const struct vertex* a, const struct vertex* b) /* Define the vertex to index hash table */ #define HTABLE_NAME vertex -#define HTABLE_DATA size_t +#define HTABLE_DATA uint32_t #define HTABLE_KEY struct vertex #define HTABLE_KEY_FUNCTOR_EQ vertex_eq #include <rsys/hash_table.h> @@ -84,10 +88,199 @@ struct scpr_polygon { struct scpr_mesh { struct darray_int64 coords; - struct darray_size_t indices; + struct darray_uint32 indices; ref_T ref; struct scpr_device* device; }; +struct intersector_segment { + uint32_t component, first_vertex; +}; +#define DARRAY_NAME segments +#define DARRAY_DATA struct intersector_segment +#include <rsys/dynamic_array.h> + +/* A segment htable links a count (1 or 2) to a segment index in the segments + * darray. + * The count is the number of ends of the segment at some range_point. */ +#define HTABLE_NAME segment_idx +#define HTABLE_KEY uint32_t +#define HTABLE_DATA char +#include <rsys/hash_table.h> + +/* Each range_point stores the segments that have 1 or 2 ends there */ +struct intersector_range_point { + struct htable_segment_idx segments; + int64_t coord; +}; +static FINLINE void +range_point_init + (struct mem_allocator* alloc, struct intersector_range_point* data) +{ + data->coord = INT64_MIN; + htable_segment_idx_init(alloc, &data->segments); +} +static FINLINE void +range_point_release(struct intersector_range_point* data) +{ + htable_segment_idx_release(&data->segments); +} +static FINLINE res_T +range_point_copy + (struct intersector_range_point* dst, struct intersector_range_point const* src) +{ + dst->coord = src->coord; + return htable_segment_idx_copy(&dst->segments, &src->segments); +} +static FINLINE res_T +range_point_copy_and_release + (struct intersector_range_point* dst, struct intersector_range_point* src) +{ + dst->coord = src->coord; + return htable_segment_idx_copy_and_release(&dst->segments, &src->segments); +} +/* A darray of range_point. */ +#define DARRAY_NAME range_points +#define DARRAY_DATA struct intersector_range_point +#define DARRAY_FUNCTOR_INIT range_point_init +#define DARRAY_FUNCTOR_COPY range_point_copy +#define DARRAY_FUNCTOR_RELEASE range_point_release +#define DARRAY_FUNCTOR_COPY_AND_RELEASE range_point_copy_and_release +#include <rsys/dynamic_array.h> + +/* An htable to link a pointer to a range_point to an 1D coordinate */ +#define HTABLE_NAME range_point_idx +#define HTABLE_KEY int64_t +#define HTABLE_DATA uint32_t +#include <rsys/hash_table.h> + +enum segment_interaction { + NO_INTERACTION = 0, + OVERLAP_X = BIT(0), + CONTACT_X = BIT(1), + OVERLAP_Y = BIT(2), + CONTACT_Y = BIT(3), + SOMETHING_X = OVERLAP_X | CONTACT_X, + SOMETHING_Y = OVERLAP_Y | CONTACT_Y, + SOME_OVERLAP = OVERLAP_X | OVERLAP_Y +}; +/* A dimension is a structure containing all the points where a segment starts + * or ends, each one storing a link to all the segments involved there. */ +struct intersector_dimension { + /* Unsorted 1D points registered against this dimension */ + struct htable_range_point_idx unsorted; + /* Same points, but sorted by increasing coordinates along this dimension */ + struct darray_range_points sorted; + /* Range on this dimension */ + int64_t range[2]; + /* Flags about segments interaction for this dimension */ + enum segment_interaction overlap, contact; +}; + +/* An htable to store overlapping information */ +struct intersector_segment_pair { + uint32_t rank1, rank2; +}; +static INLINE char +pair_eq + (struct intersector_segment_pair const* a, + struct intersector_segment_pair const* b) +{ + ASSERT(a && b); + return a->rank1 == b->rank1 && a->rank2 == b->rank2; +} +#define HTABLE_NAME interacting_segments +#define HTABLE_KEY struct intersector_segment_pair +#define HTABLE_DATA unsigned char +#define HTABLE_KEY_FUNCTOR_EQ pair_eq +#include <rsys/hash_table.h> + +/* An htable to keep track of registered components */ +#pragma pack(push, 1) /* Avoid padding */ +struct intersector_component { + scpr_polygon* polygon; + uint32_t component; + /* If there is padding, hashing reports uninitialized data in htable code */ +}; +#pragma pack(pop) +static FINLINE void +component_init(struct mem_allocator* alloc, struct intersector_component* key) +{ + ASSERT(alloc && key); + (void)alloc; + key->polygon = NULL; + key->component = UINT32_MAX; +} +static INLINE char +component_eq + (struct intersector_component const* a, + struct intersector_component const* b) +{ + ASSERT(a && b); + return a->polygon == b->polygon && a->component == b->component; +} +static FINLINE void +component_release(struct intersector_component* key) +{ + ASSERT(key); + if(key->polygon) SCPR(polygon_ref_put(key->polygon)); +} +static FINLINE res_T +component_copy + (struct intersector_component* dst, struct intersector_component const* src) +{ + ASSERT(dst && src); + dst->polygon = src->polygon; + dst->component = src->component; + if(dst->polygon) SCPR(polygon_ref_get(dst->polygon)); + return RES_OK; +} +static FINLINE res_T +component_copy_and_release + (struct intersector_component* dst, struct intersector_component* src) +{ + ASSERT(dst && src); + dst->polygon = src->polygon; + dst->component = src->component; + return RES_OK; +} +#define HTABLE_NAME registered_components +#define HTABLE_KEY struct intersector_component +#define HTABLE_DATA char +#define HTABLE_KEY_FUNCTOR_INIT component_init +#define HTABLE_KEY_FUNCTOR_EQ component_eq +#define HTABLE_KEY_FUNCTOR_COPY component_copy +#define HTABLE_KEY_FUNCTOR_RELEASE component_release +#define HTABLE_KEY_FUNCTOR_COPY_AND_RELEASE component_copy_and_release +#include <rsys/hash_table.h> + +#define DARRAY_NAME components +#define DARRAY_DATA struct intersector_component +#define DARRAY_FUNCTOR_INIT component_init +#define DARRAY_FUNCTOR_EQ component_eq +#define DARRAY_FUNCTOR_COPY component_copy +#define DARRAY_FUNCTOR_RELEASE component_release +#define DARRAY_FUNCTOR_COPY_AND_RELEASE component_copy_and_release +#include <rsys/dynamic_array.h> + +/* A set of segments from different polygons spatially sorted to help find + * (self-)intersections. */ +struct scpr_intersector { + struct scpr_device* device; + /* All the registered segments */ + struct darray_segments segments; + /* Spatial partition of the segments along the 2 dimensions */ + struct intersector_dimension dimensions[2]; + /* All the pairs of segment that are in interaction in some way or another. + * They are all the candidates for actual intersection plus some others that + * are just close, but are without interaction. */ + struct htable_interacting_segments interacting_segments; + /* All the registererd components (an htable to check for unicity and a darray + * to store the components with indexing capability) */ + struct htable_registered_components registererd_components; + struct darray_components components; + ref_T ref; +}; + #endif diff --git a/src/scpr_intersector.c b/src/scpr_intersector.c @@ -0,0 +1,802 @@ +/* Copyright (C) 2016-2018, 2021 |Meso|Star> (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 "scpr.h" +#include "scpr_c.h" + +#include <polygon.h> +#include <rsys/logger.h> +#include <rsys/ref_count.h> +#include <rsys/mem_allocator.h> +#include <rsys/rsys.h> +#include <rsys/double2.h> +#include <rsys/double3.h> + +#undef PI +#include <clipper2/clipper.h> + +#include <new> +#include <stdlib.h> +#include <math.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +init_dimension + (struct mem_allocator* allocator, + struct intersector_dimension* dim, + enum segment_interaction overlap, + enum segment_interaction share) +{ + ASSERT(dim && allocator); + htable_range_point_idx_init(allocator, &dim->unsorted); + darray_range_points_init(allocator, &dim->sorted); + dim->range[0] = INT64_MAX; + dim->range[1] = INT64_MIN; + dim->overlap= overlap; + dim->contact = share; +} + +static void +release_dimension + (struct intersector_dimension* dim) +{ + ASSERT(dim); + htable_range_point_idx_release(&dim->unsorted); + darray_range_points_release(&dim->sorted); +} + +static void +intersector_release(ref_T* ref) +{ + struct scpr_intersector* intersector; + struct mem_allocator* allocator; + ASSERT(ref); + intersector = CONTAINER_OF(ref, struct scpr_intersector, ref); + allocator = intersector->device->allocator; + darray_segments_release(&intersector->segments); + release_dimension(intersector->dimensions); + release_dimension(intersector->dimensions + 1); + SCPR(device_ref_put(intersector->device)); + htable_interacting_segments_release(&intersector->interacting_segments); + htable_registered_components_release(&intersector->registererd_components); + darray_components_release(&intersector->components); + MEM_RM(allocator, intersector); +} + +/* Register a point (a segment end) against a dimension */ +static res_T +register_point + (struct scpr_intersector* intersector, + const int dim_idx, + const int64_t coord, + const uint32_t seg_idx) +{ + uint32_t* pidx; + uint32_t rp_idx; + struct intersector_dimension* dim; + struct intersector_range_point* sorted; + res_T res = RES_OK; + ASSERT(intersector); + + dim = intersector->dimensions + dim_idx; + pidx = htable_range_point_idx_find(&dim->unsorted, &coord); + if(!pidx) { + /* First time this point: create it in sorted and store its index in unsorted */ + char one = 1; + size_t count = darray_range_points_size_get(&dim->sorted); + ERR(darray_range_points_resize(&dim->sorted, 1+count)); + sorted = darray_range_points_data_get(&dim->sorted); + rp_idx = (uint32_t)count; + sorted[rp_idx].coord = coord; + /* Register this segment at this range point */ + ERR(htable_segment_idx_set(&sorted[rp_idx].segments, &seg_idx, &one)); + /* Update range */ + if(dim->range[0] > coord) dim->range[0] = coord; + if(dim->range[1] < coord) dim->range[1] = coord; + } else { + /* This point is already known: look for seg_idx already there */ + char *found, one_or_two; + rp_idx = *pidx; + sorted = darray_range_points_data_get(&dim->sorted); + found = htable_segment_idx_find(&sorted[rp_idx].segments, &seg_idx); + one_or_two = found ? 2 : 1; + ERR(htable_segment_idx_set(&sorted[rp_idx].segments, &seg_idx, &one_or_two)); + } + ERR(htable_range_point_idx_set(&dim->unsorted, &coord, &rp_idx)); + +exit: + return res; +error: + goto exit; +} + +/* Compare 2 range_points */ +static int +compare_range_points(const void* a, const void* b) +{ + struct intersector_range_point *pa = (struct intersector_range_point*)a; + struct intersector_range_point *pb = (struct intersector_range_point*)b; + ASSERT(a && b && pa->coord != pb->coord); + return (int)(pa->coord - pb->coord); +} + +/* Sort the range points by increasing coordinates */ +static void +sort_points + (struct intersector_dimension* dim) +{ + size_t count; + struct intersector_range_point* sorted; + ASSERT(dim); + + /* All the range points are already in sorted; just need to sort them */ + count = darray_range_points_size_get(&dim->sorted); + ASSERT(count == htable_range_point_idx_size_get(&dim->unsorted)); + sorted = darray_range_points_data_get(&dim->sorted); + qsort(sorted, count, sizeof(*sorted), compare_range_points); +} + +/* Create a segment pair suitable for use as an htable key + * To ensure unicity, pairs are ordered */ +static void +init_segment_pair + (struct intersector_segment_pair* pair, + const uint32_t seg_idx1, + const uint32_t seg_idx2) +{ + ASSERT(pair); + pair->rank1 = seg_idx1; + pair->rank2 = seg_idx2; + if(pair->rank1 > pair->rank2) { + SWAP(uint32_t, pair->rank1, pair->rank2); + } +} + +#ifndef NDEBUG +static int +segments_bbox_interacts_1_core + (int64_t lower1, int64_t upper1, int64_t lower2, int64_t upper2) +{ + if(lower1 > upper2) return 0; + if(lower2 > upper1) return 0; + return 1; +} + +/* Check if 2 polygon Bbox interact */ +static int +segments_bbox_interacts + (Clipper2Lib::Point64 *v11, + Clipper2Lib::Point64 *v12, + Clipper2Lib::Point64 *v21, + Clipper2Lib::Point64 *v22) +{ + int64_t lower1[2], upper1[2], lower2[2], upper2[2]; + ASSERT(v11 && v12 && v21 && v22); + lower1[0] = MMIN(v11->x, v12->x); + lower1[1] = MMIN(v11->y, v12->y); + lower2[0] = MMIN(v21->x, v22->x); + lower2[1] = MMIN(v21->y, v22->y); + upper1[0] = MMAX(v11->x, v12->x); + upper1[1] = MMAX(v11->y, v12->y); + upper2[0] = MMAX(v21->x, v22->x); + upper2[1] = MMAX(v21->y, v22->y); + if(!segments_bbox_interacts_1_core(lower1[0], upper1[0], lower2[0], upper2[0])) + return 0; + return segments_bbox_interacts_1_core(lower1[1], upper1[1], lower2[1], upper2[1]); +} + +static int +segments_bbox_interacts_1 + (uint32_t seg_idx1, + uint32_t seg_idx2, + enum segment_interaction inter, + struct scpr_intersector* intersector) +{ + const struct intersector_segment *segments, *seg1, *seg2; + Clipper2Lib::Point64 *v11, * v12, *v21, *v22; + int64_t lower1, upper1, lower2, upper2; + size_t count, r11, r12, r21, r22; + const struct intersector_component* components; + struct scpr_polygon *polygon1, *polygon2; + uint32_t comp_idx1, comp_idx2; + + segments = darray_segments_cdata_get(&intersector->segments); + seg1 = segments + seg_idx1; + seg2 = segments + seg_idx2; + components = darray_components_cdata_get(&intersector->components); + comp_idx1 = components[seg1->component].component; + comp_idx2 = components[seg2->component].component; + polygon1 = components[seg1->component].polygon; + polygon2 = components[seg2->component].polygon; + + /* Get vertices */ + r11 = seg1->first_vertex; + SCPR(polygon_get_vertices_count(polygon1, comp_idx1, &count)); + r12 = (r11 + 1) % count; + r21 = seg2->first_vertex; + SCPR(polygon_get_vertices_count(polygon2, comp_idx2, &count)); + r22 = (r21 + 1) % count; + v11 = &polygon1->paths[comp_idx1][r11]; + v12 = &polygon1->paths[comp_idx1][r12]; + v21 = &polygon2->paths[comp_idx2][r21]; + v22 = &polygon2->paths[comp_idx2][r22]; + if(inter & SOMETHING_X) { + lower1 = MMIN(v11->x, v12->x); + upper1 = MMAX(v11->x, v12->x); + lower2 = MMIN(v21->x, v22->x); + upper2 = MMAX(v21->x, v22->x); + } else { + lower1 = MMIN(v11->y, v12->y); + upper1 = MMAX(v11->y, v12->y); + lower2 = MMIN(v21->y, v22->y); + upper2 = MMAX(v21->y, v22->y); + } + return segments_bbox_interacts_1_core(lower1, upper1, lower2, upper2); +} +#endif + +/* Register interaction of 2 segments along 1 dimension */ +static res_T +register_segment_interaction + (const uint32_t seg_idx1, + const uint32_t seg_idx2, + const int allow_pair_creation, + const enum segment_interaction inter, + struct scpr_intersector* intersector) +{ + res_T res = RES_OK; + struct intersector_segment_pair pair; + unsigned char *pinteract; + + ASSERT(intersector); + if(seg_idx1 != seg_idx2) { + ASSERT(segments_bbox_interacts_1(seg_idx1, seg_idx2, inter, intersector)); + + /* Fill a record for this pair of segments */ + init_segment_pair(&pair, seg_idx1, seg_idx2); + pinteract = + htable_interacting_segments_find(&intersector->interacting_segments, &pair); + if(pinteract || allow_pair_creation) { + unsigned char interact; + if(!pinteract) { /* First occurence of this pair: create empty record */ + interact = (unsigned char)inter; + } else { + ASSERT((*pinteract | inter) < UCHAR_MAX); + interact = (unsigned char)(*pinteract | inter); + } + + /* Register this interaction */ + ERR(htable_interacting_segments_set(&intersector->interacting_segments, + &pair, &interact)); + } + } + +exit: + return res; +error: + goto exit; +} + +/* Register interaction of segment of seg_idx with all the segments in + * open_segments along 1 dimension */ +static res_T +register_segment_interactions + (const uint32_t seg_idx, + struct htable_segment_idx* open_segments, + const int allow_pair_creation, + const enum segment_interaction inter, + struct scpr_intersector* intersector) +{ + res_T res = RES_OK; + struct htable_segment_idx_iterator it, end; + + ASSERT(open_segments && intersector); + + htable_segment_idx_begin(open_segments, &it); + htable_segment_idx_end(open_segments, &end); + /* Loop on segments using this point */ + while(!htable_segment_idx_iterator_eq(&it, &end)) { + uint32_t seg2_idx = *htable_segment_idx_iterator_key_get(&it); + ERR(register_segment_interaction(seg_idx, seg2_idx, allow_pair_creation, + inter, intersector)); + htable_segment_idx_iterator_next(&it); + } + +exit: + return res; +error: + goto exit; +} + +/* Register segments interaction along 1 dimension */ +static res_T +register_interaction + (const int dim_idx, + struct scpr_intersector* intersector) +{ + res_T res = RES_OK; + size_t count, i; + struct intersector_range_point* points; + struct htable_segment_idx open_segments; + struct mem_allocator* allocator; + struct intersector_dimension* dimension; + char *begins = NULL, *ends = NULL; + uint32_t* seg_index = NULL; + ASSERT(intersector); + + allocator = intersector->device->allocator; + dimension = intersector->dimensions + dim_idx; + htable_segment_idx_init(allocator, &open_segments); + count = darray_range_points_size_get(&dimension->sorted); + points = darray_range_points_data_get(&dimension->sorted); + for(i = 0; i < count; i++) { + struct intersector_range_point* point = points + i; + struct htable_segment_idx_iterator it, end; + size_t scount, s; + + /* 0) Init begins, ends and seg_index + * Cannot recompute them on the fly as found will change */ + scount = htable_segment_idx_size_get(&point->segments); + begins = (char*)MEM_REALLOC(allocator, begins, scount * sizeof(*begins)); + ends = (char*)MEM_REALLOC(allocator, ends, scount * sizeof(*ends)); + seg_index = (uint32_t*)MEM_REALLOC(allocator, seg_index, + scount * sizeof(*seg_index)); + if(!begins || !ends || !seg_index) { + res = RES_MEM_ERR; + goto error; + } + s = 0; + htable_segment_idx_begin(&point->segments, &it); + htable_segment_idx_end(&point->segments, &end); + while(!htable_segment_idx_iterator_eq(&it, &end)) { + char c = *htable_segment_idx_iterator_data_get(&it); + uint32_t seg_idx = *htable_segment_idx_iterator_key_get(&it); + if(c == 2) { /* Both ends of the segment at this point */ + ends[s] = begins[s] = 1; + } else { + char* found = htable_segment_idx_find(&open_segments, &seg_idx); + begins[s] = (found == NULL); + ends[s] = (found != NULL); + } + seg_index[s] = seg_idx; + s++; + htable_segment_idx_iterator_next(&it); + } + ASSERT(s == scount); + + /* 1) Segments beginning and ending at this point overlap other segments that + * begin and end at the same point and contact segments that only begin or + * only end at this point */ + for(s = 0; s < scount; s++) { + if(begins[s] && ends[s]) { + size_t j; + for(j = 0; j < scount; j++) { + if(j > s && begins[j] && ends[j]) { + ERR(register_segment_interaction(seg_index[s], seg_index[j], !dim_idx, + dimension->overlap, intersector)); + } + else if(begins[j] ^ ends[j]) { + ERR(register_segment_interaction(seg_index[s], seg_index[j], !dim_idx, + dimension->contact, intersector)); + } + } + } + } + + /* 2) Segments only ending at this point overlap open segments */ + for(s = 0; s < scount; s++) { + if(!begins[s] && ends[s]) { + ERR(register_segment_interactions(seg_index[s], &open_segments, !dim_idx, + dimension->overlap, intersector)); + } + } + + /* 3) Segments ending at this point are now closed */ + for(s = 0; s < scount; s++) { + if(ends[s]) { + size_t n = htable_segment_idx_erase(&open_segments, seg_index + s); + CHK(n == (begins[s] ? 0 : 1)); + } + } + + /* 4) Segments beginning and ending at this point overlap open segments */ + for(s = 0; s < scount; s++) { + if(begins[s] && ends[s]) { + ERR(register_segment_interactions(seg_index[s], &open_segments, !dim_idx, + dimension->overlap, intersector)); + } + } + + /* 5) Segments only beginning at this point are now open */ + for(s = 0; s < scount; s++) { + if(begins[s] && !ends[s]) { + char one = 1; + ERR(htable_segment_idx_set(&open_segments, seg_index + s, &one)); + } + } + + /* 6) Segments only beginning at this point overlap open segments */ + for(s = 0; s < scount; s++) { + if(begins[s] && !ends[s]) { + ERR(register_segment_interactions(seg_index[s], &open_segments, !dim_idx, + dimension->overlap, intersector)); + } + } + } + +exit: + MEM_RM(allocator, begins); + MEM_RM(allocator, ends); + MEM_RM(allocator, seg_index); + htable_segment_idx_release(&open_segments); + return res; +error: + goto exit; +} + +static int64_t +safe_mul + (int64_t a, + int64_t b, + int* ovflw) +{ + if(*ovflw) return 0; + if(b != 0 && abs(a) > 1 + (INT64_MAX / abs(b))) goto error; + return a * b; +error: + *ovflw = 1; + return 0; +} + +/* Check if a triangle is CCW (>0), CW (<0), or flat (=0) */ +static int +is_ccw + (Clipper2Lib::Point64* v1, + Clipper2Lib::Point64* v2, + Clipper2Lib::Point64* v3, + int* ovflw) +{ + int64_t tmp1, tmp2; + ASSERT(v1 && v2 && v3); + /* Limited coordinate range garanties that sub computations stay in int64_t + * range */ + tmp1 = safe_mul(v2->x - v1->x, v3->y - v1->y, ovflw); + tmp2 = safe_mul(v3->x - v1->x, v2->y - v1->y, ovflw); + if(tmp1 < tmp2) return -1; + if(tmp1 > tmp2) return +1; + return 0; +} + +/* Check if 2 segments intersect */ +static res_T +check_two_segments_intersection + (const struct intersector_segment* seg1, + const struct intersector_segment* seg2, + struct scpr_intersector_check_callbacks* callbacks, + struct scpr_intersector* intersector, + void* data) +{ + res_T res = RES_OK; + size_t count, r11, r12, r21, r22; + Clipper2Lib::Point64 *v11, * v12, *v21, *v22; + int ccw1, ccw2, ccw3, ccw4, ovflw = 0; + const struct intersector_component* components; + struct scpr_polygon *polygon1, *polygon2; + uint32_t comp_idx1, comp_idx2; + + ASSERT(seg1 && seg2 && callbacks); + + components = darray_components_cdata_get(&intersector->components); + comp_idx1 = components[seg1->component].component; + comp_idx2 = components[seg2->component].component; + polygon1 = components[seg1->component].polygon; + polygon2 = components[seg2->component].polygon; + + /* Get vertices */ + r11 = seg1->first_vertex; + ERR(scpr_polygon_get_vertices_count(polygon1, comp_idx1, &count)); + r12 = (r11 + 1) % count; + r21 = seg2->first_vertex; + ERR(scpr_polygon_get_vertices_count(polygon2, comp_idx2, &count)); + r22 = (r21 + 1) % count; + v11 = &polygon1->paths[comp_idx1][r11]; + v12 = &polygon1->paths[comp_idx1][r12]; + v21 = &polygon2->paths[comp_idx2][r21]; + v22 = &polygon2->paths[comp_idx2][r22]; + ASSERT(segments_bbox_interacts(v11, v12, v21, v22)); + + /* Test triangles orientation */ + ccw1 = is_ccw(v11, v12, v21, &ovflw); + ccw2 = is_ccw(v11, v12, v22, &ovflw); + ccw3 = is_ccw(v21, v22, v11, &ovflw); + ccw4 = is_ccw(v21, v22, v12, &ovflw); + if(ovflw) { + res = RES_BAD_ARG; + goto error; + } + /* Analyse ccw results */ + if(ccw1 * ccw2 > 0 && ccw3 * ccw4 > 0) { + /* No intersection at all */ + } + else if(ccw1 == 0 && ccw2 == 0 && ccw3 == 0 && ccw4 == 0) { + /* collinear segments */ + struct scpr_callback_segment arg1, arg2; + arg1.polygon = polygon1; + arg1.component = comp_idx1; + arg1.first_vertex = r11; + arg1.last_vertex = r12; + arg2.polygon = polygon2; + arg2.component = comp_idx2; + arg2.first_vertex = r21; + arg2.last_vertex = r22; + if(callbacks->collinear_segments + && callbacks->collinear_segments(&arg1, &arg2, data)) + { + res = RES_BAD_ARG; + goto error; + } + } + else if(ccw1 * ccw2 < 0 && ccw3 * ccw4 < 0) { + /* Basic segment intersection */ + struct scpr_callback_segment arg1, arg2; + arg1.polygon = polygon1; + arg1.component = comp_idx1; + arg1.first_vertex = r11; + arg1.last_vertex = r12; + arg2.polygon = polygon2; + arg2.component = comp_idx2; + arg2.first_vertex = r21; + arg2.last_vertex = r22; + if(callbacks->simple_intersection + && callbacks->simple_intersection(&arg1, &arg2, data)) + { + res = RES_BAD_ARG; + goto error; + } +} + +exit: + return res; +error: + goto exit; +} + +/* Test intersection of all the registered pairs of interacting segments */ +static res_T +check_interacting_pairs + (struct scpr_intersector* intersector, + struct scpr_intersector_check_callbacks* callbacks, + void* data) +{ + res_T res = RES_OK; + struct htable_interacting_segments_iterator it, end; + const struct intersector_segment* segments; + + ASSERT(intersector && callbacks); + + segments = darray_segments_cdata_get(&intersector->segments); + htable_interacting_segments_begin(&intersector->interacting_segments, &it); + htable_interacting_segments_end(&intersector->interacting_segments, &end); + /* Loop on interacting segments */ + while(!htable_interacting_segments_iterator_eq(&it, &end)) { + unsigned char overlapping + = *htable_interacting_segments_iterator_data_get(&it); + if(overlapping & SOME_OVERLAP + && overlapping & SOMETHING_X && overlapping & SOMETHING_Y) + { + struct intersector_segment_pair* pair + = htable_interacting_segments_iterator_key_get(&it); + const struct intersector_segment* seg1 = segments + pair->rank1; + const struct intersector_segment* seg2 = segments + pair->rank2; + ERR(check_two_segments_intersection(seg1, seg2, callbacks, intersector, data)); + } + htable_interacting_segments_iterator_next(&it); + } + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +scpr_intersector_create + (struct scpr_device* device, + struct scpr_intersector** out_intersector) +{ + res_T res = RES_OK; + struct scpr_intersector* intersector = NULL; + struct mem_allocator* allocator; + + if(!device || ! out_intersector) { + res = RES_BAD_ARG; + goto error; + } + + allocator = device->allocator; + intersector = (struct scpr_intersector*)MEM_ALLOC(allocator, sizeof(*intersector)); + if(!intersector) { + res = RES_MEM_ERR; + goto error; + } + ref_init(&intersector->ref); + intersector->device = device; + ERR(scpr_device_ref_get(device)); + darray_segments_init(allocator, &intersector->segments); + init_dimension(allocator, intersector->dimensions, OVERLAP_X, CONTACT_X); + init_dimension(allocator, intersector->dimensions + 1, OVERLAP_Y, CONTACT_Y); + htable_interacting_segments_init(allocator, &intersector->interacting_segments); + htable_registered_components_init(allocator, &intersector->registererd_components); + darray_components_init(allocator, &intersector->components); + +exit: + if(out_intersector) *out_intersector = intersector; + return res; +error: + if(intersector) { + SCPR(intersector_ref_put(intersector)); + intersector = NULL; + } + goto exit; +} + +res_T +scpr_intersector_ref_get + (struct scpr_intersector* intersector) +{ + if(!intersector) return RES_BAD_ARG; + ref_get(&intersector->ref); + return RES_OK; +} + +res_T +scpr_intersector_ref_put + (struct scpr_intersector* intersector) +{ + if(!intersector) return RES_BAD_ARG; + ref_put(&intersector->ref, intersector_release); + return RES_OK; +} + +res_T +scpr_intersector_register_polygon + (struct scpr_intersector* intersector, + struct scpr_polygon* polygon) +{ + res_T res = RES_OK; + size_t i; + + if(!intersector || !polygon) { + res = RES_BAD_ARG; + goto error; + } + + for(i = 0; i < polygon->paths.size(); i++) { + ERR(scpr_intersector_register_component(intersector, polygon, i)); + } + +exit: + return res; +error: + goto exit; +} + +res_T +scpr_intersector_register_component + (struct scpr_intersector* intersector, + struct scpr_polygon* polygon, + const size_t icomponent) +{ + res_T res = RES_OK; + uint32_t i; + size_t new_count, prev_count, c; + struct intersector_component comp; + char *found, one = 1; + + if(!intersector || !polygon || icomponent >= polygon->paths.size()) { + res = RES_BAD_ARG; + goto error; + } + + /* Check single registration */ + comp.polygon = polygon; + comp.component = (uint32_t)icomponent; + found = htable_registered_components_find(&intersector->registererd_components, + &comp); + if(found) { + logger_print(intersector->device->logger, LOG_ERROR, + "Component %zu already registered.\n", icomponent); + res = RES_BAD_ARG; + goto error; + } + c = htable_registered_components_size_get(&intersector->registererd_components); + if(c >= UINT32_MAX) { + logger_print(intersector->device->logger, LOG_ERROR, + "Too many components registered.\n"); + res = RES_BAD_ARG; + goto error; + } + ASSERT(c == darray_components_size_get(&intersector->components)); + ERR(htable_registered_components_set(&intersector->registererd_components, + &comp, &one)); + ERR(darray_components_push_back(&intersector->components, &comp)); + + prev_count = darray_segments_size_get(&intersector->segments); + ASSERT(prev_count <= UINT32_MAX); + new_count = polygon->paths[icomponent].size(); + if(prev_count + new_count > UINT32_MAX) { + logger_print(intersector->device->logger, LOG_ERROR, + "Too many segments registered.\n"); + res = RES_BAD_ARG; + goto error; + } + ERR(darray_segments_resize(&intersector->segments, + (uint32_t)(prev_count + new_count))); + + for(i = 0; i < new_count; i++) { + Clipper2Lib::Point64 *p0, *p1; + uint32_t seg_idx = (uint32_t)(prev_count + i); + struct intersector_segment* segment + = darray_segments_data_get(&intersector->segments) + seg_idx; + segment->component = (uint32_t)c; + segment->first_vertex = i; + p0 = &polygon->paths[icomponent][i]; + p1 = &polygon->paths[icomponent][(i+1)%new_count]; + ERR(register_point(intersector, 0, p0->x, seg_idx)); + ERR(register_point(intersector, 0, p1->x, seg_idx)); + ERR(register_point(intersector, 1, p0->y, seg_idx)); + ERR(register_point(intersector, 1, p1->y, seg_idx)); + } + +exit: + return res; +error: + goto exit; +} + +res_T +scpr_intersector_check + (struct scpr_intersector* intersector, + struct scpr_intersector_check_callbacks* callbacks, + void* data) +{ + int i; + res_T res = RES_OK; + + if(!intersector || !callbacks + || (!callbacks->simple_intersection && !callbacks->collinear_segments)) + { + res = RES_BAD_ARG; + goto error; + } + + /* Construct candidates for intersection */ + for(i = 0; i < 2; i++) { + sort_points(&intersector->dimensions[i]); + ERR(register_interaction(i, intersector)); + } + /* Check intersections */ + ERR(check_interacting_pairs(intersector, callbacks, data)); + +exit: + return res; +error: + goto exit; +} diff --git a/src/scpr_mesh.c b/src/scpr_mesh.c @@ -17,6 +17,7 @@ #include "scpr_c.h" #include <rsys/mem_allocator.h> +#include <rsys/logger.h> #include <polygon.h> #undef PI @@ -68,7 +69,7 @@ triangle_compute_aabb ASSERT(tri && lower && upper); lower[0] = lower[1] = INT64_MAX; - upper[0] = upper[1] = -INT64_MAX; + upper[0] = upper[1] = INT64_MIN; FOR_EACH(ivert, 0, 3) { int i; for(i = 0; i < 2; i++) { @@ -82,11 +83,11 @@ static res_T register_vertex (const int64_t pos[2], struct darray_int64* coords, - struct darray_size_t* indices, + struct darray_uint32* indices, struct htable_vertex* vertices) { struct vertex v; - size_t* pid, id; + uint32_t* pid, id; unsigned i; res_T res = RES_OK; ASSERT(pos && coords && indices && vertices); @@ -103,14 +104,15 @@ register_vertex } else { const size_t count = darray_int64_size_get(coords); + ASSERT(count <= UINT32_MAX); ERR(darray_int64_resize(coords, count+2/*#coords*/)); for(i = 0; i < 2; i++) darray_int64_data_get(coords)[count+i] = pos[i]; - id = count / 2; + id = (uint32_t)count / 2; ERR(htable_vertex_set(vertices, &v, &id)); } - ERR(darray_size_t_push_back(indices, &id)); + ERR(darray_uint32_push_back(indices, &id)); exit: return res; @@ -122,7 +124,7 @@ static FINLINE res_T register_triangle (int64_t tri[3][2], struct darray_int64* coords, /* Vertex buffer */ - struct darray_size_t* indices, /* Index buffer */ + struct darray_uint32* indices, /* Index buffer */ struct htable_vertex* vertices) /* Map a vertex to its index */ { size_t ivert; @@ -143,7 +145,7 @@ register_paths (struct scpr_device* dev, const Clipper2Lib::Paths64& paths, struct darray_int64* coords, /* Vertex buffer */ - struct darray_size_t* indices, /* Index buffer */ + struct darray_uint32* indices, /* Index buffer */ struct htable_vertex* vertices, /* Map a vertex to its index */ struct polygon* polygon) /* Used to triangulate the clipped polygons */ { @@ -209,7 +211,7 @@ mesh_compute_aabb SCPR(mesh_get_triangles_count(mesh, &ntris)); lower[0] = lower[1] = INT64_MAX; - upper[0] = upper[1] = -INT64_MAX; + upper[0] = upper[1] = INT64_MIN; FOR_EACH(itri, 0, ntris) { size_t ids[3], ivert; @@ -237,7 +239,7 @@ mesh_release(ref_T* ref) allocator = mesh->device->allocator; SCPR(device_ref_put(mesh->device)); darray_int64_release(&mesh->coords); - darray_size_t_release(&mesh->indices); + darray_uint32_release(&mesh->indices); MEM_RM(allocator, mesh); } @@ -267,7 +269,7 @@ scpr_mesh_create mesh->device = dev; SCPR(device_ref_get(dev)); darray_int64_init(dev->allocator, &mesh->coords); - darray_size_t_init(dev->allocator, &mesh->indices); + darray_uint32_init(dev->allocator, &mesh->indices); exit: if(out_mesh) *out_mesh = mesh; @@ -307,8 +309,8 @@ scpr_mesh_setup_indexed_vertices void* data) { int64_t* pos = NULL; - size_t* ids = NULL; - size_t i; + uint32_t* ids = NULL; + size_t i, j; res_T res = RES_OK; if(!mesh || !ntris || !get_indices || !nverts || !get_position || !data) { @@ -316,8 +318,22 @@ scpr_mesh_setup_indexed_vertices goto error; } + if(ntris > UINT32_MAX) { + logger_print(mesh->device->logger, LOG_ERROR, + "Too many triangles.\n"); + res = RES_BAD_ARG; + goto error; + } + + if(nverts > UINT32_MAX) { + logger_print(mesh->device->logger, LOG_ERROR, + "Too many vertices.\n"); + res = RES_BAD_ARG; + goto error; + } + ERR(darray_int64_resize(&mesh->coords, nverts*2/*#coords per vertex*/)); - ERR(darray_size_t_resize(&mesh->indices, ntris*3/*#vertices per triangle*/)); + ERR(darray_uint32_resize(&mesh->indices, ntris*3/*#vertices per triangle*/)); /* Fetch mesh positions */ pos = darray_int64_data_get(&mesh->coords); @@ -328,15 +344,15 @@ scpr_mesh_setup_indexed_vertices } /* Fetch mesh indices */ - ids = darray_size_t_data_get(&mesh->indices); + ids = darray_uint32_data_get(&mesh->indices); FOR_EACH(i, 0, ntris) { - get_indices(i, ids + i*3, data); - if(ids[i*3+0] >= nverts - || ids[i*3+1] >= nverts - || ids[i*3+2] >= nverts) { + size_t tmp[3]; + get_indices(i, tmp, data); + if(tmp[0] >= nverts || tmp[1] >= nverts || tmp[2] >= nverts) { res = RES_BAD_ARG; goto error; } + for(j = 0; j < 3; j++) ids[i*3 + j] = (uint32_t)tmp[j]; } exit: @@ -344,7 +360,7 @@ exit: error: if(mesh) { darray_int64_clear(&mesh->coords); - darray_size_t_clear(&mesh->indices); + darray_uint32_clear(&mesh->indices); } goto exit; } @@ -362,8 +378,8 @@ res_T scpr_mesh_get_triangles_count(const struct scpr_mesh* mesh, size_t* ntris) { if(!mesh || !ntris) return RES_BAD_ARG; - ASSERT((darray_size_t_size_get(&mesh->indices)%3/*#vertices per triangle*/)==0); - *ntris = darray_size_t_size_get(&mesh->indices) / 3/*#vertices per triangle*/; + ASSERT((darray_uint32_size_get(&mesh->indices)%3/*#vertices per triangle*/)==0); + *ntris = darray_uint32_size_get(&mesh->indices) / 3/*#vertices per triangle*/; return RES_OK; } @@ -376,9 +392,9 @@ scpr_mesh_get_indices if(!mesh || !ids) return RES_BAD_ARG; SCPR(mesh_get_triangles_count(mesh, &ntris)); if(itri >= ntris) return RES_BAD_ARG; - ids[0] = darray_size_t_cdata_get(&mesh->indices)[i+0]; - ids[1] = darray_size_t_cdata_get(&mesh->indices)[i+1]; - ids[2] = darray_size_t_cdata_get(&mesh->indices)[i+2]; + ids[0] = darray_uint32_cdata_get(&mesh->indices)[i+0]; + ids[1] = darray_uint32_cdata_get(&mesh->indices)[i+1]; + ids[2] = darray_uint32_cdata_get(&mesh->indices)[i+2]; return RES_OK; } @@ -407,7 +423,7 @@ scpr_mesh_clip int64_t lower[2], upper[2]; struct polygon* polygon = NULL; /* Use to triangulate clipped polygons */ struct darray_int64 coords; /* Coordinates of the clipped mesh */ - struct darray_size_t indices; /* Indices of the clipped mesh */ + struct darray_uint32 indices; /* Indices of the clipped mesh */ struct htable_vertex vertices; /* Map a coordinate to its index */ Clipper2Lib::Clipper64 clipper; Clipper2Lib::Paths64 output; /* Contour of the clipped polgyon */ @@ -427,7 +443,7 @@ scpr_mesh_clip clip_type = scpr_operation_to_clip_type(op); darray_int64_init(allocator, &coords); - darray_size_t_init(allocator, &indices); + darray_uint32_init(allocator, &indices); htable_vertex_init(allocator, &vertices); if(aabb_is_degenerated(poly_desc->lower, poly_desc->upper)) goto exit; @@ -491,12 +507,12 @@ scpr_mesh_clip } ERR(darray_int64_copy_and_clear(&mesh->coords, &coords)); - ERR(darray_size_t_copy_and_clear(&mesh->indices, &indices)); + ERR(darray_uint32_copy_and_clear(&mesh->indices, &indices)); exit: if(polygon) POLYGON(ref_put(polygon)); darray_int64_release(&coords); - darray_size_t_release(&indices); + darray_uint32_release(&indices); htable_vertex_release(&vertices); return res; error: diff --git a/src/scpr_polygon.c b/src/scpr_polygon.c @@ -19,9 +19,9 @@ #include <new> #include <polygon.h> #include <rsys/logger.h> +#include <rsys/ref_count.h> #include <rsys/mem_allocator.h> #include <rsys/rsys.h> -#include <rsys/double2.h> #undef PI #include <clipper2/clipper.h> @@ -101,7 +101,7 @@ one_path_is_eq sz = pp->size(); FOR_EACH(i, 0, sz) { if(matched[i]) continue; - if(path_is_eq(&pp->at(i), p)) { + if(path_is_eq(&(*pp)[i], p)) { matched[i] = 1; return 1; } @@ -137,7 +137,7 @@ scpr_polygon_create /* Allocate paths the C++ way (placement new) */ new (&polygon->paths) Clipper2Lib::PathsD; polygon->lower[0] = polygon->lower[1] = INT64_MAX; - polygon->upper[0] = polygon->upper[1] = -INT64_MAX; + polygon->upper[0] = polygon->upper[1] = INT64_MIN; exit: if(out_polygon) *out_polygon = polygon; @@ -172,7 +172,7 @@ scpr_polygon_ref_put res_T scpr_polygon_setup_indexed_vertices (struct scpr_polygon* polygon, - const size_t ncomponents, /* #connex components */ + const size_t ncomponents, void (*get_nverts)(const size_t icomponent, size_t *nverts, void* ctx), void (*get_position) (const size_t icomponent, const size_t ivert, double pos[2], void* ctx), @@ -189,6 +189,13 @@ scpr_polygon_setup_indexed_vertices goto error; } + if(ncomponents > UINT32_MAX) { + logger_print(polygon->device->logger, LOG_ERROR, + "Too many components.\n"); + res = RES_BAD_ARG; + goto error; + } + dev = polygon->device; TRY(paths.resize(ncomponents)); @@ -197,6 +204,12 @@ scpr_polygon_setup_indexed_vertices /* Get count for connex component c */ get_nverts(c, &nverts, data); + if(nverts > UINT32_MAX) { + logger_print(polygon->device->logger, LOG_ERROR, + "Too many vertices for component %zu.\n", c); + res = RES_BAD_ARG; + goto error; + } TRY(paths[c].resize(nverts)); /* Fetch polygon positions for connex component c */ @@ -216,8 +229,9 @@ scpr_polygon_setup_indexed_vertices /* Merge vertices, ... */ TRY(paths = Clipper2Lib::SimplifyPaths(paths, dev->inv_scale)); polygon->paths = std::move(paths); + paths.Clipper2Lib::Paths64::~Paths64(); changedc = (ncomponents != polygon->paths.size()); - for(c = 0; !changedv && c < paths.size(); c++) { + for(c = 0; !changedv && c < polygon->paths.size(); c++) { size_t nv; get_nverts(c, &nv, data); changedv |= (nv != polygon->paths[c].size()); @@ -230,7 +244,7 @@ scpr_polygon_setup_indexed_vertices /* Build bounding box */ polygon->lower[0] = polygon->lower[1] = INT64_MAX; - polygon->upper[0] = polygon->upper[1] = -INT64_MAX; + polygon->upper[0] = polygon->upper[1] = INT64_MIN; FOR_EACH(c, 0, ncomponents) { size_t i, nverts; get_nverts(c, &nverts, data); @@ -252,7 +266,7 @@ error: if(polygon) { polygon->paths.clear(); polygon->lower[0] = polygon->lower[1] = INT64_MAX; - polygon->upper[0] = polygon->upper[1] = -INT64_MAX; + polygon->upper[0] = polygon->upper[1] = INT64_MIN; } goto exit; } @@ -422,7 +436,7 @@ scpr_offset_polygon /* Rebuild AABB */ poly_desc->lower[0] = poly_desc->lower[1] = INT64_MAX; - poly_desc->upper[0] = poly_desc->upper[1] = -INT64_MAX; + poly_desc->upper[0] = poly_desc->upper[1] = INT64_MIN; FOR_EACH(c, 0, poly_desc->paths.size()) { size_t i, nverts; nverts = poly_desc->paths[c].size(); @@ -477,7 +491,7 @@ scpr_polygon_eq /* Check actual coordinates */ matched = (char*)calloc(sz, sizeof(*matched)); FOR_EACH(i, 0, sz) { - if(!one_path_is_eq(&polygon1->paths, &polygon2->paths.at(i), matched)) { + if(!one_path_is_eq(&polygon1->paths, &polygon2->paths[i], matched)) { *is_eq = 0; goto exit; } @@ -490,3 +504,73 @@ exit: error: goto exit; } + +res_T +scpr_polygon_dump_to_obj + (struct scpr_polygon* polygon, + FILE* stream) +{ + res_T res = RES_OK; + size_t i, j, n = 1; + + if(!polygon || !stream) { + res = RES_BAD_ARG; + goto error; + } + + /* Vertice */ + for(i = 0; i < polygon->paths.size(); i++) { + for(j = 0; j < polygon->paths[i].size(); j++) { + Clipper2Lib::Point64& pt = polygon->paths[i][j]; + fprintf(stream, "v %zu %zu 0\n", pt.x, pt.y); + } + } + + /* Lines */ + for(i = 0; i < polygon->paths.size(); i++) { + size_t fst = n; + fprintf(stream, "l "); + for(j = 0; j < polygon->paths[i].size(); j++) { + fprintf(stream, " %zu", n++); + } + fprintf(stream, " %zu\n", fst); + } + +exit: + return res; +error: + goto exit; +} + +res_T +scpr_polygon_dump_component_to_obj + (struct scpr_polygon* polygon, + const size_t icomponent, + FILE* stream) +{ + res_T res = RES_OK; + size_t i; + + if(!polygon || !stream || icomponent >= polygon->paths.size()) { + res = RES_BAD_ARG; + goto error; + } + + /* Vertice */ + for(i = 0; i < polygon->paths[icomponent].size(); i++) { + Clipper2Lib::Point64& pt = polygon->paths[icomponent][i]; + fprintf(stream, "v %zu %zu 0\n", pt.x, pt.y); + } + + /* Line */ + fprintf(stream, "l "); + for(i = 0; i < polygon->paths[icomponent].size(); i++) { + fprintf(stream, " %zu", ++i); + } + fprintf(stream, " 1\n"); + +exit: + return res; +error: + goto exit; +} diff --git a/src/test_scpr_intersector.c b/src/test_scpr_intersector.c @@ -0,0 +1,206 @@ +/* Copyright (C) 2016-2018, 2021 |Meso|Star> (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/>. */ + +#define _POSIX_C_SOURCE 200112L + +#include "scpr.h" +#include "test_scpr_utils.h" + +#include <rsys/rsys.h> + +#include <memory.h> + +struct cpt { + size_t simple, collinear; +}; + +static int +simple_intersection + (struct scpr_callback_segment* segment1, + struct scpr_callback_segment* segment2, + void* data) +{ + struct cpt* cpt = (struct cpt*)data; + (void)segment1; (void)segment2; + ASSERT(cpt); + cpt->simple++; + return 0; +} + +static int +collinear_segments + (struct scpr_callback_segment* segment1, + struct scpr_callback_segment* segment2, + void* data) +{ + struct cpt* cpt = (struct cpt*)data; + (void)segment1; (void)segment2; + ASSERT(cpt); + cpt->collinear++; + return 0; +} + +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct scpr_device* dev; + struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; + struct scpr_intersector* inter; + struct scpr_intersector_check_callbacks cb + = SCPR_INTERSECTOR_CHECK_CALLBACKS_NULL__ ; + struct polygon_context ctx; + struct scpr_polygon *p01, *p23, *p4; + size_t ncomps = 2; + double** coords; + /* 1,1 + * +-----------+----+ + * | / | / + * | / | / + * +-----------+-C2--+ + * | | | | + * | | | | + * | +-C0--+-+---+----+----+ + * | 0,0 |/ | | + * | +-C3-------+-C4-+ + * +-C1--------+ + * -0.5,-0.5 + */ + double coords0[] = { + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0 + }; + double coords1[] = { + -0.5, -0.5, + 0.5, -0.5, + 0.5, 0.5, + -0.5, 0.5 + }; + double coords2[] = { + 0.5, 0.5, + 1.0, 0.5, + 1.5, 1.0, + 1.0, 1.0 + }; + double coords3[] = { + 0.5, -0.3, + 1.5, -0.3, + 1.5, 0.0, + 0.7, 0.0 + }; + double coords4[] = { + 1.5, -0.3, + 2.0, -0.3, + 2.0, 0.0, + 1.5, 0 + }; + size_t nverts[] = { 4, 4 }; + struct cpt cpt = { 0, 0 }; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + args.allocator = &allocator; + args.verbosity_level = 3; + args.precision = 6; + OK(scpr_device_create(&args, &dev)); + + BAD(scpr_intersector_create(NULL, NULL)); + BAD(scpr_intersector_create(dev, NULL)); + BAD(scpr_intersector_create(NULL, &inter)); + OK(scpr_intersector_create(dev, &inter)); + + BAD(scpr_intersector_ref_get(NULL)); + OK(scpr_intersector_ref_get(inter)); + + BAD(scpr_intersector_ref_put(NULL)); + OK(scpr_intersector_ref_put(inter)); + + OK(scpr_polygon_create(dev, &p01)); + OK(scpr_polygon_create(dev, &p23)); + OK(scpr_polygon_create(dev, &p4)); + coords = (double**)MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); + coords[0] = (double*)MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords)); + coords[1] = (double*)MEM_CALLOC(&allocator, nverts[1], 2*sizeof(**coords)); + + ctx.coords = coords; + ctx.nverts = nverts; + ctx.ncomps = ncomps; + + #define SETUP scpr_polygon_setup_indexed_vertices + memcpy(coords[0], coords0, 2*nverts[0]*sizeof(**coords)); + memcpy(coords[1], coords1, 2*nverts[1]*sizeof(**coords)); + OK(SETUP(p01, ncomps, pget_nverts, pget_pos, &ctx)); + + memcpy(coords[0], coords2, 2*nverts[0]*sizeof(**coords)); + memcpy(coords[1], coords3, 2*nverts[1]*sizeof(**coords)); + OK(SETUP(p23, ncomps, pget_nverts, pget_pos, &ctx)); + + memcpy(coords[0], coords4, 2*nverts[0]*sizeof(**coords)); + OK(SETUP(p4, 1, pget_nverts, pget_pos, &ctx)); + + BAD(scpr_intersector_register_component(NULL, NULL, ncomps)); + BAD(scpr_intersector_register_component(NULL, NULL, 0)); + BAD(scpr_intersector_register_component(NULL, p01, ncomps)); + BAD(scpr_intersector_register_component(inter, NULL, ncomps)); + BAD(scpr_intersector_register_component(NULL, p01, 0)); + BAD(scpr_intersector_register_component(inter, NULL, 0)); + BAD(scpr_intersector_register_component(inter, p01, ncomps)); + OK(scpr_intersector_register_component(inter, p01, 0)); + OK(scpr_intersector_register_component(inter, p01, 1)); + OK(scpr_intersector_register_component(inter, p23, 0)); + OK(scpr_intersector_register_component(inter, p23, 1)); + BAD(scpr_intersector_register_component(inter, p23, 1)); /* Registered twice */ + + BAD(scpr_intersector_register_polygon(NULL, NULL)); + BAD(scpr_intersector_register_polygon(NULL, p01)); + BAD(scpr_intersector_register_polygon(inter, NULL)); + OK(scpr_intersector_register_polygon(inter, p4)); + BAD(scpr_intersector_register_polygon(inter, p4)); /* Registered twice */ + + BAD(scpr_intersector_check(NULL, NULL, NULL)); + BAD(scpr_intersector_check(NULL, &cb, NULL)); + BAD(scpr_intersector_check(inter, NULL, NULL)); + BAD(scpr_intersector_check(inter, &cb, NULL)); + BAD(scpr_intersector_check(inter, &cb, &cpt)); /* Callbacks are all NULL */ + /* Report only intersections */ + cb.simple_intersection = simple_intersection; + OK(scpr_intersector_check(inter, &cb, &cpt)); + CHK(cpt.simple == 2); + CHK(cpt.collinear == 0); + /* Report intersections and collinearity */ + cb.collinear_segments = collinear_segments; + cpt.simple = cpt.collinear = 0; + OK(scpr_intersector_check(inter, &cb, &cpt)); + CHK(cpt.simple == 2); + CHK(cpt.collinear == 2); + + OK(scpr_polygon_ref_put(p01)); + OK(scpr_polygon_ref_put(p23)); + OK(scpr_polygon_ref_put(p4)); + OK(scpr_intersector_ref_put(inter)); + OK(scpr_device_ref_put(dev)); + + MEM_RM(&allocator, coords[0]); + MEM_RM(&allocator, coords[1]); + MEM_RM(&allocator, coords); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +} + diff --git a/src/test_scpr_polygon.c b/src/test_scpr_polygon.c @@ -13,6 +13,7 @@ * 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 <stdio.h> #define _POSIX_C_SOURCE 200112L #include "scpr.h" @@ -59,6 +60,7 @@ main(int argc, char** argv) struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; int eq, in; double low[2] = {0, 0}, up[2] = {1, 1}; + FILE* stream = NULL; (void)argc, (void)argv; mem_init_proxy_allocator(&allocator, &mem_default_allocator); @@ -206,6 +208,25 @@ main(int argc, char** argv) BAD(scpr_offset_polygon(NULL, 0, SCPR_JOIN_MITER)); OK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER)); + BAD(scpr_polygon_dump_to_obj(NULL, NULL)); + BAD(scpr_polygon_dump_to_obj(polygon, NULL)); + BAD(scpr_polygon_dump_to_obj(NULL, stream)); + BAD(scpr_polygon_dump_to_obj(polygon, stream)); + stream = tmpfile(); + OK(scpr_polygon_dump_to_obj(polygon, stream)); + fclose(stream); + stream = NULL; + + BAD(scpr_polygon_dump_component_to_obj(NULL, ncomps, NULL)); + BAD(scpr_polygon_dump_component_to_obj(polygon, ncomps, NULL)); + BAD(scpr_polygon_dump_component_to_obj(NULL, 0, NULL)); + BAD(scpr_polygon_dump_component_to_obj(NULL, ncomps, stream)); + BAD(scpr_polygon_dump_component_to_obj(polygon, ncomps, stream)); + BAD(scpr_polygon_dump_component_to_obj(polygon, 0, stream)); + stream = tmpfile(); + OK(scpr_polygon_dump_component_to_obj(polygon, 0, stream)); + fclose(stream); + OK(scpr_device_ref_put(dev)); OK(scpr_polygon_ref_put(polygon)); OK(scpr_polygon_ref_put(copy));