star-cpr

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

commit 8b2755579987c610bc794eeff6eb74ba03048deb
parent aa367ce9d1e2cf578a66d1afd899b8b58ccd7702
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Mon, 20 Feb 2023 16:55:50 +0100

Add intersection detection on polygon components

At this stage only report segment intersections and colinear segments

Diffstat:
Mcmake/CMakeLists.txt | 2++
Msrc/scpr.h | 45+++++++++++++++++++++++++++++++++++++++++++++
Msrc/scpr_c.h | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scpr_intersector.c | 708+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_scpr_intersector.c | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 1124 insertions(+), 0 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -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,20 @@ struct scpr_device_create_args { static const struct scpr_device_create_args SCPR_DEVICE_CREATE_ARGS_DEFAULT = SCPR_DEVICE_CREATE_ARGS_DEFAULT__; +/* A struct holding callbacks that are called when carying an intersection check + * on a bunch of segments. */ +struct scpr_intersector_check_callbacks { + void(*simple_intersection) + (struct scpr_polygon* polygon1, size_t component1, size_t segment1, + struct scpr_polygon* polygon2, size_t component2, size_t segment2, + void* data); + void(*colinear_segments) + (struct scpr_polygon* polygon1, size_t component1, size_t segment1, + struct scpr_polygon* polygon2, size_t component2, size_t segment2, + void* data); +}; +#define SCPR_INTERSECTOR_CHECK_CALLBACKS_NULL__ { NULL, NULL } + BEGIN_DECLS /******************************************************************************* @@ -267,6 +282,36 @@ 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 component for further analysis */ +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); + END_DECLS #endif /* SCPR_H */ diff --git a/src/scpr_c.h b/src/scpr_c.h @@ -18,6 +18,7 @@ #include "scpr.h" +#include <cstddef> #include <rsys/rsys.h> #include <rsys/ref_count.h> #include <rsys/double2.h> @@ -90,4 +91,179 @@ struct scpr_mesh { struct scpr_device* device; }; +struct intersector_segment { + scpr_polygon* polygon; + size_t component, rank; +}; +#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 size_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 size_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 { + size_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 */ +struct intersector_component { + scpr_polygon* polygon; + size_t component; +}; +static FINLINE void +component_init(struct mem_allocator* alloc, struct intersector_component* key) +{ + ASSERT(alloc && key); + (void)alloc; + key->polygon = NULL; +} +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> + +/* 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 */ + struct htable_registered_components registererd_components; + ref_T ref; +}; + #endif diff --git a/src/scpr_intersector.c b/src/scpr_intersector.c @@ -0,0 +1,708 @@ +/* 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); + 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 size_t seg_idx) +{ + size_t* pidx; + size_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 = 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 size_t seg_idx1, + const size_t seg_idx2) +{ + ASSERT(pair); + pair->rank1 = seg_idx1; + pair->rank2 = seg_idx2; + if(pair->rank1 > pair->rank2) { + SWAP(ptrdiff_t, pair->rank1, pair->rank2); + } +} + +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; +} + +static int +segments_bbox_interacts_1 + (size_t seg_idx1, + size_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; + + segments = darray_segments_cdata_get(&intersector->segments); + seg1 = segments + seg_idx1; + seg2 = segments + seg_idx2; + + /* Get vertices */ + r11 = seg1->rank; + SCPR(polygon_get_vertices_count(seg1->polygon, seg1->component, &count)); + r12 = (r11 + 1) % count; + r21 = seg2->rank; + SCPR(polygon_get_vertices_count(seg2->polygon, seg2->component, &count)); + r22 = (r21 + 1) % count; + v11 = &seg1->polygon->paths[seg1->component][r11]; + v12 = &seg1->polygon->paths[seg1->component][r12]; + v21 = &seg2->polygon->paths[seg2->component][r21]; + v22 = &seg2->polygon->paths[seg2->component][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); +} + +/* Register interaction of 2 segments along 1 dimension */ +static res_T +register_segment_interaction + (const size_t seg_idx1, + const size_t seg_idx2, + const enum segment_interaction inter, + struct scpr_intersector* intersector) +{ + res_T res = RES_OK; + struct intersector_segment_pair pair; + unsigned char interact, *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) { /* First occurence of this pair: create empty record */ + pinteract = &interact; + interact = NO_INTERACTION; + } + /* Register this interaction */ + ASSERT((*pinteract | inter) < UCHAR_MAX); + *pinteract = (unsigned char)(*pinteract | inter); + ERR(htable_interacting_segments_set(&intersector->interacting_segments, + &pair, pinteract)); + } + +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 size_t seg_idx, + struct htable_segment_idx* open_segments, + 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)) { + size_t seg2_idx = *htable_segment_idx_iterator_key_get(&it); + ERR(register_segment_interaction(seg_idx, seg2_idx, 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; + size_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 = (size_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); + size_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], + dimension->overlap, intersector)); + } + else if(begins[j] ^ ends[j]) { + ERR(register_segment_interaction(seg_index[s], seg_index[j], + 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, + 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, + 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, + 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; +} + +/* 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 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, + 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; + + ASSERT(seg1 && seg2 && callbacks); + + /* Get vertices */ + r11 = seg1->rank; + ERR(scpr_polygon_get_vertices_count(seg1->polygon, seg1->component, &count)); + r12 = (r11 + 1) % count; + r21 = seg2->rank; + ERR(scpr_polygon_get_vertices_count(seg2->polygon, seg2->component, &count)); + r22 = (r21 + 1) % count; + v11 = &seg1->polygon->paths[seg1->component][r11]; + v12 = &seg1->polygon->paths[seg1->component][r12]; + v21 = &seg2->polygon->paths[seg2->component][r21]; + v22 = &seg2->polygon->paths[seg2->component][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) { + /* Colinear segments */ + callbacks->colinear_segments(seg1->polygon, seg1->component, seg1->rank, + seg2->polygon, seg2->component, seg2->rank, data); + } + else if(ccw1 * ccw2 < 0 && ccw3 * ccw4 < 0) { + /* Basic segment intersection */ + callbacks->simple_intersection(seg1->polygon, seg1->component, seg1->rank, + seg2->polygon, seg2->component, seg2->rank, data); + } + +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, 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); + +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_component + (struct scpr_intersector* intersector, + struct scpr_polygon* polygon, + const size_t icomponent) +{ + res_T res = RES_OK; + size_t prev_count, new_count, i; + 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 = icomponent; + found = htable_registered_components_find(&intersector->registererd_components, + &comp); + if(found) { + logger_print(intersector->device->logger, LOG_ERROR, + "Component already registered.\n"); + res = RES_BAD_ARG; + goto error; + } + htable_registered_components_set(&intersector->registererd_components, &comp, + &one); + + prev_count = darray_segments_size_get(&intersector->segments); + new_count = polygon->paths[icomponent].size(); + ERR(darray_segments_resize(&intersector->segments, prev_count + new_count)); + + for(i = 0; i < new_count; i++) { + Clipper2Lib::Point64 *p0, *p1; + size_t seg_idx = prev_count + i; + struct intersector_segment* segment + = darray_segments_data_get(&intersector->segments) + seg_idx; + segment->polygon = polygon; + segment->component = icomponent; + segment->rank = 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->colinear_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/test_scpr_intersector.c b/src/test_scpr_intersector.c @@ -0,0 +1,193 @@ +/* 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 basic, colinear; +}; + +static void +basic_intersection + (struct scpr_polygon* polygon1, size_t component1, size_t segment1, + struct scpr_polygon* polygon2, size_t component2, size_t segment2, + void* data) +{ + struct cpt* cpt = (struct cpt*)data; + (void)polygon1; (void)component1; (void)segment1; + (void)polygon2; (void)component2; (void)segment2; + ASSERT(cpt); + cpt->basic++; +} + +static void +colinear_segments + (struct scpr_polygon* polygon1, size_t component1, size_t segment1, + struct scpr_polygon* polygon2, size_t component2, size_t segment2, + void* data) +{ + struct cpt* cpt = (struct cpt*)data; + (void)polygon1; (void)component1; (void)segment1; + (void)polygon2; (void)component2; (void)segment2; + ASSERT(cpt); + cpt->colinear++; +} + +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)); + OK(scpr_intersector_register_component(inter, p4, 0)); + + 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)); + cb.simple_intersection = basic_intersection; + cb.colinear_segments = colinear_segments; + OK(scpr_intersector_check(inter, &cb, &cpt)); + CHK(cpt.basic == 2); + CHK(cpt.colinear == 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; +} +