star-cpr

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

commit 531327dc13e560add3e1e9e9d5e791d70dbca8d5
parent e71faac430c1b5244ebfb5b4dd2910a7d723da12
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Mon, 23 Jan 2023 16:44:10 +0100

Merge branch 'release_0.2'

Diffstat:
MREADME.md | 10+++++++++-
Mcmake/CMakeLists.txt | 30+++++++++++++++++-------------
Mcmake/ClipperConfig.cmake | 48++++++++++++++++++++++++------------------------
Msrc/scpr.h | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Asrc/scpr_c.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/scpr_mesh.c | 244+++++++++++++++++--------------------------------------------------------------
Asrc/scpr_polygon.c | 436+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_scpr_clip.c | 156+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/test_scpr_mesh.c | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Asrc/test_scpr_offset.c | 396+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_scpr_polygon.c | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_scpr_utils.h | 37+++++++++++++++++++++++++++++++++----
12 files changed, 1424 insertions(+), 323 deletions(-)

diff --git a/README.md b/README.md @@ -22,6 +22,14 @@ project from the `cmake/CMakeLists.txt` file by appending to the ## Release notes +### Version 0.2 + +- Switch Clipper library to Clipper2. This increases requirements on compiler to + C++17. +- Allow complex polygons (polygons with multiple components, defining holes). +- Add a function to offset polygons. +- Make constraints on coordinates explicit: limited range and limited precision. + ### Version 0.1.3 - Sets the CMake minimum version to 3.1: since CMake 3.20, version 2.8 has @@ -40,7 +48,7 @@ Update the version of the RSys dependency to 0.6: replace the deprecated ## License -Copyright (C) 2016-2018, 2021 |Meso|Star> (<contact@meso-star.com>). +Copyright (C) 2016-2018, 2021 |Meso|Star> (<contact@meso-star.com>). Star-CliPpeR is free software released under the GPL v3+ license: GNU GPL version 3 or later. You are welcome to redistribute it under certain conditions; refer to the COPYING file for details. diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -17,7 +17,7 @@ cmake_minimum_required(VERSION 3.1) project(scpr C CXX) enable_testing() -option(NO_TEST "Do not compile the test pograms" OFF) +option(NO_TEST "Do not compile the test programs" OFF) set(SCPR_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src) @@ -25,15 +25,15 @@ set(SCPR_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src) # Dependencies ################################################################################ get_filename_component(_current_source_dir ${CMAKE_CURRENT_LIST_FILE} PATH) -set(Clipper_DIR ${_current_source_dir}/) +set(Clipper2_DIR ${_current_source_dir}/) -find_package(Clipper REQUIRED) find_package(RCMake REQUIRED) find_package(RSys 0.6 REQUIRED) +find_package(Clipper2 1 REQUIRED) find_package(Polygon 0.0.5 REQUIRED) include_directories( - ${Clipper_INCLUDE_DIR} + ${Clipper2_INCLUDE_DIR} ${Polygon_INCLUDE_DIR} ${RSys_INCLUDE_DIR}) @@ -41,33 +41,35 @@ set(CMAKE_MODULE_PATH ${RCMAKE_SOURCE_DIR}) include(rcmake) include(rcmake_runtime) -rcmake_append_runtime_dirs(_runtime_dirs RSys Polygon) +rcmake_append_runtime_dirs(_runtime_dirs RSys Polygon Clipper2) ################################################################################ # Define targets ################################################################################ set(VERSION_MAJOR 0) -set(VERSION_MINOR 1) -set(VERSION_PATCH 3) +set(VERSION_MINOR 2) +set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) -set(SCPR_FILES_SRC scpr_mesh.c) -set(SCPR_FILES_INC scpr.h) +set(SCPR_FILES_SRC scpr_mesh.c scpr_polygon.c) +set(SCPR_FILES_INC_API scpr.h) +set(SCPR_FILES_INC scpr_c.h) set(SCPR_FILES_DOC COPYING README.md) rcmake_prepend_path(SCPR_FILES_SRC ${SCPR_SOURCE_DIR}) rcmake_prepend_path(SCPR_FILES_INC ${SCPR_SOURCE_DIR}) +rcmake_prepend_path(SCPR_FILES_INC_API ${SCPR_SOURCE_DIR}) rcmake_prepend_path(SCPR_FILES_DOC ${PROJECT_SOURCE_DIR}/../) set_source_files_properties(${SCPR_FILES_SRC} PROPERTIES LANGUAGE CXX) -add_library(scpr SHARED ${SCPR_FILES_SRC} ${SCPR_FILES_INC}) +add_library(scpr SHARED ${SCPR_FILES_SRC} ${SCPR_FILES_INC} ${SCPR_FILES_INC_API}) set_target_properties(scpr PROPERTIES DEFINE_SYMBOL SCPR_SHARED_BUILD VERSION ${VERSION} SOVERSION ${VERSION_MAJOR}) -target_link_libraries(scpr RSys Polygon Clipper) +target_link_libraries(scpr RSys Polygon Clipper2) if(CMAKE_COMPILER_IS_GNUCXX) - set_target_properties(scpr PROPERTIES COMPILE_FLAGS "-Wno-long-long") + set_target_properties(scpr PROPERTIES COMPILE_FLAGS "-Wno-long-long -std=c++17") endif() rcmake_setup_devel(scpr StarCPR ${VERSION} star/scpr_version.h) @@ -87,7 +89,9 @@ if(NOT NO_TEST) add_test(${_name} ${_name}) endfunction() new_test(test_scpr_clip) + new_test(test_scpr_offset) new_test(test_scpr_mesh) + new_test(test_scpr_polygon) rcmake_copy_runtime_libraries(test_scpr_clip) endif() @@ -99,6 +103,6 @@ install(TARGETS scpr ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) -install(FILES ${SCPR_FILES_INC} DESTINATION include/star/) +install(FILES ${SCPR_FILES_INC_API} DESTINATION include/star/) install(FILES ${SCPR_FILES_DOC} DESTINATION share/doc/star-cpr/) diff --git a/cmake/ClipperConfig.cmake b/cmake/ClipperConfig.cmake @@ -31,21 +31,21 @@ cmake_minimum_required(VERSION 3.1) -# Try to find the Clipper devel. Once done this will define: -# - Clipper_FOUND: system has Clipper -# - Clipper_INCLUDE_DIR: the include directory -# - Clipper: Link this to use Clipper +# Try to find the Clipper2 devel. Once done this will define: +# - Clipper2_FOUND: system has Clipper2 +# - Clipper2_INCLUDE_DIR: the include directory +# - Clipper2: Link this to use Clipper2 -find_path(Clipper_INCLUDE_DIR polyclipping/clipper.hpp) -set(Clipper_INCLUDE_DIR ${Clipper_INCLUDE_DIR}/polyclipping/) +find_path(Clipper2_INCLUDE_DIR polyclipping/clipper.hpp) +set(Clipper2_INCLUDE_DIR ${Clipper2_INCLUDE_DIR}/polyclipping/) -unset(Clipper_LIBRARY CACHE) -unset(Clipper_LIBRARY_DEBUG CACHE) -unset(Clipper_LIBRARY_RELWITHDEBINFO CACHE) -unset(Clipper_LIBRARY_MINSIZEREL CACHE) -find_library(Clipper_LIBRARY polyclipping +unset(Clipper2_LIBRARY CACHE) +unset(Clipper2_LIBRARY_DEBUG CACHE) +unset(Clipper2_LIBRARY_RELWITHDEBINFO CACHE) +unset(Clipper2_LIBRARY_MINSIZEREL CACHE) +find_library(Clipper2_LIBRARY polyclipping DOC "Path to the clipper library used during release builds.") -find_library(Clipper_LIBRARY_DEBUG polyclipping-dbg +find_library(Clipper2_LIBRARY_DEBUG polyclipping-dbg DOC "Path to the clipper library used during debug builds.") # Create the imported library target @@ -53,22 +53,22 @@ if(CMAKE_HOST_WIN32) set(_property IMPORTED_IMPLIB) else(CMAKE_HOST_WIN32) set(_property IMPORTED_LOCATION) - if(NOT Clipper_LIBRARY_DEBUG) # Fallback lib - get_property(_doc CACHE Clipper_LIBRARY_DEBUG PROPERTY HELPSTRING) - set(Clipper_LIBRARY_DEBUG ${Clipper_LIBRARY} CACHE PATH ${_doc} FORCE) + if(NOT Clipper2_LIBRARY_DEBUG) # Fallback lib + get_property(_doc CACHE Clipper2_LIBRARY_DEBUG PROPERTY HELPSTRING) + set(Clipper2_LIBRARY_DEBUG ${Clipper2_LIBRARY} CACHE PATH ${_doc} FORCE) endif() endif() -add_library(Clipper SHARED IMPORTED) -set_target_properties(Clipper PROPERTIES - ${_property} ${Clipper_LIBRARY_DEBUG} - ${_property}_DEBUG ${Clipper_LIBRARY_DEBUG} - ${_property}_RELEASE ${Clipper_LIBRARY}) +add_library(Clipper2 SHARED IMPORTED) +set_target_properties(Clipper2 PROPERTIES + ${_property} ${Clipper2_LIBRARY_DEBUG} + ${_property}_DEBUG ${Clipper2_LIBRARY_DEBUG} + ${_property}_RELEASE ${Clipper2_LIBRARY}) # Check the package include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Clipper DEFAULT_MSG - Clipper_INCLUDE_DIR - Clipper_LIBRARY - Clipper_LIBRARY_DEBUG) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Clipper2 DEFAULT_MSG + Clipper2_INCLUDE_DIR + Clipper2_LIBRARY + Clipper2_LIBRARY_DEBUG) diff --git a/src/scpr.h b/src/scpr.h @@ -41,24 +41,93 @@ enum scpr_operation { SCPR_OPERATIONS_COUNT__ }; -/* Public polygon data type. Define the list of the countour vertices. */ -struct scpr_polygon { - void (*get_position)(const size_t ivert, double pos[2], void* ctx); - size_t nvertices; /* #vertices */ - /* User data provided as the last argument of the get_position functor */ - void* context; +enum scpr_join_type { + SCPR_JOIN_SQUARE, + SCPR_JOIN_ROUND, + SCPR_JOIN_MITER, + SCPR_JOIN_TYPES_COUNT__ }; -#define SCPR_POLYGON_NULL__ { NULL, 0, NULL } -static const struct scpr_polygon SCPR_POLYGON_NULL = SCPR_POLYGON_NULL__; /* Forward declaration */ struct mem_allocator; +/* Opaque 2D closed polygon data type */ +struct scpr_polygon; /* Opaque 2D mesh data type */ struct scpr_mesh; BEGIN_DECLS +/* Polygons can be made of any number of paths. + * Polygons inside/outside regions are defined by their winding numbers + * considering the Even-Odd convention. + * E.g. a CCW path inside a CW one defines a hole. */ +SCPR_API res_T +scpr_polygon_create + (struct mem_allocator* allocator, /* May be NULL <=> Use default allocator */ + struct scpr_polygon** polygon); + +SCPR_API res_T +scpr_polygon_create_copy + (struct mem_allocator* allocator, /* May be NULL <=> Use default allocator */ + const struct scpr_polygon* src_polygon, + struct scpr_polygon** polygon); + +SCPR_API res_T +scpr_polygon_ref_get + (struct scpr_polygon* polygon); + +SCPR_API res_T +scpr_polygon_ref_put + (struct scpr_polygon* polygon); + +/* To ensure constant precision, vertice coordinates are truncated, and have a + * limited range. Range checking along with the associated truncation process + * occur at vertices setup. + * The range of the coordinates is [-INT64_MAX*10E-6 +INT64_MAX*10E-6], that is + * [-9223372036854.775807 +9223372036854.775807] or approximately + * [-9.2E12 +9.2E12], and vertex coordinates are truncated past the 6th decimal + * place. It is an error to use out-of-range values. + * Truncated coordinates can be retrieved using the appropriate getters. */ +SCPR_API res_T +scpr_polygon_setup_indexed_vertices + (struct scpr_polygon* polygon, + const size_t ncomponents, /* #connex components */ + 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), + void* data); /* Client data set as the last param of the callbacks */ + +SCPR_API res_T +scpr_offset_polygon + (struct scpr_polygon* polygon, + const double offset, /* Can be either positive or negative */ + const enum scpr_join_type join_type); + +SCPR_API res_T +scpr_polygon_get_components_count + (const struct scpr_polygon* polygon, + size_t* ncomps); + +SCPR_API res_T +scpr_polygon_get_vertices_count + (const struct scpr_polygon* polygon, + const size_t icomponent, + size_t* nverts); + +SCPR_API res_T +scpr_polygon_get_position + (const struct scpr_polygon* polygon, + const size_t icomponent, + const size_t ivert, + double position[2]); + +SCPR_API res_T +scpr_polygon_eq + (const struct scpr_polygon* polygon1, + const struct scpr_polygon* polygon2, + int* is_eq); + SCPR_API res_T scpr_mesh_create (struct mem_allocator* allocator, /* May be NULL <=> Use default allocator */ diff --git a/src/scpr_c.h b/src/scpr_c.h @@ -0,0 +1,63 @@ +/* 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/>. */ + +#ifndef SCPR_C_H__ +#define SCPR_C_H__ + +#include "scpr.h" + +#include <rsys/rsys.h> +#include <rsys/ref_count.h> +#include <rsys/double2.h> +#include <rsys/dynamic_array_double.h> +#include <rsys/dynamic_array_size_t.h> +#include <rsys/hash_table.h> + +#undef PI +#include <clipper2/clipper.h> + +#define ERR(Expr) { if((res = (Expr)) != RES_OK) goto error; } (void)0 + +struct mem_allocator; + +struct vertex { double pos[2]; }; +static FINLINE int +vertex_eq(const struct vertex* a, const struct vertex* b) +{ return d2_eq(a->pos, b->pos); } + +/* Define the vertex to index hash table */ +#define HTABLE_NAME vertex +#define HTABLE_DATA size_t +#define HTABLE_KEY struct vertex +#define HTABLE_KEY_FUNCTOR_EQ vertex_eq +#include <rsys/hash_table.h> + +struct scpr_polygon { + Clipper2Lib::PathsD paths; + double lower[2], upper[2]; /* Polygon AABB */ + + ref_T ref; + struct mem_allocator* allocator; +}; + +struct scpr_mesh { + struct darray_double coords; + struct darray_size_t indices; + + ref_T ref; + struct mem_allocator* allocator; +}; + +#endif diff --git a/src/scpr_mesh.c b/src/scpr_mesh.c @@ -14,88 +14,24 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "scpr.h" +#include "scpr_c.h" -#include <rsys/double2.h> -#include <rsys/dynamic_array_double.h> -#include <rsys/dynamic_array_size_t.h> -#include <rsys/hash_table.h> #include <rsys/mem_allocator.h> -#include <rsys/ref_count.h> #include <polygon.h> -#include <clipper.hpp> -STATIC_ASSERT(sizeof(ClipperLib::cInt) >= sizeof(double), Unexpected_Type_Size); - -struct vertex { double pos[2]; }; -static FINLINE int -vertex_eq(const struct vertex* a, const struct vertex* b) -{ return d2_eq(a->pos, b->pos); } - -/* Define the vertex to index hash table */ -#define HTABLE_NAME vertex -#define HTABLE_DATA size_t -#define HTABLE_KEY struct vertex -#define HTABLE_KEY_FUNCTOR_EQ vertex_eq -#include <rsys/hash_table.h> - -struct poly { - struct darray_double coords; - double lower[2], upper[2]; /* Polygon AABB */ -}; - -struct scpr_mesh { - struct darray_double coords; - struct darray_size_t indices; - - ref_T ref; - struct mem_allocator* allocator; -}; +#undef PI +#include <clipper2/clipper.h> /******************************************************************************* * Helper functions ******************************************************************************/ -static FINLINE ClipperLib::cInt -double_to_cInt(const double d, const double scale) -{ - double dbl = d; - union { int64_t i; double d; } ucast; - ClipperLib::cInt i; - ASSERT(scale > 0); - - /* Map 'd' in [1, 2] => Fix the exponent */ - ucast.d = 1 + fabs(d / scale); - - /* Store the positive or null exponent in the 52^th bit. This ensure that if - * 'd' is equal to 2 then it is not encoded as 0. */ - i = (((ucast.i >> 52) & 0x7FF) - 1023) << 52; - - /* Store the mantissa in the [0 .. 51] bits */ - i = (ucast.i & 0x000FFFFFFFFFFFFF) | i; - - /* Apply the sign to the resulting integer */ - return dbl < 0 ? -i : i; -} - -static FINLINE double -cInt_to_double(const ClipperLib::cInt i, const double scale) -{ - double dbl; - union { int64_t i; double d; } ucast; - ASSERT(scale > 0); - - ucast.i = llabs(i); - ucast.i = ((1023 + (ucast.i >> 52)) << 52) | (ucast.i & 0x000FFFFFFFFFFFFF); - dbl = (ucast.d - 1) * scale; - return i < 0 ? -dbl : dbl; -} - -static FINLINE ClipperLib::ClipType +static FINLINE Clipper2Lib::ClipType scpr_operation_to_clip_type(const enum scpr_operation op) { - ClipperLib::ClipType ctype = ClipperLib::ctIntersection; + Clipper2Lib::ClipType ctype; switch(op) { - case SCPR_AND: ctype = ClipperLib::ctIntersection; break; - case SCPR_SUB: ctype = ClipperLib::ctDifference; break; + case SCPR_AND: ctype = Clipper2Lib::ClipType::Intersection; break; + case SCPR_SUB: ctype = Clipper2Lib::ClipType::Difference; break; default: FATAL("Unreachable code\n"); break; } return ctype; @@ -122,53 +58,6 @@ aabb_is_degenerated(const double lower[2], const double upper[2]) return lower[0] >= upper[0] || lower[1] >= upper[1]; } -static INLINE void -poly_init(struct mem_allocator* allocator, struct poly* poly) -{ - ASSERT(allocator && poly); - darray_double_init(allocator, &poly->coords); -} - -static INLINE void -poly_release(struct poly* poly) -{ - ASSERT(poly); - darray_double_release(&poly->coords); -} - -static res_T -poly_setup(struct poly* poly, const struct scpr_polygon* desc) -{ - size_t ivert; - res_T res = RES_OK; - ASSERT(poly && desc); - - if(!desc->get_position && !desc->nvertices) { - res = RES_BAD_ARG; - goto error; - } - - res = darray_double_resize(&poly->coords, desc->nvertices * 2/*#coords*/); - if(res != RES_OK) goto error; - - d2_splat(poly->lower, DBL_MAX); - d2_splat(poly->upper,-DBL_MAX); - FOR_EACH(ivert, 0, desc->nvertices) { - double* pos = darray_double_data_get(&poly->coords) + ivert*2; - desc->get_position(ivert, pos, desc->context); - d2_min(poly->lower, poly->lower, pos); - d2_max(poly->upper, poly->upper, pos); - } - -exit: - return res; -error: - darray_double_clear(&poly->coords); - d2_splat(poly->lower, DBL_MAX); - d2_splat(poly->upper,-DBL_MAX); - goto exit; -} - static void triangle_compute_aabb (double tri[3][2], @@ -211,17 +100,14 @@ register_vertex } else { const size_t ivert = darray_double_size_get(coords); - res = darray_double_resize(coords, ivert+2/*#coords*/); - if(res != RES_OK) goto error; + ERR(darray_double_resize(coords, ivert+2/*#coords*/)); d2_set(darray_double_data_get(coords)+ivert, pos); id = ivert / 2; - res = htable_vertex_set(vertices, &v, &id); - if(res != RES_OK) goto error; + ERR(htable_vertex_set(vertices, &v, &id)); } - res = darray_size_t_push_back(indices, &id); - if(res != RES_OK) goto error; + ERR(darray_size_t_push_back(indices, &id)); exit: return res; @@ -240,20 +126,21 @@ register_triangle res_T res = RES_OK; ASSERT(tri && coords && indices && vertices); FOR_EACH(ivert, 0, 3) { - res = register_vertex(tri[ivert], coords, indices, vertices); - if(res != RES_OK) return res; + ERR(register_vertex(tri[ivert], coords, indices, vertices)); } +exit: return RES_OK; +error: + goto exit; } static res_T register_paths - (const ClipperLib::Paths& paths, + (const Clipper2Lib::PathsD& paths, struct darray_double* coords, /* Vertex buffer */ struct darray_size_t* indices, /* Index buffer */ struct htable_vertex* vertices, /* Map a vertex to its index */ - const double extend[2], /* Scale to apply to the cInt coordinates */ struct polygon* polygon) /* Use to triangulate the clipped polygons */ { size_t ivert; @@ -265,10 +152,9 @@ register_paths if(paths[ipath].size() == 3) { FOR_EACH(ivert, 0, 3) { double pos[2]; - pos[0] = cInt_to_double(paths[ipath][ivert].X, extend[0]); - pos[1] = cInt_to_double(paths[ipath][ivert].Y, extend[1]); - res = register_vertex(pos, coords, indices, vertices); - if(res != RES_OK) goto error; + pos[0] = paths[ipath][ivert].x; + pos[1] = paths[ipath][ivert].y; + ERR(register_vertex(pos, coords, indices, vertices)); } } else { /* Triangulate the clipped primitive */ @@ -281,15 +167,13 @@ register_paths float fpos[3] = {0.f, 0.f, 0.f}; double pos[2]; - pos[0] = cInt_to_double(paths[ipath][ivert].X, extend[0]); - pos[1] = cInt_to_double(paths[ipath][ivert].Y, extend[1]); + pos[0] = paths[ipath][ivert].x; + pos[1] = paths[ipath][ivert].y; fpos[0] = (float)pos[0], fpos[1] = (float)pos[1]; - res = polygon_vertex_add(polygon, fpos); - if(res != RES_OK) goto error; + ERR(polygon_vertex_add(polygon, fpos)); } - res = polygon_triangulate(polygon, &ids, &nids); - if(res != RES_OK) goto error; + ERR(polygon_triangulate(polygon, &ids, &nids)); FOR_EACH(ivert, 0, nids) { float fpos[3]; @@ -297,8 +181,7 @@ register_paths POLYGON(vertex_get(polygon, ids[ivert], fpos)); pos[0] = (float)fpos[0]; pos[1] = (float)fpos[1]; - res = register_vertex(pos, coords, indices, vertices); - if(res != RES_OK) goto error; + ERR(register_vertex(pos, coords, indices, vertices)); } } } @@ -409,15 +292,13 @@ scpr_mesh_setup_indexed_vertices size_t i; res_T res = RES_OK; - if(!mesh || !ntris || !get_indices || !nverts || !get_position) { + if(!mesh || !ntris || !get_indices || !nverts || !get_position || !data) { res = RES_BAD_ARG; goto error; } - res = darray_double_resize(&mesh->coords, nverts*2/*#coords per vertex*/); - if(res != RES_OK) goto error; - res = darray_size_t_resize(&mesh->indices, ntris*3/*#vertices per triangle*/); - if(res != RES_OK) goto error; + ERR(darray_double_resize(&mesh->coords, nverts*2/*#coords per vertex*/)); + ERR(darray_size_t_resize(&mesh->indices, ntris*3/*#vertices per triangle*/)); /* Fetch mesh positions */ pos = darray_double_data_get(&mesh->coords); @@ -500,19 +381,17 @@ scpr_mesh_clip const enum scpr_operation op, struct scpr_polygon* poly_desc) { - double lower[2], upper[2], extend[2]; - struct poly poly; + double lower[2], upper[2]; struct polygon* polygon = NULL; /* Use to triangulate clipped polygons */ struct darray_double coords; /* Coordinates of the clipped mesh */ struct darray_size_t indices; /* Indices of the clipped mesh */ struct htable_vertex vertices; /* Map a coordinate to its index */ - ClipperLib::Clipper clipper(ClipperLib::ioStrictlySimple); - ClipperLib::Paths output; /* Contour of the clipped polgyon */ - ClipperLib::Path cand_path; /* Contour of the candidate polygon */ - ClipperLib::Path clip_path; /* Contour of the clip polygon */ - ClipperLib::ClipType clip_type; /* Type of clipping to perform */ - size_t ivert, nverts; - size_t itri, ntris; + Clipper2Lib::ClipperD clipper; + Clipper2Lib::PathsD output; /* Contour of the clipped polgyon */ + Clipper2Lib::PathsD cand_path; /* Contour of the candidate polygon */ + Clipper2Lib::PathD tmp; + Clipper2Lib::ClipType clip_type; /* Type of clipping to perform */ + size_t itri, ntris, ivert; res_T res = RES_OK; @@ -525,32 +404,17 @@ scpr_mesh_clip darray_size_t_init(mesh->allocator, &indices); htable_vertex_init(mesh->allocator, &vertices); - poly_init(mesh->allocator, &poly); - res = poly_setup(&poly, poly_desc); - if(res != RES_OK) goto error; - if(aabb_is_degenerated(poly.lower, poly.upper)) goto exit; + if(aabb_is_degenerated(poly_desc->lower, poly_desc->upper)) goto exit; mesh_compute_aabb(mesh, lower, upper); if(aabb_is_degenerated(lower, upper)) goto exit; /* Compute the overall aabb of the candidate and the clip polygon */ - d2_min(lower, lower, poly.lower); - d2_max(upper, upper, poly.upper); - d2_sub(extend, upper, lower); - - /* Setup the clip path */ - nverts = darray_double_size_get(&poly.coords) / 2/*#coords per vertex*/; - FOR_EACH(ivert, 0, nverts) { - const double* v = darray_double_cdata_get(&poly.coords) + ivert*2; - ClipperLib::IntPoint pt; - pt.X = double_to_cInt(v[0], extend[0]); - pt.Y = double_to_cInt(v[1], extend[1]); - clip_path.push_back(pt); - } + d2_min(lower, lower, poly_desc->lower); + d2_max(upper, upper, poly_desc->upper); /* Create the polygon structure used to triangulate the clipped polygons */ - res = polygon_create(mesh->allocator, &polygon); - if(res != RES_OK) goto error; + ERR(polygon_create(mesh->allocator, &polygon)); /* Clip the triangles of the mesh */ SCPR(mesh_get_triangles_count(mesh, &ntris)); @@ -565,47 +429,43 @@ scpr_mesh_clip SCPR(mesh_get_position(mesh, ids[2], tri[2])); triangle_compute_aabb(tri, lower, upper); - /* Do not clip triangles that don not intersect the clip AABB */ - if(!aabb_intersect(lower, upper, poly.lower, poly.upper)) { + /* Do not clip triangles that do not intersect the clip AABB */ + if(!aabb_intersect(lower, upper, poly_desc->lower, poly_desc->upper)) { if(op != SCPR_AND) { - res = register_triangle(tri, &coords, &indices, &vertices); - if(res != RES_OK) goto error; + ERR(register_triangle(tri, &coords, &indices, &vertices)); } continue; } /* Setup the candidate path */ cand_path.clear(); + tmp.clear(); FOR_EACH(ivert, 0, 3) { - ClipperLib::IntPoint pt; - pt.X = double_to_cInt(tri[ivert][0], extend[0]); - pt.Y = double_to_cInt(tri[ivert][1], extend[1]); - cand_path.push_back(pt); + Clipper2Lib::PointD pt; + pt.x = tri[ivert][0]; + pt.y = tri[ivert][1]; + tmp.push_back(pt); } + cand_path.push_back(tmp); /* Clip the polygon */ clipper.Clear(); - clipper.AddPath(cand_path, ClipperLib::ptSubject, 1); - clipper.AddPath(clip_path, ClipperLib::ptClip, 1); - clipper.Execute(clip_type, output); + clipper.AddSubject(cand_path); + clipper.AddClip(poly_desc->paths); + clipper.Execute(clip_type, Clipper2Lib::FillRule::EvenOdd, output); /* Register the resulting clipped polygons */ - res = register_paths - (output, &coords, &indices, &vertices, extend, polygon); - if(res != RES_OK) goto error; + ERR(register_paths (output, &coords, &indices, &vertices, polygon)); } - res = darray_double_copy_and_clear(&mesh->coords, &coords); - if(res != RES_OK) FATAL("Unexpected error.\n"); - res = darray_size_t_copy_and_clear(&mesh->indices, &indices); - if(res != RES_OK) FATAL("Unexpected error.\n"); + ERR(darray_double_copy_and_clear(&mesh->coords, &coords)); + ERR(darray_size_t_copy_and_clear(&mesh->indices, &indices)); exit: if(polygon) POLYGON(ref_put(polygon)); darray_double_release(&coords); darray_size_t_release(&indices); htable_vertex_release(&vertices); - poly_release(&poly); return res; error: goto exit; diff --git a/src/scpr_polygon.c b/src/scpr_polygon.c @@ -0,0 +1,436 @@ +/* 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 "clipper2/clipper.core.h" +#include "scpr.h" +#include "scpr_c.h" + +#include <new> +#include <polygon.h> +#include <rsys/mem_allocator.h> +#include <rsys/rsys.h> +#include <rsys/double2.h> + +#undef PI +#include <clipper2/clipper.h> + +#include <math.h> + +/* Sets the precision parameter, as expected by Clipper2. + * Allowed range is [-8 +8]. + * This parameter defines both the floating point precision for the coordinates + * and the range of the coordinates. + * With a precision of 0, coordinates are truncated to the nearest integer and + * the coordinate range is [-INT64_MAX +INT64_MAX] (that is + * [-9223372036854775807 +9223372036854775807] or ~ [-9.2E18 + 9.2E18]). + * Increasing precision by 1 adds 1 more decimal place to the coordinate + * precision and divides the coordinate range by 10. */ +#define PRECISION 6 + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static FINLINE Clipper2Lib::JoinType +scpr_join_type_to_clipper_join_type(const enum scpr_join_type t) +{ + Clipper2Lib::JoinType jtype; + switch(t) { + case SCPR_JOIN_SQUARE: jtype = Clipper2Lib::JoinType::Square; break; + case SCPR_JOIN_ROUND: jtype = Clipper2Lib::JoinType::Round; break; + case SCPR_JOIN_MITER: jtype = Clipper2Lib::JoinType::Miter; break; + default: FATAL("Unreachable code\n"); break; + } + return jtype; +} + +static void +polygon_release(ref_T* ref) +{ + struct scpr_polygon* polygon; + ASSERT(ref); + polygon = CONTAINER_OF(ref, struct scpr_polygon, ref); + /* Call destructor for paths */ + polygon->paths.Clipper2Lib::PathsD::~PathsD(); + MEM_RM(polygon->allocator, polygon); +} + +static int +path_is_eq + (const Clipper2Lib::PathD* p1, + const Clipper2Lib::PathD* p2) +{ + size_t i, first_vtx, sz; + int opposite_cw; + ASSERT(p1 && p2); + sz = p1->size(); + if(sz != p2->size()) { + return 0; + } + FOR_EACH(opposite_cw, 0, 2) { + FOR_EACH(first_vtx, 0, sz) { + int eq = 1; + FOR_EACH(i, 0, sz) { + size_t n; + if(opposite_cw) n = sz - 1 - (i + first_vtx) % sz; + else n = (i + first_vtx) % sz; + if((*p1)[i] != (*p2)[n]) { + eq = 0; + break; /* This opposite_cw/fstv failed: try next */ + } + } + /* Could prove p1 == p2 */ + if(eq) return 1; + } + } + return 0; +} + +static int +one_path_is_eq + (const Clipper2Lib::PathsD* pp, + const Clipper2Lib::PathD* p, + char* matched) +{ + size_t i, sz; + ASSERT(pp && p && matched); + sz = pp->size(); + FOR_EACH(i, 0, sz) { + if(matched[i]) continue; + if(path_is_eq(&pp->at(i), p)) { + matched[i] = 1; + return 1; + } + } + return 0; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +scpr_polygon_create + (struct mem_allocator* mem_allocator, + struct scpr_polygon** out_polygon) +{ + struct scpr_polygon* polygon = NULL; + struct mem_allocator* allocator = NULL; + res_T res = RES_OK; + + if(!out_polygon) { + res = RES_BAD_ARG; + goto error; + } + + allocator = mem_allocator ? mem_allocator : &mem_default_allocator; + polygon = (struct scpr_polygon*) + MEM_CALLOC(allocator, 1, sizeof(struct scpr_polygon)); + if(!polygon) { + res = RES_MEM_ERR; + goto error; + } + ref_init(&polygon->ref); + polygon->allocator = allocator; + /* Allocate paths the C++ way (placement new) */ + new (&polygon->paths) Clipper2Lib::PathsD; + d2_splat(polygon->lower, DBL_MAX); + d2_splat(polygon->upper,-DBL_MAX); + +exit: + if(out_polygon) *out_polygon = polygon; + return res; + +error: + if(polygon) { + SCPR(polygon_ref_put(polygon)); + polygon = NULL; + } + goto exit; +} + +res_T +scpr_polygon_ref_get + (struct scpr_polygon* polygon) +{ + if(!polygon) return RES_BAD_ARG; + ref_get(&polygon->ref); + return RES_OK; +} + +res_T +scpr_polygon_ref_put + (struct scpr_polygon* polygon) +{ + if(!polygon) return RES_BAD_ARG; + ref_put(&polygon->ref, polygon_release); + return RES_OK; +} + +#define TRY(Expr) \ + try { \ + (Expr); \ + } \ + catch(std::bad_alloc &e) { \ + res = RES_MEM_ERR; \ + goto error; \ + } \ + catch(...) { \ + res = RES_UNKNOWN_ERR; \ + goto error; \ + } + +res_T +scpr_polygon_setup_indexed_vertices + (struct scpr_polygon* polygon, + const size_t ncomponents, /* #connex components */ + 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), + void* data) +{ + size_t c; + double scale = pow(10, PRECISION); + res_T res = RES_OK; + + if(!polygon || !get_nverts || !get_position || !data) { + res = RES_BAD_ARG; + goto error; + } + + TRY(polygon->paths.resize(ncomponents)); + + d2_splat(polygon->lower, DBL_MAX); + d2_splat(polygon->upper,-DBL_MAX); + + FOR_EACH(c, 0, ncomponents) { + size_t i, nverts; + + /* Get count for connex component c */ + get_nverts(c, &nverts, data); + TRY(polygon->paths[c].resize(nverts)); + + /* Fetch polygon positions for connex component c */ + FOR_EACH(i, 0, nverts) { + double tmp[2]; + int64_t tmp2[2]; + Clipper2Lib::PointD pt; + get_position(c, i, tmp, data); + /* Truncate precision to ensure further consistency */ + tmp2[0] = std::llround(tmp[0] * scale); + tmp2[1] = std::llround(tmp[1] * scale); + /* Store truncated vertex */ + pt.x = (double)tmp2[0] / scale; + pt.y = (double)tmp2[1] / scale; + if(fabs(tmp[0] - pt.x) > scale) { + res = RES_BAD_ARG; + goto error; + } + if(fabs(tmp[1] - pt.y) * scale > 1) { + res = RES_BAD_ARG; + goto error; + } + polygon->paths[c][i] = pt; + + d2_min(polygon->lower, polygon->lower, tmp); + d2_max(polygon->upper, polygon->upper, tmp); + } + } + +exit: + return res; +error: + if(polygon) { + polygon->paths.clear(); + d2_splat(polygon->lower, DBL_MAX); + d2_splat(polygon->upper,-DBL_MAX); + } + goto exit; +} + +res_T +scpr_polygon_create_copy + (struct mem_allocator* allocator, + const struct scpr_polygon* src_polygon, + struct scpr_polygon** out_polygon) +{ + struct scpr_polygon* copy; + int copy_created = 0; + res_T res = RES_OK; + + if(!src_polygon || !out_polygon) { + res = RES_BAD_ARG; + goto error; + } + ERR(scpr_polygon_create(allocator, &copy)); + copy_created = 1; + + copy->paths = src_polygon->paths; + d2_set(copy->lower, src_polygon->lower); + d2_set(copy->upper, src_polygon->upper); + +exit: + if(out_polygon) *out_polygon = copy; + return res; +error: + if(copy_created) CHK(RES_OK == scpr_polygon_ref_put(copy)); + copy = NULL; + goto exit; +} +#undef TRY + +res_T +scpr_polygon_get_components_count + (const struct scpr_polygon* polygon, + size_t* ncomps) +{ + if(!polygon || !ncomps) { + return RES_BAD_ARG; + } + *ncomps = polygon->paths.size(); + return RES_OK; +} + +res_T +scpr_polygon_get_vertices_count + (const struct scpr_polygon* polygon, + const size_t icomponent, + size_t* nverts) +{ + if(!polygon || !nverts || icomponent >= polygon->paths.size()) { + return RES_BAD_ARG; + } + *nverts = polygon->paths[icomponent].size(); + return RES_OK; +} + +res_T +scpr_polygon_get_position + (const struct scpr_polygon* polygon, + const size_t icomponent, + const size_t ivert, + double pos[2]) +{ + size_t nverts; + const size_t i = ivert; + const Clipper2Lib::PointD* pt; + if(!polygon || !pos || icomponent >= polygon->paths.size()) { + return RES_BAD_ARG; + } + SCPR(polygon_get_vertices_count(polygon, icomponent, &nverts)); + if(ivert >= nverts) return RES_BAD_ARG; + pt = &polygon->paths[icomponent][i]; + pos[0] = pt->x; + pos[1] = pt->y; + return RES_OK; +} + +res_T +scpr_offset_polygon + (struct scpr_polygon* poly_desc, + const double offset, /* Can be either positive or negative */ + const enum scpr_join_type join_type) +{ + size_t c; + Clipper2Lib::PathD tmp; + Clipper2Lib::JoinType cjt; + Clipper2Lib::PathsD polygon; + res_T res = RES_OK; + + if(!poly_desc) { + res = RES_BAD_ARG; + goto error; + } + + /* Check join type */ + switch(join_type) { + case SCPR_JOIN_SQUARE: + case SCPR_JOIN_ROUND: + case SCPR_JOIN_MITER: + break; + default: + res = RES_BAD_ARG; + goto error; + } + + /* Some known problems when offset=0, not leaving polygon unchanged */ + cjt = scpr_join_type_to_clipper_join_type(join_type); + poly_desc->paths = Clipper2Lib::InflatePaths(poly_desc->paths, offset, cjt, + Clipper2Lib::EndType::Polygon, 2, PRECISION); + + /* Rebuild AABB */ + d2_splat(poly_desc->lower, DBL_MAX); + d2_splat(poly_desc->upper,-DBL_MAX); + FOR_EACH(c, 0, poly_desc->paths.size()) { + size_t i, nverts; + nverts = poly_desc->paths[c].size(); + + FOR_EACH(i, 0, nverts) { + double pos[2]; + ERR(scpr_polygon_get_position(poly_desc, c, i, pos)); + d2_min(poly_desc->lower, poly_desc->lower, pos); + d2_max(poly_desc->upper, poly_desc->upper, pos); + } + } + +exit: + return res; +error: + goto exit; +} + +res_T +scpr_polygon_eq + (const struct scpr_polygon* polygon1, + const struct scpr_polygon* polygon2, + int* is_eq) +{ + size_t i, sz; + char* matched = NULL; + res_T res = RES_OK; + + if(!polygon1 || !polygon2 || !is_eq) { + res = RES_BAD_ARG; + goto error; + } + + sz = polygon1->paths.size(); + if(sz != polygon2->paths.size()) { + *is_eq = 0; + goto exit; + } + if(!d2_eq(polygon1->lower, polygon2->lower) + || !d2_eq(polygon1->upper, polygon2->upper)) + { + *is_eq = 0; + goto exit; + } + + /* 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)) { + *is_eq = 0; + goto exit; + } + } + *is_eq = 1; + +exit: + free(matched); + return res; +error: + goto exit; +} + + diff --git a/src/test_scpr_clip.c b/src/test_scpr_clip.c @@ -19,15 +19,7 @@ #include <rsys/math.h> #include <rsys/stretchy_array.h> -static void -get_clip_pos(const size_t ivert, double pos[2], void* ctx) -{ - const double* coords = ctx; - CHK(pos != NULL); - CHK(ctx != NULL); - pos[0] = coords[ivert*2+0]; - pos[1] = coords[ivert*2+1]; -} +#include <memory.h> static void dump_obj(FILE* stream, const struct scpr_mesh* mesh) @@ -56,78 +48,125 @@ dump_obj(FILE* stream, const struct scpr_mesh* mesh) } static void -test_triangle(struct scpr_mesh* mesh) +test_triangle + (struct mem_allocator* allocator, + struct scpr_mesh* mesh) { const double triangle_pos[] = { 0.0, 0.0, 0.0, 1.0, 1.0, 0.0 }; const size_t triangle_ids[] = { 0, 1, 2 }; - const double clip_pos[] = { -1.0, 0.25, 1.0, 0.75, 1, 0.25 }; - struct scpr_polygon poly; - struct mesh_context ctx; + double** clip_pos; + size_t nverts[] = { 3 }; + size_t ncomps = 1; + const double clip_pos0[] = { -1.0, 0.25, 1.0, 0.75, 1, 0.25 }; + struct scpr_polygon* poly; + struct polygon_context pctx; + struct mesh_context mctx; size_t ntris; - ctx.coords = triangle_pos; - ctx.nverts = 3; - ctx.indices = triangle_ids; - ctx.ntris = 1; + clip_pos = MEM_CALLOC(allocator, ncomps, sizeof(*clip_pos)); + *clip_pos = MEM_CALLOC(allocator, nverts[0], 2*sizeof(**clip_pos)); + memcpy(*clip_pos, clip_pos0, 2*nverts[0]*sizeof(**clip_pos)); + + pctx.coords = clip_pos; + pctx.nverts = nverts; + pctx.ncomps = ncomps; + CHK(scpr_polygon_create(allocator, &poly) == RES_OK); + CHK(scpr_polygon_setup_indexed_vertices(poly, ncomps, pget_nverts, pget_pos, &pctx) + == RES_OK); + + mctx.coords = triangle_pos; + mctx.nverts = 3; + mctx.indices = triangle_ids; + mctx.ntris = 1; CHK(scpr_mesh_setup_indexed_vertices - (mesh, ctx.ntris, get_ids, 3, get_pos, &ctx) == RES_OK); + (mesh, mctx.ntris, mget_ids, 3, mget_pos, &mctx) == RES_OK); - poly.get_position = get_clip_pos; - poly.nvertices = sizeof(clip_pos)/(2*sizeof(double)); - poly.context = (void*)clip_pos; CHK(scpr_mesh_clip(NULL, SCPR_OPERATIONS_COUNT__, NULL) == RES_BAD_ARG); CHK(scpr_mesh_clip(mesh, SCPR_OPERATIONS_COUNT__, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_clip(NULL, SCPR_OPERATIONS_COUNT__, &poly) == RES_BAD_ARG); - CHK(scpr_mesh_clip(mesh, SCPR_OPERATIONS_COUNT__, &poly) == RES_BAD_ARG); + CHK(scpr_mesh_clip(NULL, SCPR_OPERATIONS_COUNT__, poly) == RES_BAD_ARG); + CHK(scpr_mesh_clip(mesh, SCPR_OPERATIONS_COUNT__, poly) == RES_BAD_ARG); CHK(scpr_mesh_clip(NULL, SCPR_SUB, NULL) == RES_BAD_ARG); CHK(scpr_mesh_clip(mesh, SCPR_SUB, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_clip(NULL, SCPR_SUB, &poly) == RES_BAD_ARG); - CHK(scpr_mesh_clip(mesh, SCPR_SUB, &poly) == RES_OK); + CHK(scpr_mesh_clip(NULL, SCPR_SUB, poly) == RES_BAD_ARG); + CHK(scpr_mesh_clip(mesh, SCPR_SUB, poly) == RES_OK); /*dump_obj(stdout, mesh);*/ CHK(scpr_mesh_get_triangles_count(mesh, &ntris) == RES_OK); CHK(ntris == 3); + + MEM_RM(allocator, *clip_pos); + MEM_RM(allocator, clip_pos); + CHK(scpr_polygon_ref_put(poly) == RES_OK); } static void -test_quad(struct scpr_mesh* mesh) +test_quad + (struct mem_allocator* allocator, + struct scpr_mesh* mesh) { const double quad_pos[] = { 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0 }; const size_t quad_ids[] = { 0, 1, 3, 3, 1, 2 }; - const double clip_pos[] = { -0.25, 0.25, -0.25, 0.75, 1.25, 0.75, 1.25, 0.25 }; - struct scpr_polygon poly; - struct mesh_context ctx; - - ctx.coords = quad_pos; - ctx.nverts = sizeof(quad_pos)/(2*sizeof(double)); - ctx.indices = quad_ids; - ctx.ntris = sizeof(quad_ids)/(3*sizeof(size_t)); + double** clip_pos; + size_t nverts[] = { 4 }; + size_t ncomps = 1; + const double clip_pos0[] = { -0.25, 0.25, -0.25, 0.75, 1.25, 0.75, 1.25, 0.25 }; + struct scpr_polygon* poly; + struct polygon_context pctx; + struct mesh_context mctx; + + clip_pos = MEM_CALLOC(allocator, ncomps, sizeof(*clip_pos)); + *clip_pos = MEM_CALLOC(allocator, nverts[0], 2*sizeof(**clip_pos)); + memcpy(*clip_pos, clip_pos0, 2*nverts[0]*sizeof(**clip_pos)); + + pctx.coords = clip_pos; + pctx.nverts = nverts; + pctx.ncomps = ncomps; + CHK(scpr_polygon_create(allocator, &poly) == RES_OK); + CHK(scpr_polygon_setup_indexed_vertices(poly, ncomps, pget_nverts, pget_pos, &pctx) + == RES_OK); + + mctx.coords = quad_pos; + mctx.nverts = sizeof(quad_pos)/(2*sizeof(double)); + mctx.indices = quad_ids; + mctx.ntris = sizeof(quad_ids)/(3*sizeof(size_t)); CHK(scpr_mesh_setup_indexed_vertices - (mesh, ctx.ntris, get_ids, ctx.nverts, get_pos, &ctx) == RES_OK); + (mesh, mctx.ntris, mget_ids, mctx.nverts, mget_pos, &mctx) == RES_OK); - poly.get_position = get_clip_pos; - poly.nvertices = sizeof(clip_pos)/(2*sizeof(double)); - poly.context = (void*)clip_pos; - CHK(scpr_mesh_clip(mesh, SCPR_AND, &poly) == RES_OK); + CHK(scpr_mesh_clip(mesh, SCPR_AND, poly) == RES_OK); /*dump_obj(stdout, mesh);*/ + + MEM_RM(allocator, *clip_pos); + MEM_RM(allocator, clip_pos); + CHK(scpr_polygon_ref_put(poly) == RES_OK); } static void -test_disk(struct scpr_mesh* mesh) +test_disk + (struct mem_allocator* allocator, + struct scpr_mesh* mesh) { - const double clip[] = { -1.75, -1.75, 1.75, -1.75, 1.75, 1.75, -1.75, 1.75 }; + double** clip_pos; + size_t nverts[] = { 4 }; + size_t ncomps = 1; + const double clip_pos0[] + = { -1.75, -1.75, 1.75, -1.75, 1.75, 1.75, -1.75, 1.75 }; const size_t ninternal_disks = 10; const double radius = 2.5; const double internal_disk_step = radius / (double)ninternal_disks; const size_t nslices = 64; - struct mesh_context ctx; - struct scpr_polygon poly; + struct scpr_polygon* poly; + struct polygon_context pctx; + struct mesh_context mctx; double* pos = NULL; size_t* ids = NULL; size_t i, j; + clip_pos = MEM_CALLOC(allocator, ncomps, sizeof(*clip_pos)); + *clip_pos = MEM_CALLOC(allocator, nverts[0], 2*sizeof(**clip_pos)); + memcpy(*clip_pos, clip_pos0, 2*nverts[0]*sizeof(**clip_pos)); + FOR_EACH(i, 0, ninternal_disks) { const double r = (double)(i+1)*internal_disk_step; FOR_EACH(j, 0, nslices) { @@ -172,22 +211,29 @@ test_disk(struct scpr_mesh* mesh) sa_push(ids, id1); } - ctx.coords = pos; - ctx.nverts = sa_size(pos)/2; - ctx.indices = ids; - ctx.ntris = sa_size(ids)/3; + pctx.coords = clip_pos; + pctx.nverts = nverts; + pctx.ncomps = ncomps; + CHK(scpr_polygon_create(allocator, &poly) == RES_OK); + CHK(scpr_polygon_setup_indexed_vertices(poly, ncomps, pget_nverts, pget_pos, &pctx) + == RES_OK); + + mctx.coords = pos; + mctx.nverts = sa_size(pos)/2; + mctx.indices = ids; + mctx.ntris = sa_size(ids)/3; CHK(scpr_mesh_setup_indexed_vertices - (mesh, ctx.ntris, get_ids, ctx.nverts, get_pos, &ctx) == RES_OK); + (mesh, mctx.ntris, mget_ids, mctx.nverts, mget_pos, &mctx) == RES_OK); - poly.get_position = get_clip_pos; - poly.nvertices = sizeof(clip)/(2*sizeof(double)); - poly.context = (void*)clip; - CHK(scpr_mesh_clip(mesh, SCPR_SUB, &poly) == RES_OK); + CHK(scpr_mesh_clip(mesh, SCPR_SUB, poly) == RES_OK); dump_obj(stdout, mesh); + MEM_RM(allocator, *clip_pos); + MEM_RM(allocator, clip_pos); sa_release(pos); sa_release(ids); + CHK(scpr_polygon_ref_put(poly) == RES_OK); } int @@ -201,9 +247,9 @@ main(int argc, char** argv) CHK(scpr_mesh_create(&allocator, &mesh) == RES_OK); - test_triangle(mesh); - test_quad(mesh); - test_disk(mesh); + test_triangle(&allocator, mesh); + test_quad(&allocator, mesh); + test_disk(&allocator, mesh); CHK(scpr_mesh_ref_put(mesh) == RES_OK); diff --git a/src/test_scpr_mesh.c b/src/test_scpr_mesh.c @@ -71,40 +71,72 @@ main(int argc, char** argv) ctx.ntris = ntris; #define SETUP scpr_mesh_setup_indexed_vertices + CHK(SETUP(NULL, 0, NULL, 0, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, NULL, 0, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, NULL, 0, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, NULL, 0, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, mget_ids, 0, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, mget_ids, 0, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, mget_ids, 0, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, mget_ids, 0, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, NULL, nverts, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, NULL, nverts, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, NULL, nverts, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, NULL, nverts, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, mget_ids, nverts, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, mget_ids, nverts, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, mget_ids, nverts, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, mget_ids, nverts, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, NULL, 0, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, NULL, 0, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, NULL, 0, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, NULL, 0, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, mget_ids, 0, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, mget_ids, 0, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, mget_ids, 0, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, mget_ids, 0, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, NULL, nverts, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, NULL, nverts, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, NULL, nverts, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, NULL, nverts, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, mget_ids, nverts, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, mget_ids, nverts, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, mget_ids, nverts, mget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, mget_ids, nverts, mget_pos, NULL) == RES_BAD_ARG); CHK(SETUP(NULL, 0, NULL, 0, NULL, &ctx) == RES_BAD_ARG); CHK(SETUP(mesh, 0, NULL, 0, NULL, &ctx) == RES_BAD_ARG); CHK(SETUP(NULL, ntris, NULL, 0, NULL, &ctx) == RES_BAD_ARG); CHK(SETUP(mesh, ntris, NULL, 0, NULL, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, 0, get_ids, 0, NULL, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, 0, get_ids, 0, NULL, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, ntris, get_ids, 0, NULL, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, ntris, get_ids, 0, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, mget_ids, 0, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, mget_ids, 0, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, mget_ids, 0, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, mget_ids, 0, NULL, &ctx) == RES_BAD_ARG); CHK(SETUP(NULL, 0, NULL, nverts, NULL, &ctx) == RES_BAD_ARG); CHK(SETUP(mesh, 0, NULL, nverts, NULL, &ctx) == RES_BAD_ARG); CHK(SETUP(NULL, ntris, NULL, nverts, NULL, &ctx) == RES_BAD_ARG); CHK(SETUP(mesh, ntris, NULL, nverts, NULL, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, 0, get_ids, nverts, NULL, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, 0, get_ids, nverts, NULL, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, ntris, get_ids, nverts, NULL, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, ntris, get_ids, nverts, NULL, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, 0, NULL, 0, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, 0, NULL, 0, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, ntris, NULL, 0, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, ntris, NULL, 0, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, 0, get_ids, 0, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, 0, get_ids, 0, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, ntris, get_ids, 0, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, ntris, get_ids, 0, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, 0, NULL, nverts, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, 0, NULL, nverts, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, ntris, NULL, nverts, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, ntris, NULL, nverts, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, 0, get_ids, nverts, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, 0, get_ids, nverts, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(NULL, ntris, get_ids, nverts, get_pos, &ctx) == RES_BAD_ARG); - CHK(SETUP(mesh, ntris, get_ids, nverts, get_pos, &ctx) == RES_OK); + CHK(SETUP(NULL, 0, mget_ids, nverts, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, mget_ids, nverts, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, mget_ids, nverts, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, mget_ids, nverts, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, NULL, 0, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, NULL, 0, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, NULL, 0, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, NULL, 0, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, mget_ids, 0, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, mget_ids, 0, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, mget_ids, 0, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, mget_ids, 0, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, NULL, nverts, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, NULL, nverts, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, NULL, nverts, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, NULL, nverts, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, 0, mget_ids, nverts, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, 0, mget_ids, nverts, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, ntris, mget_ids, nverts, mget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, ntris, mget_ids, nverts, mget_pos, &ctx) == RES_OK); ctx.indices = indices_bad; - CHK(SETUP(mesh, 1, get_ids, nverts, get_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(mesh, 1, mget_ids, nverts, mget_pos, &ctx) == RES_BAD_ARG); #undef SETUP CHK(scpr_mesh_get_triangles_count(NULL, NULL) == RES_BAD_ARG); @@ -121,7 +153,7 @@ main(int argc, char** argv) ctx.indices = indices; CHK(scpr_mesh_setup_indexed_vertices - (mesh, ntris, get_ids, nverts, get_pos, &ctx) == RES_OK); + (mesh, ntris, mget_ids, nverts, mget_pos, &ctx) == RES_OK); CHK(scpr_mesh_get_triangles_count(mesh, &n) == RES_OK); CHK(n == ntris); CHK(scpr_mesh_get_vertices_count(mesh, &n) == RES_OK); diff --git a/src/test_scpr_offset.c b/src/test_scpr_offset.c @@ -0,0 +1,396 @@ +/* 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 "test_scpr_utils.h" + +#include <memory.h> + +static void +test_single(void) +{ + const double coords0[] = { + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + 1.0, 0.0 + }; + const double coords1[] = { + -1.0, -1.0, + -1.0, 2.0, + 2.0, 2.0, + 2.0, -1.0 + }; + const double coords2[] = { + 0.12345678901234, 0.0, + 0.0, 1.0, + 1.0, 9223372036854, + 1.0, 0.0 + }; + const double coords3[] = { + 9223372036855, 0.0, + 0.0, 1.0, + 1.0, 9223372036854, + 1.0, 0.0 + }; + double** coords; + size_t nverts[] = { 4 }; + size_t ncomps = 1; + struct mem_allocator allocator; + struct polygon_context ctx; + struct scpr_polygon* polygon; + struct scpr_polygon* expected; + int eq; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + + coords = MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); + *coords = MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords)); + memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords)); + + CHK(scpr_polygon_create(&allocator, &polygon) == RES_OK); + CHK(scpr_polygon_create(&allocator, &expected) == RES_OK); + + ctx.coords = coords; + ctx.nverts = nverts; + ctx.ncomps = ncomps; + + CHK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + /* Offset 0 = unchanged */ + CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Offset 1 */ + memcpy(*coords, coords1, 2*nverts[0]*sizeof(**coords)); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Offset -1: back to original polygon */ + memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords)); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Offset -5: empty polygon */ + ncomps = 0; + ctx.ncomps = ncomps; + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, -5, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Check consistency with a non representable coordinate */ + memcpy(*coords, coords2, 2*nverts[0]*sizeof(**coords)); + + ncomps = 1; + ctx.ncomps = ncomps; + + CHK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + /* Offset 0 = unchanged */ + CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Check out of range */ + memcpy(*coords, coords3, 2*nverts[0]*sizeof(**coords)); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_BAD_ARG); + + /* Cleanup */ + CHK(scpr_polygon_ref_put(polygon) == RES_OK); + CHK(scpr_polygon_ref_put(expected) == RES_OK); + + MEM_RM(&allocator, *coords); + MEM_RM(&allocator, coords); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); +} + +static void +test_double(void) +{ + const double coords0[] = { + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + 1.0, 0.0 + }; + const double coords1[] = { + 10.0, 0.0, + 10.0, 1.0, + 11.0, 1.0, + 11.0, 0.0 + }; + const double coords2[] = { + -1.0, -1.0, + -1.0, 2.0, + 2.0, 2.0, + 2.0, -1.0 + }; + const double coords3[] = { + 9.0, -1.0, + 9.0, 2.0, + 12.0, 2.0, + 12.0, -1.0 + }; + const double coords4[] = { + -4.5, -4.5, + -4.5, 5.5, + 15.5, 5.5, + 15.5, -4.5 + }; + double** coords; + size_t nverts[] = { 4, 4 }; + size_t ncomps = 2; + struct mem_allocator allocator; + struct polygon_context ctx; + struct scpr_polygon* polygon; + struct scpr_polygon* expected; + int eq; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + + coords = MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); + *coords = MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords)); + *(coords+1) = MEM_CALLOC(&allocator, nverts[1], 2*sizeof(**coords)); + memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords)); + memcpy(*(coords+1), coords1, 2*nverts[1]*sizeof(**coords)); + + CHK(scpr_polygon_create(&allocator, &polygon) == RES_OK); + CHK(scpr_polygon_create(&allocator, &expected) == RES_OK); + + ctx.coords = coords; + ctx.nverts = nverts; + ctx.ncomps = ncomps; + + CHK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + /* Offset 0 = unchanged */ + CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Offset 1 */ + memcpy(*coords, coords2, 2*nverts[0]*sizeof(**coords)); + memcpy(*(coords+1), coords3, 2*nverts[1]*sizeof(**coords)); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Offset -1: back to original polygon */ + memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords)); + memcpy(*(coords+1), coords1, 2*nverts[1]*sizeof(**coords)); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Offset 4.5: the 2 squares merge */ + ncomps = 1; + ctx.ncomps = ncomps; + memcpy(*coords, coords4, 2*nverts[0]*sizeof(**coords)); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, 4.5, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Offset -5: empty polygon */ + ncomps = 0; + ctx.ncomps = ncomps; + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, -5, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + CHK(scpr_polygon_ref_put(polygon) == RES_OK); + CHK(scpr_polygon_ref_put(expected) == RES_OK); + + MEM_RM(&allocator, *coords); + MEM_RM(&allocator, *(coords+1)); + MEM_RM(&allocator, coords); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); +} + +static void +test_internal(void) +{ + const double coords0[] = { + -10, -10, + -10, 10, + 10, 10, + 10, -10 + }; + const double coords1[] = { + -5, -5, + 5, -5, + 5, 5, + -5, 5 + }; + const double coords2[] = { + -9, -9, + -9, 9, + 9, 9, + 9, -9 + }; + const double coords3[] = { + -6, -6, + 6, -6, + 6, 6, + -6, 6 + }; + const double coords4[] = { + -15, -15, + -15, 15, + 15, 15, + 15, -15 + }; + double** coords; + size_t nverts[] = { 4, 4 }; + size_t ncomps = 2; + struct mem_allocator allocator; + struct polygon_context ctx; + struct scpr_polygon* polygon; + struct scpr_polygon* expected; + int eq; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + + coords = MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); + *coords = MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords)); + *(coords+1) = MEM_CALLOC(&allocator, nverts[1], 2*sizeof(**coords)); + memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords)); + memcpy(*(coords+1), coords1, 2*nverts[1]*sizeof(**coords)); + + CHK(scpr_polygon_create(&allocator, &polygon) == RES_OK); + CHK(scpr_polygon_create(&allocator, &expected) == RES_OK); + + ctx.coords = coords; + ctx.nverts = nverts; + ctx.ncomps = ncomps; + + CHK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + /* Offset 0 = unchanged */ + CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Offset -1 */ + memcpy(*coords, coords3, 2*nverts[0]*sizeof(**coords)); + memcpy(*(coords+1), coords2, 2*nverts[1]*sizeof(**coords)); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Offset 1: back to original polygon */ + memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords)); + memcpy(*(coords+1), coords1, 2*nverts[1]*sizeof(**coords)); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* Offset 5: internal path disappears */ + ncomps = 1; + ctx.ncomps = ncomps; + memcpy(*coords, coords4, 2*nverts[0]*sizeof(**coords)); + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, 5, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + /* From the original polygon, offset -2.5: empty polygon */ + ncomps = 2; + ctx.ncomps = ncomps; + memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords)); + memcpy(*(coords+1), coords1, 2*nverts[1]*sizeof(**coords)); + CHK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + ncomps = 0; + ctx.ncomps = ncomps; + CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) + == RES_OK); + + CHK(scpr_offset_polygon(polygon, -2.5, SCPR_JOIN_MITER) == RES_OK); + CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + CHK(eq); + + CHK(scpr_polygon_ref_put(polygon) == RES_OK); + CHK(scpr_polygon_ref_put(expected) == RES_OK); + + MEM_RM(&allocator, *coords); + MEM_RM(&allocator, *(coords+1)); + MEM_RM(&allocator, coords); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); +} + +int +main(int argc, char** argv) +{ + (void)argc; (void)argv; + test_single(); + test_double(); + test_internal(); + return 0; +} diff --git a/src/test_scpr_polygon.c b/src/test_scpr_polygon.c @@ -0,0 +1,158 @@ +/* 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 "test_scpr_utils.h" + +#include <memory.h> +#include <rsys/rsys.h> + +int +main(int argc, char** argv) +{ + double coords0[] = { + 0.0, 0.0, + 0.0, 0.5, + 0.0, 1.0, + 0.5, 0.0, + 0.5, 0.5, + 0.5, 1.0, + 1.0, 0.0, + 1.0, 0.5, + 1.0, 1.0 + }; + double** coords; + size_t nverts[] = { 9 }; + size_t ncomps = 1; + double pos[2]; + size_t i, c, n; + struct mem_allocator allocator; + struct polygon_context ctx; + struct scpr_polygon* polygon; + struct scpr_polygon* copy; + int eq; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + + coords = MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); + *coords = MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords)); + memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords)); + + CHK(scpr_polygon_create(NULL, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_create(&allocator, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_create(NULL, &polygon) == RES_OK); + CHK(scpr_polygon_create(NULL, &copy) == RES_OK); + CHK(scpr_polygon_ref_put(copy) == RES_OK); + + CHK(scpr_polygon_ref_get(NULL) == RES_BAD_ARG); + CHK(scpr_polygon_ref_get(polygon) == RES_OK); + CHK(scpr_polygon_ref_put(NULL) == RES_BAD_ARG); + CHK(scpr_polygon_ref_put(polygon) == RES_OK); + CHK(scpr_polygon_ref_put(polygon) == RES_OK); + + CHK(scpr_polygon_create(&allocator, &polygon) == RES_OK); + + CHK(scpr_polygon_get_components_count(polygon, &n) == RES_OK); + CHK(n == 0); + + ctx.coords = coords; + ctx.nverts = nverts; + ctx.ncomps = ncomps; + + #define SETUP scpr_polygon_setup_indexed_vertices + CHK(SETUP(NULL, ncomps, NULL, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(polygon, ncomps, NULL, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ncomps, pget_nverts, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(polygon, ncomps, pget_nverts, NULL, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ncomps, NULL, pget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(polygon, ncomps, NULL, pget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ncomps, pget_nverts, pget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(polygon, ncomps, pget_nverts, pget_pos, NULL) == RES_BAD_ARG); + CHK(SETUP(NULL, ncomps, NULL, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(polygon, ncomps, NULL, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, ncomps, pget_nverts, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(polygon, ncomps, pget_nverts, NULL, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, ncomps, NULL, pget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(polygon, ncomps, NULL, pget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(NULL, ncomps, pget_nverts, pget_pos, &ctx) == RES_BAD_ARG); + CHK(SETUP(polygon, ncomps, pget_nverts, pget_pos, &ctx) == RES_OK); + #undef SETUP + + CHK(scpr_polygon_get_components_count(NULL, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_get_components_count(polygon, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_get_components_count(NULL, &n) == RES_BAD_ARG); + CHK(scpr_polygon_get_components_count(polygon, &n) == RES_OK); + CHK(n == ncomps); + + CHK(scpr_polygon_get_vertices_count(NULL, ncomps, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_get_vertices_count(polygon, ncomps, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_get_vertices_count(NULL, 0, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_get_vertices_count(polygon, 0, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_get_vertices_count(NULL, ncomps, &n) == RES_BAD_ARG); + CHK(scpr_polygon_get_vertices_count(polygon, ncomps, &n) == RES_BAD_ARG); + CHK(scpr_polygon_get_vertices_count(NULL, 0, &n) == RES_BAD_ARG); + CHK(scpr_polygon_get_vertices_count(polygon, 0, &n) == RES_OK); + CHK(n == nverts[0]); + + CHK(scpr_polygon_eq(NULL, NULL, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_eq(polygon, NULL, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_eq(NULL, polygon, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_eq(polygon, polygon, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_eq(NULL, NULL, &eq) == RES_BAD_ARG); + CHK(scpr_polygon_eq(polygon, NULL, &eq) == RES_BAD_ARG); + CHK(scpr_polygon_eq(polygon, 0, &eq) == RES_BAD_ARG); + CHK(scpr_polygon_eq(polygon, polygon, &eq) == RES_OK); + CHK(eq); + + CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG); + CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG); + CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG); + CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG); + CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG); + CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG); + CHK(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos) == RES_BAD_ARG); + FOR_EACH(c, 0, ncomps) { + FOR_EACH(i, 0, nverts[c]) { + CHK(scpr_polygon_get_position(polygon, c, i, pos) == RES_OK); + CHK(pos[0] == coords[c][i*2+0]); + CHK(pos[1] == coords[c][i*2+1]); + } + } + + CHK(scpr_polygon_create_copy(NULL, NULL, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_create_copy(NULL, NULL, &copy) == RES_BAD_ARG); + CHK(scpr_polygon_create_copy(NULL, polygon, NULL) == RES_BAD_ARG); + CHK(scpr_polygon_create_copy(NULL, polygon, &copy) == RES_OK); + CHK(scpr_polygon_eq(polygon, copy, &eq) == RES_OK); + CHK(eq); + + CHK(scpr_offset_polygon(NULL, 0, SCPR_JOIN_TYPES_COUNT__) == RES_BAD_ARG); + CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_TYPES_COUNT__) == RES_BAD_ARG); + CHK(scpr_offset_polygon(NULL, 0, SCPR_JOIN_MITER) == RES_BAD_ARG); + CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK); + + CHK(scpr_polygon_ref_put(polygon) == RES_OK); + CHK(scpr_polygon_ref_put(copy) == RES_OK); + + MEM_RM(&allocator, *coords); + 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_utils.h b/src/test_scpr_utils.h @@ -19,6 +19,35 @@ #include <rsys/mem_allocator.h> #include <stdio.h> +struct polygon_context { + double** coords; + size_t* nverts; + size_t ncomps; +}; + +static INLINE void +pget_nverts(const size_t icomp, size_t* nverts, void* context) +{ + const struct polygon_context* ctx = context; + CHK(nverts != NULL); + CHK(context != NULL); + CHK(icomp < ctx->ncomps); + *nverts = ctx->nverts[icomp]; +} + +static INLINE void +pget_pos(const size_t icomp, const size_t ivert, double pos[2], void* context) +{ + const struct polygon_context* ctx = context; + CHK(pos != NULL); + CHK(context != NULL); + CHK(ctx->coords != NULL); + CHK(icomp < ctx->ncomps); + CHK(ivert < ctx->nverts[icomp]); + pos[0] = ctx->coords[icomp][ivert*2 + 0]; + pos[1] = ctx->coords[icomp][ivert*2 + 1]; +} + struct mesh_context { const double* coords; size_t nverts; @@ -26,8 +55,8 @@ struct mesh_context { size_t ntris; }; -static void -get_pos(const size_t ivert, double pos[2], void* context) +static INLINE void +mget_pos(const size_t ivert, double pos[2], void* context) { const struct mesh_context* ctx = context; CHK(pos != NULL); @@ -38,8 +67,8 @@ get_pos(const size_t ivert, double pos[2], void* context) pos[1] = ctx->coords[ivert*2 + 1]; } -static void -get_ids(const size_t itri, size_t ids[3], void* context) +static INLINE void +mget_ids(const size_t itri, size_t ids[3], void* context) { const struct mesh_context* ctx = context; CHK(ids != NULL);