star-cpr

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

commit 58b26eeea2b9f865a10b67e9500e8c18f46be5c7
parent 531327dc13e560add3e1e9e9d5e791d70dbca8d5
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Wed,  1 Mar 2023 16:11:52 +0100

Merge branch 'release_0.3'

Diffstat:
MREADME.md | 12+++++++++++-
Mcmake/CMakeLists.txt | 18++++++++++++++----
Dcmake/ClipperConfig.cmake | 74--------------------------------------------------------------------------
Msrc/scpr.h | 193++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/scpr_c.h | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Asrc/scpr_device.c | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scpr_intersector.c | 802+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/scpr_mesh.c | 254+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/scpr_polygon.c | 330++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/test_scpr_clip.c | 118+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Asrc/test_scpr_device.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_scpr_intersector.c | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_scpr_mesh.c | 213+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/test_scpr_offset.c | 224+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/test_scpr_polygon.c | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/test_scpr_utils.h | 47+++++++++++++++++++++++++++++++++++++++++++----
16 files changed, 2636 insertions(+), 645 deletions(-)

diff --git a/README.md b/README.md @@ -11,7 +11,7 @@ clipping polygon. The library uses [CMake](http://www.cmake.org) and the [RCMake](https://gitlab.com/vaplv/rcmake/#tab-readme) package to build. It also depends on the -[Clipper](http://www.angusj.com/delphi/clipper.php), +[Clipper2](https://github.com/AngusJohnson/Clipper2), [Polygon](https://gitlab.Com/vaplv/polygon/#tab-readme) and [RSys](https://gitlab.com/vaplv/rsys/#tab-readme) libraries. @@ -22,6 +22,16 @@ project from the `cmake/CMakeLists.txt` file by appending to the ## Release notes +### Version 0.3 + +- Add functions to detect polygons intersections. At this stage, only + intersections of segments ares detected (neither "vertex on a segment", nor + "polygons sharing a vertex" are detected). +- Add a device to hold library settings; this breaks the whole API. +- Change internals to use integer representation for polygons. +- Improve out-of-range detection. +- Upgrade Clipper2 to 1.2.0 + ### Version 0.2 - Switch Clipper library to Clipper2. This increases requirements on compiler to diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -29,7 +29,7 @@ set(Clipper2_DIR ${_current_source_dir}/) find_package(RCMake REQUIRED) find_package(RSys 0.6 REQUIRED) -find_package(Clipper2 1 REQUIRED) +find_package(Clipper2 1.2 REQUIRED) find_package(Polygon 0.0.5 REQUIRED) include_directories( @@ -47,11 +47,15 @@ rcmake_append_runtime_dirs(_runtime_dirs RSys Polygon Clipper2) # Define targets ################################################################################ set(VERSION_MAJOR 0) -set(VERSION_MINOR 2) +set(VERSION_MINOR 3) set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) -set(SCPR_FILES_SRC scpr_mesh.c scpr_polygon.c) +set(SCPR_FILES_SRC + scpr_device.c + scpr_intersector.c + 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) @@ -69,7 +73,7 @@ set_target_properties(scpr PROPERTIES target_link_libraries(scpr RSys Polygon Clipper2) if(CMAKE_COMPILER_IS_GNUCXX) - set_target_properties(scpr PROPERTIES COMPILE_FLAGS "-Wno-long-long -std=c++17") + set_target_properties(scpr PROPERTIES COMPILE_FLAGS "-std=c++17") endif() rcmake_setup_devel(scpr StarCPR ${VERSION} star/scpr_version.h) @@ -85,10 +89,16 @@ if(NOT NO_TEST) function(new_test _name) add_executable(${_name} ${SCPR_SOURCE_DIR}/${_name}.c) + set_source_files_properties(${SCPR_SOURCE_DIR}/${_name}.c PROPERTIES LANGUAGE C) + if(CMAKE_COMPILER_IS_GNUCXX) + set_target_properties(${name} PROPERTIES COMPILE_FLAGS "-std=c99") + endif() target_link_libraries(${_name} scpr RSys ${MATH_LIB}) add_test(${_name} ${_name}) 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/cmake/ClipperConfig.cmake b/cmake/ClipperConfig.cmake @@ -1,74 +0,0 @@ -# Copyright (C) 2016-2018, 2021 |Meso|Star> (contact@meso-star.com) -# -# This software is a computer program whose purpose is to generate files -# used to build the Star-3D library. -# -# This software is governed by the CeCILL license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". -# -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. -# -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. -# -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL license and that you accept its terms. - -cmake_minimum_required(VERSION 3.1) - -# 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(Clipper2_INCLUDE_DIR polyclipping/clipper.hpp) -set(Clipper2_INCLUDE_DIR ${Clipper2_INCLUDE_DIR}/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(Clipper2_LIBRARY_DEBUG polyclipping-dbg - DOC "Path to the clipper library used during debug builds.") - -# Create the imported library target -if(CMAKE_HOST_WIN32) - set(_property IMPORTED_IMPLIB) -else(CMAKE_HOST_WIN32) - set(_property IMPORTED_LOCATION) - 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(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(Clipper2 DEFAULT_MSG - Clipper2_INCLUDE_DIR - Clipper2_LIBRARY - Clipper2_LIBRARY_DEBUG) - diff --git a/src/scpr.h b/src/scpr.h @@ -51,25 +51,129 @@ enum scpr_join_type { /* Forward declaration */ struct mem_allocator; -/* Opaque 2D closed polygon data type */ +/* Forward declaration of the star-cpr opaque data types. These data types are + * ref counted. Once created the caller implicitly owns the created data, i.e. + * its reference counter is set to 1. The scpr_<TYPE>_ref_<get|put> functions + * get or release a reference on the data, i.e. they increment or decrement the + * reference counter, respectively. When this counter reaches 0, the object is + * silently destroyed and cannot be used anymore. */ +struct scpr_device; struct scpr_polygon; -/* Opaque 2D mesh data type */ struct scpr_mesh; +struct scpr_intersector; + +/* Input arguments of the scpr_device_create function */ +struct scpr_device_create_args { + struct logger* logger; /* NULL <=> default logger */ + struct mem_allocator* allocator; /* NULL <=> default allocator */ + int verbosity_level; + int precision; /* Number of decimal place preserved; in [0 8] */ +}; +#define SCPR_DEVICE_CREATE_ARGS_DEFAULT__ { \ + NULL, NULL, 1, 6 \ +} +static const struct scpr_device_create_args SCPR_DEVICE_CREATE_ARGS_DEFAULT = + SCPR_DEVICE_CREATE_ARGS_DEFAULT__; + +/* A struct to represent segments when calling user callbacks. */ +struct scpr_callback_segment { + struct scpr_polygon* polygon; + size_t component; + size_t first_vertex; + size_t last_vertex; +}; + +/* A struct holding callbacks that are called when carying an intersection check + * on a bunch of segments. + * If a callbacks is NULL, the corresponding events are unreported, but at least + * 1 callback must be non NULL. + * If a callback call returns non-zero, the whole scpr_intersector_check call + * exits whith a RES_BAD_ARG error status without further processing. */ +struct scpr_intersector_check_callbacks { + int(*simple_intersection) + (struct scpr_callback_segment* segment1, + struct scpr_callback_segment* segment2, + void* ctx); + int(*collinear_segments) + (struct scpr_callback_segment* segment1, + struct scpr_callback_segment* segment2, + void* ctx); +}; +#define SCPR_INTERSECTOR_CHECK_CALLBACKS_NULL__ { NULL, NULL } BEGIN_DECLS -/* Polygons can be made of any number of paths. +/******************************************************************************* + * star-scpr Device. It is an handle toward the Stardis library. It manages the + * star-scpr resources. + ******************************************************************************/ +SCPR_API res_T +scpr_device_create + (const struct scpr_device_create_args* args, + struct scpr_device** dev); + +SCPR_API res_T +scpr_device_ref_get + (struct scpr_device* dev); + +SCPR_API res_T +scpr_device_ref_put + (struct scpr_device* dev); + +/* Get the range for polygon and mesh vertices. + * The range depends on the precision parameter of the device and is + * [-2^(46-precision) + 2^(46-precision)]. */ +SCPR_API res_T +scpr_device_get_range + (const struct scpr_device* dev, + double range[2]); + +/* Check if values are in range */ +SCPR_API res_T +scpr_device_in_range + (const struct scpr_device* dev, + const double* values, + const size_t count, + int* in_range); + +/* Scale a set of values. + * Internal representation for vertices in star-cpr is in interger format. + * The process of converting reals to integers is named scaling (and the reverse + * process is named unscaling). */ +SCPR_API res_T +scpr_device_scale + (const struct scpr_device* dev, + const double* values, + const size_t count, + int64_t* scaled); + +/* Unscale a set of values. + * Internal representation for vertices in star-cpr is in interger format. + * The process of converting reals to integers is named scaling (and the reverse + * process is named unscaling). */ +SCPR_API res_T +scpr_device_unscale + (const struct scpr_device* dev, + const int64_t* values, + const size_t count, + double* unscaled); + +/******************************************************************************* + * Type of polygons, as manipulated by star-cpr. + * Polygons can be made of any number of paths and are subject to a range limit + * and a precision (see device). * 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. */ + * 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_device* dev, struct scpr_polygon** polygon); SCPR_API res_T scpr_polygon_create_copy - (struct mem_allocator* allocator, /* May be NULL <=> Use default allocator */ + (struct scpr_device* dev, const struct scpr_polygon* src_polygon, struct scpr_polygon** polygon); @@ -81,14 +185,11 @@ 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. */ +/* The number of components and vertices can be changed due to a + * simplification process and one should not take for granted that the number of + * components and vertices stay as provided at construction time. + * The actual counts and coordinates should be retrieved using the appropriate + * getters. */ SCPR_API res_T scpr_polygon_setup_indexed_vertices (struct scpr_polygon* polygon, @@ -99,6 +200,13 @@ scpr_polygon_setup_indexed_vertices void* data); /* Client data set as the last param of the callbacks */ SCPR_API res_T +scpr_polygon_in_bbox + (struct scpr_polygon* polygon, + const double lower[2], + const double upper[2], + int* inside); + +SCPR_API res_T scpr_offset_polygon (struct scpr_polygon* polygon, const double offset, /* Can be either positive or negative */ @@ -122,6 +230,8 @@ scpr_polygon_get_position const size_t ivert, double position[2]); +/* Logical comparison for polygons. + * Component order and orientation are not considered. */ SCPR_API res_T scpr_polygon_eq (const struct scpr_polygon* polygon1, @@ -129,8 +239,24 @@ scpr_polygon_eq int* is_eq); SCPR_API res_T +scpr_polygon_dump_to_obj + (struct scpr_polygon* polygon, + FILE* stream); + +SCPR_API res_T +scpr_polygon_dump_component_to_obj + (struct scpr_polygon* polygon, + const size_t icomponent, + FILE* stream); + +/******************************************************************************* + * Type of meshes, as manipulated by star-cpr. + * Meshes can be made of any number of triangles and are subject to a range + * limit and a precision (see device). + ******************************************************************************/ +SCPR_API res_T scpr_mesh_create - (struct mem_allocator* allocator, /* May be NULL <=> Use default allocator */ + (struct scpr_device* dev, struct scpr_mesh** mesh); SCPR_API res_T @@ -150,7 +276,7 @@ scpr_mesh_setup_indexed_vertices void (*get_position)(const size_t ivert, double pos[2], void* ctx), void* data); /* Client data set as the last param of the callbacks */ -/* Clip the mesh against the polygon contour */ +/* Clip the mesh against the polygon using the EvenOdd filling rule. */ SCPR_API res_T scpr_mesh_clip (struct scpr_mesh* mesh, /* Candidate mesh to clip */ @@ -179,6 +305,41 @@ scpr_mesh_get_position const size_t ivert, double position[2]); +/******************************************************************************* + * An intersector provide a way to check for polygon intersections, either + * self intersections or intersections between polygons. + ******************************************************************************/ +SCPR_API res_T +scpr_intersector_create + (struct scpr_device* dev, + struct scpr_intersector** intersector); + +SCPR_API res_T +scpr_intersector_ref_get + (struct scpr_intersector* intersector); + +SCPR_API res_T +scpr_intersector_ref_put + (struct scpr_intersector* intersector); + +/* Register a polygon's a polygon or a component for further analysis */ +SCPR_API res_T +scpr_intersector_register_polygon + (struct scpr_intersector* intersector, + struct scpr_polygon* polygon); + +SCPR_API res_T +scpr_intersector_register_component + (struct scpr_intersector* intersector, + struct scpr_polygon* polygon, + const size_t icomponent); + +SCPR_API res_T +scpr_intersector_check + (struct scpr_intersector* intersector, + struct scpr_intersector_check_callbacks* callbacks, + void* data); /* Client data set as the last param of the callbacks */ + END_DECLS #endif /* SCPR_H */ diff --git a/src/scpr_c.h b/src/scpr_c.h @@ -18,46 +18,269 @@ #include "scpr.h" +#include <cstddef> #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/dynamic_array.h> #include <rsys/hash_table.h> +#include <math.h> + #undef PI #include <clipper2/clipper.h> +#define DARRAY_NAME uint32 +#define DARRAY_DATA uint32_t +#include <rsys/dynamic_array.h> + +#define DARRAY_NAME int64 +#define DARRAY_DATA int64_t +#include <rsys/dynamic_array.h> + #define ERR(Expr) { if((res = (Expr)) != RES_OK) goto error; } (void)0 +#define TRY(Expr) \ + try { \ + (Expr); \ + } \ + catch(std::bad_alloc &e) { \ + res = RES_MEM_ERR; \ + goto error; \ + } \ + catch(...) { \ + res = RES_UNKNOWN_ERR; \ + goto error; \ + } + struct mem_allocator; -struct vertex { double pos[2]; }; +struct vertex { int64_t pos[2]; }; static FINLINE int vertex_eq(const struct vertex* a, const struct vertex* b) -{ return d2_eq(a->pos, b->pos); } +{ ASSERT(a && b); return (a->pos[0] == b->pos[0]) && (a->pos[1] == b->pos[1]); } /* Define the vertex to index hash table */ #define HTABLE_NAME vertex -#define HTABLE_DATA size_t +#define HTABLE_DATA uint32_t #define HTABLE_KEY struct vertex #define HTABLE_KEY_FUNCTOR_EQ vertex_eq #include <rsys/hash_table.h> -struct scpr_polygon { - Clipper2Lib::PathsD paths; - double lower[2], upper[2]; /* Polygon AABB */ +struct scpr_device { + int precision; + double scale; + double inv_scale; + double range[2]; ref_T ref; struct mem_allocator* allocator; + struct logger* logger; +}; + +struct scpr_polygon { + Clipper2Lib::Paths64 paths; + int64_t lower[2], upper[2]; /* Polygon AABB */ + + ref_T ref; + struct scpr_device* device; }; struct scpr_mesh { - struct darray_double coords; - struct darray_size_t indices; + struct darray_int64 coords; + struct darray_uint32 indices; ref_T ref; - struct mem_allocator* allocator; + struct scpr_device* device; +}; + +struct intersector_segment { + uint32_t component, first_vertex; +}; +#define DARRAY_NAME segments +#define DARRAY_DATA struct intersector_segment +#include <rsys/dynamic_array.h> + +/* A segment htable links a count (1 or 2) to a segment index in the segments + * darray. + * The count is the number of ends of the segment at some range_point. */ +#define HTABLE_NAME segment_idx +#define HTABLE_KEY uint32_t +#define HTABLE_DATA char +#include <rsys/hash_table.h> + +/* Each range_point stores the segments that have 1 or 2 ends there */ +struct intersector_range_point { + struct htable_segment_idx segments; + int64_t coord; +}; +static FINLINE void +range_point_init + (struct mem_allocator* alloc, struct intersector_range_point* data) +{ + data->coord = INT64_MIN; + htable_segment_idx_init(alloc, &data->segments); +} +static FINLINE void +range_point_release(struct intersector_range_point* data) +{ + htable_segment_idx_release(&data->segments); +} +static FINLINE res_T +range_point_copy + (struct intersector_range_point* dst, struct intersector_range_point const* src) +{ + dst->coord = src->coord; + return htable_segment_idx_copy(&dst->segments, &src->segments); +} +static FINLINE res_T +range_point_copy_and_release + (struct intersector_range_point* dst, struct intersector_range_point* src) +{ + dst->coord = src->coord; + return htable_segment_idx_copy_and_release(&dst->segments, &src->segments); +} +/* A darray of range_point. */ +#define DARRAY_NAME range_points +#define DARRAY_DATA struct intersector_range_point +#define DARRAY_FUNCTOR_INIT range_point_init +#define DARRAY_FUNCTOR_COPY range_point_copy +#define DARRAY_FUNCTOR_RELEASE range_point_release +#define DARRAY_FUNCTOR_COPY_AND_RELEASE range_point_copy_and_release +#include <rsys/dynamic_array.h> + +/* An htable to link a pointer to a range_point to an 1D coordinate */ +#define HTABLE_NAME range_point_idx +#define HTABLE_KEY int64_t +#define HTABLE_DATA uint32_t +#include <rsys/hash_table.h> + +enum segment_interaction { + NO_INTERACTION = 0, + OVERLAP_X = BIT(0), + CONTACT_X = BIT(1), + OVERLAP_Y = BIT(2), + CONTACT_Y = BIT(3), + SOMETHING_X = OVERLAP_X | CONTACT_X, + SOMETHING_Y = OVERLAP_Y | CONTACT_Y, + SOME_OVERLAP = OVERLAP_X | OVERLAP_Y +}; +/* A dimension is a structure containing all the points where a segment starts + * or ends, each one storing a link to all the segments involved there. */ +struct intersector_dimension { + /* Unsorted 1D points registered against this dimension */ + struct htable_range_point_idx unsorted; + /* Same points, but sorted by increasing coordinates along this dimension */ + struct darray_range_points sorted; + /* Range on this dimension */ + int64_t range[2]; + /* Flags about segments interaction for this dimension */ + enum segment_interaction overlap, contact; +}; + +/* An htable to store overlapping information */ +struct intersector_segment_pair { + uint32_t rank1, rank2; +}; +static INLINE char +pair_eq + (struct intersector_segment_pair const* a, + struct intersector_segment_pair const* b) +{ + ASSERT(a && b); + return a->rank1 == b->rank1 && a->rank2 == b->rank2; +} +#define HTABLE_NAME interacting_segments +#define HTABLE_KEY struct intersector_segment_pair +#define HTABLE_DATA unsigned char +#define HTABLE_KEY_FUNCTOR_EQ pair_eq +#include <rsys/hash_table.h> + +/* An htable to keep track of registered components */ +#pragma pack(push, 1) /* Avoid padding */ +struct intersector_component { + scpr_polygon* polygon; + uint32_t component; + /* If there is padding, hashing reports uninitialized data in htable code */ +}; +#pragma pack(pop) +static FINLINE void +component_init(struct mem_allocator* alloc, struct intersector_component* key) +{ + ASSERT(alloc && key); + (void)alloc; + key->polygon = NULL; + key->component = UINT32_MAX; +} +static INLINE char +component_eq + (struct intersector_component const* a, + struct intersector_component const* b) +{ + ASSERT(a && b); + return a->polygon == b->polygon && a->component == b->component; +} +static FINLINE void +component_release(struct intersector_component* key) +{ + ASSERT(key); + if(key->polygon) SCPR(polygon_ref_put(key->polygon)); +} +static FINLINE res_T +component_copy + (struct intersector_component* dst, struct intersector_component const* src) +{ + ASSERT(dst && src); + dst->polygon = src->polygon; + dst->component = src->component; + if(dst->polygon) SCPR(polygon_ref_get(dst->polygon)); + return RES_OK; +} +static FINLINE res_T +component_copy_and_release + (struct intersector_component* dst, struct intersector_component* src) +{ + ASSERT(dst && src); + dst->polygon = src->polygon; + dst->component = src->component; + return RES_OK; +} +#define HTABLE_NAME registered_components +#define HTABLE_KEY struct intersector_component +#define HTABLE_DATA char +#define HTABLE_KEY_FUNCTOR_INIT component_init +#define HTABLE_KEY_FUNCTOR_EQ component_eq +#define HTABLE_KEY_FUNCTOR_COPY component_copy +#define HTABLE_KEY_FUNCTOR_RELEASE component_release +#define HTABLE_KEY_FUNCTOR_COPY_AND_RELEASE component_copy_and_release +#include <rsys/hash_table.h> + +#define DARRAY_NAME components +#define DARRAY_DATA struct intersector_component +#define DARRAY_FUNCTOR_INIT component_init +#define DARRAY_FUNCTOR_EQ component_eq +#define DARRAY_FUNCTOR_COPY component_copy +#define DARRAY_FUNCTOR_RELEASE component_release +#define DARRAY_FUNCTOR_COPY_AND_RELEASE component_copy_and_release +#include <rsys/dynamic_array.h> + +/* A set of segments from different polygons spatially sorted to help find + * (self-)intersections. */ +struct scpr_intersector { + struct scpr_device* device; + /* All the registered segments */ + struct darray_segments segments; + /* Spatial partition of the segments along the 2 dimensions */ + struct intersector_dimension dimensions[2]; + /* All the pairs of segment that are in interaction in some way or another. + * They are all the candidates for actual intersection plus some others that + * are just close, but are without interaction. */ + struct htable_interacting_segments interacting_segments; + /* All the registererd components (an htable to check for unicity and a darray + * to store the components with indexing capability) */ + struct htable_registered_components registererd_components; + struct darray_components components; + ref_T ref; }; #endif diff --git a/src/scpr_device.c b/src/scpr_device.c @@ -0,0 +1,189 @@ +/* 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 <new> +#include <polygon.h> +#include <rsys/mem_allocator.h> +#include <rsys/logger.h> +#include <rsys/rsys.h> +#include <rsys/double2.h> + +#undef PI +#include <clipper2/clipper.h> + +#include <math.h> + +#ifdef OS_UNIX + /* On UNIX assume a VT100-like terminal emulator */ + #define MSG_INFO_PREFIX "star-cpr (\x1b[1m\x1b[32minfo\x1b[0m): " + #define MSG_ERROR_PREFIX "star-cpr (\x1b[1m\x1b[31merror\x1b[0m): " + #define MSG_WARNING_PREFIX "star-cpr (\x1b[1m\x1b[33mwarning\x1b[0m): " +#else + #define MSG_INFO_PREFIX "star-cpr (info): " + #define MSG_ERROR_PREFIX "star-cpr (error): " + #define MSG_WARNING_PREFIX "star-cpr (warning): " +#endif + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +device_release(ref_T* ref) +{ + struct scpr_device* device; + struct mem_allocator* allocator; + ASSERT(ref); + device = CONTAINER_OF(ref, struct scpr_device, ref); + allocator = device->allocator; + MEM_RM(allocator, device); +} + +int INLINE +is_in_range + (const double x, + const double range[2]) +{ + ASSERT(range); + return range[0] <= x && x <= range[1]; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +scpr_device_create + (const struct scpr_device_create_args* args, + struct scpr_device** out_device) +{ + struct scpr_device* device = NULL; + struct mem_allocator* allocator = NULL; + res_T res = RES_OK; + + if(!args || !out_device || args->precision < 0 || args->precision > 8) { + res = RES_BAD_ARG; + goto error; + } + + allocator = args->allocator ? args->allocator : &mem_default_allocator; + device = (struct scpr_device*)MEM_CALLOC(allocator, 1, sizeof(struct scpr_device)); + if(!device) { + res = RES_MEM_ERR; + goto error; + } + ref_init(&device->ref); + device->allocator = allocator; + device->logger = args->logger ? args->logger : LOGGER_DEFAULT; + device->precision = args->precision; + /* to optimize scaling / descaling precision + * set the scale to a power of double's radix (2) (#25) */ + device->scale = pow(2, (int)log2(pow(10, device->precision)) + 1); + device->inv_scale = 1/device->scale; + device->range[1] = pow(2, 46 - device->precision); + device->range[0] = -device->range[1]; + +exit: + if(out_device) *out_device = device; + return res; + +error: + if(device) { + SCPR(device_ref_put(device)); + device = NULL; + } + goto exit; +} + +res_T +scpr_device_ref_get + (struct scpr_device* device) +{ + if(!device) return RES_BAD_ARG; + ref_get(&device->ref); + return RES_OK; +} + +res_T +scpr_device_ref_put + (struct scpr_device* device) +{ + if(!device) return RES_BAD_ARG; + ref_put(&device->ref, device_release); + return RES_OK; +} + +res_T +scpr_device_get_range + (const struct scpr_device* dev, + double range[2]) +{ + if(!dev || !range) return RES_BAD_ARG; + d2_set(range, dev->range); + return RES_OK; +} + +res_T +scpr_device_in_range + (const struct scpr_device* dev, + const double* values, + const size_t count, + int* in_range) + +{ + size_t i; + int in = 1; + if(!dev || !values || !in_range) return RES_BAD_ARG; + for(i = 0; i < count; i++) { + if(!is_in_range(values[i], dev->range)) { + in = 0; + break; + } + } + *in_range = in; + return RES_OK; +} + +res_T +scpr_device_scale + (const struct scpr_device* dev, + const double* values, + const size_t count, + int64_t* scaled) +{ + size_t i; + if(!dev || !values || !scaled) return RES_BAD_ARG; + for(i = 0; i < count; i++) { + if(!is_in_range(values[i], dev->range)) return RES_BAD_ARG; + scaled[i] = std::llround(values[i] * dev->scale); + } + return RES_OK; +} + +res_T +scpr_device_unscale + (const struct scpr_device* dev, + const int64_t* values, + const size_t count, + double* unscaled) +{ + size_t i; + if(!dev || !values || !unscaled) return RES_BAD_ARG; + for(i = 0; i < count; i++) { + unscaled[i] = (double)values[i] * dev->inv_scale; + } + return RES_OK; +} diff --git a/src/scpr_intersector.c b/src/scpr_intersector.c @@ -0,0 +1,802 @@ +/* Copyright (C) 2016-2018, 2021 |Meso|Star> (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "scpr.h" +#include "scpr_c.h" + +#include <polygon.h> +#include <rsys/logger.h> +#include <rsys/ref_count.h> +#include <rsys/mem_allocator.h> +#include <rsys/rsys.h> +#include <rsys/double2.h> +#include <rsys/double3.h> + +#undef PI +#include <clipper2/clipper.h> + +#include <new> +#include <stdlib.h> +#include <math.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +init_dimension + (struct mem_allocator* allocator, + struct intersector_dimension* dim, + enum segment_interaction overlap, + enum segment_interaction share) +{ + ASSERT(dim && allocator); + htable_range_point_idx_init(allocator, &dim->unsorted); + darray_range_points_init(allocator, &dim->sorted); + dim->range[0] = INT64_MAX; + dim->range[1] = INT64_MIN; + dim->overlap= overlap; + dim->contact = share; +} + +static void +release_dimension + (struct intersector_dimension* dim) +{ + ASSERT(dim); + htable_range_point_idx_release(&dim->unsorted); + darray_range_points_release(&dim->sorted); +} + +static void +intersector_release(ref_T* ref) +{ + struct scpr_intersector* intersector; + struct mem_allocator* allocator; + ASSERT(ref); + intersector = CONTAINER_OF(ref, struct scpr_intersector, ref); + allocator = intersector->device->allocator; + darray_segments_release(&intersector->segments); + release_dimension(intersector->dimensions); + release_dimension(intersector->dimensions + 1); + SCPR(device_ref_put(intersector->device)); + htable_interacting_segments_release(&intersector->interacting_segments); + htable_registered_components_release(&intersector->registererd_components); + darray_components_release(&intersector->components); + MEM_RM(allocator, intersector); +} + +/* Register a point (a segment end) against a dimension */ +static res_T +register_point + (struct scpr_intersector* intersector, + const int dim_idx, + const int64_t coord, + const uint32_t seg_idx) +{ + uint32_t* pidx; + uint32_t rp_idx; + struct intersector_dimension* dim; + struct intersector_range_point* sorted; + res_T res = RES_OK; + ASSERT(intersector); + + dim = intersector->dimensions + dim_idx; + pidx = htable_range_point_idx_find(&dim->unsorted, &coord); + if(!pidx) { + /* First time this point: create it in sorted and store its index in unsorted */ + char one = 1; + size_t count = darray_range_points_size_get(&dim->sorted); + ERR(darray_range_points_resize(&dim->sorted, 1+count)); + sorted = darray_range_points_data_get(&dim->sorted); + rp_idx = (uint32_t)count; + sorted[rp_idx].coord = coord; + /* Register this segment at this range point */ + ERR(htable_segment_idx_set(&sorted[rp_idx].segments, &seg_idx, &one)); + /* Update range */ + if(dim->range[0] > coord) dim->range[0] = coord; + if(dim->range[1] < coord) dim->range[1] = coord; + } else { + /* This point is already known: look for seg_idx already there */ + char *found, one_or_two; + rp_idx = *pidx; + sorted = darray_range_points_data_get(&dim->sorted); + found = htable_segment_idx_find(&sorted[rp_idx].segments, &seg_idx); + one_or_two = found ? 2 : 1; + ERR(htable_segment_idx_set(&sorted[rp_idx].segments, &seg_idx, &one_or_two)); + } + ERR(htable_range_point_idx_set(&dim->unsorted, &coord, &rp_idx)); + +exit: + return res; +error: + goto exit; +} + +/* Compare 2 range_points */ +static int +compare_range_points(const void* a, const void* b) +{ + struct intersector_range_point *pa = (struct intersector_range_point*)a; + struct intersector_range_point *pb = (struct intersector_range_point*)b; + ASSERT(a && b && pa->coord != pb->coord); + return (int)(pa->coord - pb->coord); +} + +/* Sort the range points by increasing coordinates */ +static void +sort_points + (struct intersector_dimension* dim) +{ + size_t count; + struct intersector_range_point* sorted; + ASSERT(dim); + + /* All the range points are already in sorted; just need to sort them */ + count = darray_range_points_size_get(&dim->sorted); + ASSERT(count == htable_range_point_idx_size_get(&dim->unsorted)); + sorted = darray_range_points_data_get(&dim->sorted); + qsort(sorted, count, sizeof(*sorted), compare_range_points); +} + +/* Create a segment pair suitable for use as an htable key + * To ensure unicity, pairs are ordered */ +static void +init_segment_pair + (struct intersector_segment_pair* pair, + const uint32_t seg_idx1, + const uint32_t seg_idx2) +{ + ASSERT(pair); + pair->rank1 = seg_idx1; + pair->rank2 = seg_idx2; + if(pair->rank1 > pair->rank2) { + SWAP(uint32_t, pair->rank1, pair->rank2); + } +} + +#ifndef NDEBUG +static int +segments_bbox_interacts_1_core + (int64_t lower1, int64_t upper1, int64_t lower2, int64_t upper2) +{ + if(lower1 > upper2) return 0; + if(lower2 > upper1) return 0; + return 1; +} + +/* Check if 2 polygon Bbox interact */ +static int +segments_bbox_interacts + (Clipper2Lib::Point64 *v11, + Clipper2Lib::Point64 *v12, + Clipper2Lib::Point64 *v21, + Clipper2Lib::Point64 *v22) +{ + int64_t lower1[2], upper1[2], lower2[2], upper2[2]; + ASSERT(v11 && v12 && v21 && v22); + lower1[0] = MMIN(v11->x, v12->x); + lower1[1] = MMIN(v11->y, v12->y); + lower2[0] = MMIN(v21->x, v22->x); + lower2[1] = MMIN(v21->y, v22->y); + upper1[0] = MMAX(v11->x, v12->x); + upper1[1] = MMAX(v11->y, v12->y); + upper2[0] = MMAX(v21->x, v22->x); + upper2[1] = MMAX(v21->y, v22->y); + if(!segments_bbox_interacts_1_core(lower1[0], upper1[0], lower2[0], upper2[0])) + return 0; + return segments_bbox_interacts_1_core(lower1[1], upper1[1], lower2[1], upper2[1]); +} + +static int +segments_bbox_interacts_1 + (uint32_t seg_idx1, + uint32_t seg_idx2, + enum segment_interaction inter, + struct scpr_intersector* intersector) +{ + const struct intersector_segment *segments, *seg1, *seg2; + Clipper2Lib::Point64 *v11, * v12, *v21, *v22; + int64_t lower1, upper1, lower2, upper2; + size_t count, r11, r12, r21, r22; + const struct intersector_component* components; + struct scpr_polygon *polygon1, *polygon2; + uint32_t comp_idx1, comp_idx2; + + segments = darray_segments_cdata_get(&intersector->segments); + seg1 = segments + seg_idx1; + seg2 = segments + seg_idx2; + components = darray_components_cdata_get(&intersector->components); + comp_idx1 = components[seg1->component].component; + comp_idx2 = components[seg2->component].component; + polygon1 = components[seg1->component].polygon; + polygon2 = components[seg2->component].polygon; + + /* Get vertices */ + r11 = seg1->first_vertex; + SCPR(polygon_get_vertices_count(polygon1, comp_idx1, &count)); + r12 = (r11 + 1) % count; + r21 = seg2->first_vertex; + SCPR(polygon_get_vertices_count(polygon2, comp_idx2, &count)); + r22 = (r21 + 1) % count; + v11 = &polygon1->paths[comp_idx1][r11]; + v12 = &polygon1->paths[comp_idx1][r12]; + v21 = &polygon2->paths[comp_idx2][r21]; + v22 = &polygon2->paths[comp_idx2][r22]; + if(inter & SOMETHING_X) { + lower1 = MMIN(v11->x, v12->x); + upper1 = MMAX(v11->x, v12->x); + lower2 = MMIN(v21->x, v22->x); + upper2 = MMAX(v21->x, v22->x); + } else { + lower1 = MMIN(v11->y, v12->y); + upper1 = MMAX(v11->y, v12->y); + lower2 = MMIN(v21->y, v22->y); + upper2 = MMAX(v21->y, v22->y); + } + return segments_bbox_interacts_1_core(lower1, upper1, lower2, upper2); +} +#endif + +/* Register interaction of 2 segments along 1 dimension */ +static res_T +register_segment_interaction + (const uint32_t seg_idx1, + const uint32_t seg_idx2, + const int allow_pair_creation, + const enum segment_interaction inter, + struct scpr_intersector* intersector) +{ + res_T res = RES_OK; + struct intersector_segment_pair pair; + unsigned char *pinteract; + + ASSERT(intersector); + if(seg_idx1 != seg_idx2) { + ASSERT(segments_bbox_interacts_1(seg_idx1, seg_idx2, inter, intersector)); + + /* Fill a record for this pair of segments */ + init_segment_pair(&pair, seg_idx1, seg_idx2); + pinteract = + htable_interacting_segments_find(&intersector->interacting_segments, &pair); + if(pinteract || allow_pair_creation) { + unsigned char interact; + if(!pinteract) { /* First occurence of this pair: create empty record */ + interact = (unsigned char)inter; + } else { + ASSERT((*pinteract | inter) < UCHAR_MAX); + interact = (unsigned char)(*pinteract | inter); + } + + /* Register this interaction */ + ERR(htable_interacting_segments_set(&intersector->interacting_segments, + &pair, &interact)); + } + } + +exit: + return res; +error: + goto exit; +} + +/* Register interaction of segment of seg_idx with all the segments in + * open_segments along 1 dimension */ +static res_T +register_segment_interactions + (const uint32_t seg_idx, + struct htable_segment_idx* open_segments, + const int allow_pair_creation, + const enum segment_interaction inter, + struct scpr_intersector* intersector) +{ + res_T res = RES_OK; + struct htable_segment_idx_iterator it, end; + + ASSERT(open_segments && intersector); + + htable_segment_idx_begin(open_segments, &it); + htable_segment_idx_end(open_segments, &end); + /* Loop on segments using this point */ + while(!htable_segment_idx_iterator_eq(&it, &end)) { + uint32_t seg2_idx = *htable_segment_idx_iterator_key_get(&it); + ERR(register_segment_interaction(seg_idx, seg2_idx, allow_pair_creation, + inter, intersector)); + htable_segment_idx_iterator_next(&it); + } + +exit: + return res; +error: + goto exit; +} + +/* Register segments interaction along 1 dimension */ +static res_T +register_interaction + (const int dim_idx, + struct scpr_intersector* intersector) +{ + res_T res = RES_OK; + size_t count, i; + struct intersector_range_point* points; + struct htable_segment_idx open_segments; + struct mem_allocator* allocator; + struct intersector_dimension* dimension; + char *begins = NULL, *ends = NULL; + uint32_t* seg_index = NULL; + ASSERT(intersector); + + allocator = intersector->device->allocator; + dimension = intersector->dimensions + dim_idx; + htable_segment_idx_init(allocator, &open_segments); + count = darray_range_points_size_get(&dimension->sorted); + points = darray_range_points_data_get(&dimension->sorted); + for(i = 0; i < count; i++) { + struct intersector_range_point* point = points + i; + struct htable_segment_idx_iterator it, end; + size_t scount, s; + + /* 0) Init begins, ends and seg_index + * Cannot recompute them on the fly as found will change */ + scount = htable_segment_idx_size_get(&point->segments); + begins = (char*)MEM_REALLOC(allocator, begins, scount * sizeof(*begins)); + ends = (char*)MEM_REALLOC(allocator, ends, scount * sizeof(*ends)); + seg_index = (uint32_t*)MEM_REALLOC(allocator, seg_index, + scount * sizeof(*seg_index)); + if(!begins || !ends || !seg_index) { + res = RES_MEM_ERR; + goto error; + } + s = 0; + htable_segment_idx_begin(&point->segments, &it); + htable_segment_idx_end(&point->segments, &end); + while(!htable_segment_idx_iterator_eq(&it, &end)) { + char c = *htable_segment_idx_iterator_data_get(&it); + uint32_t seg_idx = *htable_segment_idx_iterator_key_get(&it); + if(c == 2) { /* Both ends of the segment at this point */ + ends[s] = begins[s] = 1; + } else { + char* found = htable_segment_idx_find(&open_segments, &seg_idx); + begins[s] = (found == NULL); + ends[s] = (found != NULL); + } + seg_index[s] = seg_idx; + s++; + htable_segment_idx_iterator_next(&it); + } + ASSERT(s == scount); + + /* 1) Segments beginning and ending at this point overlap other segments that + * begin and end at the same point and contact segments that only begin or + * only end at this point */ + for(s = 0; s < scount; s++) { + if(begins[s] && ends[s]) { + size_t j; + for(j = 0; j < scount; j++) { + if(j > s && begins[j] && ends[j]) { + ERR(register_segment_interaction(seg_index[s], seg_index[j], !dim_idx, + dimension->overlap, intersector)); + } + else if(begins[j] ^ ends[j]) { + ERR(register_segment_interaction(seg_index[s], seg_index[j], !dim_idx, + dimension->contact, intersector)); + } + } + } + } + + /* 2) Segments only ending at this point overlap open segments */ + for(s = 0; s < scount; s++) { + if(!begins[s] && ends[s]) { + ERR(register_segment_interactions(seg_index[s], &open_segments, !dim_idx, + dimension->overlap, intersector)); + } + } + + /* 3) Segments ending at this point are now closed */ + for(s = 0; s < scount; s++) { + if(ends[s]) { + size_t n = htable_segment_idx_erase(&open_segments, seg_index + s); + CHK(n == (begins[s] ? 0 : 1)); + } + } + + /* 4) Segments beginning and ending at this point overlap open segments */ + for(s = 0; s < scount; s++) { + if(begins[s] && ends[s]) { + ERR(register_segment_interactions(seg_index[s], &open_segments, !dim_idx, + dimension->overlap, intersector)); + } + } + + /* 5) Segments only beginning at this point are now open */ + for(s = 0; s < scount; s++) { + if(begins[s] && !ends[s]) { + char one = 1; + ERR(htable_segment_idx_set(&open_segments, seg_index + s, &one)); + } + } + + /* 6) Segments only beginning at this point overlap open segments */ + for(s = 0; s < scount; s++) { + if(begins[s] && !ends[s]) { + ERR(register_segment_interactions(seg_index[s], &open_segments, !dim_idx, + dimension->overlap, intersector)); + } + } + } + +exit: + MEM_RM(allocator, begins); + MEM_RM(allocator, ends); + MEM_RM(allocator, seg_index); + htable_segment_idx_release(&open_segments); + return res; +error: + goto exit; +} + +static int64_t +safe_mul + (int64_t a, + int64_t b, + int* ovflw) +{ + if(*ovflw) return 0; + if(b != 0 && abs(a) > 1 + (INT64_MAX / abs(b))) goto error; + return a * b; +error: + *ovflw = 1; + return 0; +} + +/* Check if a triangle is CCW (>0), CW (<0), or flat (=0) */ +static int +is_ccw + (Clipper2Lib::Point64* v1, + Clipper2Lib::Point64* v2, + Clipper2Lib::Point64* v3, + int* ovflw) +{ + int64_t tmp1, tmp2; + ASSERT(v1 && v2 && v3); + /* Limited coordinate range garanties that sub computations stay in int64_t + * range */ + tmp1 = safe_mul(v2->x - v1->x, v3->y - v1->y, ovflw); + tmp2 = safe_mul(v3->x - v1->x, v2->y - v1->y, ovflw); + if(tmp1 < tmp2) return -1; + if(tmp1 > tmp2) return +1; + return 0; +} + +/* Check if 2 segments intersect */ +static res_T +check_two_segments_intersection + (const struct intersector_segment* seg1, + const struct intersector_segment* seg2, + struct scpr_intersector_check_callbacks* callbacks, + struct scpr_intersector* intersector, + void* data) +{ + res_T res = RES_OK; + size_t count, r11, r12, r21, r22; + Clipper2Lib::Point64 *v11, * v12, *v21, *v22; + int ccw1, ccw2, ccw3, ccw4, ovflw = 0; + const struct intersector_component* components; + struct scpr_polygon *polygon1, *polygon2; + uint32_t comp_idx1, comp_idx2; + + ASSERT(seg1 && seg2 && callbacks); + + components = darray_components_cdata_get(&intersector->components); + comp_idx1 = components[seg1->component].component; + comp_idx2 = components[seg2->component].component; + polygon1 = components[seg1->component].polygon; + polygon2 = components[seg2->component].polygon; + + /* Get vertices */ + r11 = seg1->first_vertex; + ERR(scpr_polygon_get_vertices_count(polygon1, comp_idx1, &count)); + r12 = (r11 + 1) % count; + r21 = seg2->first_vertex; + ERR(scpr_polygon_get_vertices_count(polygon2, comp_idx2, &count)); + r22 = (r21 + 1) % count; + v11 = &polygon1->paths[comp_idx1][r11]; + v12 = &polygon1->paths[comp_idx1][r12]; + v21 = &polygon2->paths[comp_idx2][r21]; + v22 = &polygon2->paths[comp_idx2][r22]; + ASSERT(segments_bbox_interacts(v11, v12, v21, v22)); + + /* Test triangles orientation */ + ccw1 = is_ccw(v11, v12, v21, &ovflw); + ccw2 = is_ccw(v11, v12, v22, &ovflw); + ccw3 = is_ccw(v21, v22, v11, &ovflw); + ccw4 = is_ccw(v21, v22, v12, &ovflw); + if(ovflw) { + res = RES_BAD_ARG; + goto error; + } + /* Analyse ccw results */ + if(ccw1 * ccw2 > 0 && ccw3 * ccw4 > 0) { + /* No intersection at all */ + } + else if(ccw1 == 0 && ccw2 == 0 && ccw3 == 0 && ccw4 == 0) { + /* collinear segments */ + struct scpr_callback_segment arg1, arg2; + arg1.polygon = polygon1; + arg1.component = comp_idx1; + arg1.first_vertex = r11; + arg1.last_vertex = r12; + arg2.polygon = polygon2; + arg2.component = comp_idx2; + arg2.first_vertex = r21; + arg2.last_vertex = r22; + if(callbacks->collinear_segments + && callbacks->collinear_segments(&arg1, &arg2, data)) + { + res = RES_BAD_ARG; + goto error; + } + } + else if(ccw1 * ccw2 < 0 && ccw3 * ccw4 < 0) { + /* Basic segment intersection */ + struct scpr_callback_segment arg1, arg2; + arg1.polygon = polygon1; + arg1.component = comp_idx1; + arg1.first_vertex = r11; + arg1.last_vertex = r12; + arg2.polygon = polygon2; + arg2.component = comp_idx2; + arg2.first_vertex = r21; + arg2.last_vertex = r22; + if(callbacks->simple_intersection + && callbacks->simple_intersection(&arg1, &arg2, data)) + { + res = RES_BAD_ARG; + goto error; + } +} + +exit: + return res; +error: + goto exit; +} + +/* Test intersection of all the registered pairs of interacting segments */ +static res_T +check_interacting_pairs + (struct scpr_intersector* intersector, + struct scpr_intersector_check_callbacks* callbacks, + void* data) +{ + res_T res = RES_OK; + struct htable_interacting_segments_iterator it, end; + const struct intersector_segment* segments; + + ASSERT(intersector && callbacks); + + segments = darray_segments_cdata_get(&intersector->segments); + htable_interacting_segments_begin(&intersector->interacting_segments, &it); + htable_interacting_segments_end(&intersector->interacting_segments, &end); + /* Loop on interacting segments */ + while(!htable_interacting_segments_iterator_eq(&it, &end)) { + unsigned char overlapping + = *htable_interacting_segments_iterator_data_get(&it); + if(overlapping & SOME_OVERLAP + && overlapping & SOMETHING_X && overlapping & SOMETHING_Y) + { + struct intersector_segment_pair* pair + = htable_interacting_segments_iterator_key_get(&it); + const struct intersector_segment* seg1 = segments + pair->rank1; + const struct intersector_segment* seg2 = segments + pair->rank2; + ERR(check_two_segments_intersection(seg1, seg2, callbacks, intersector, data)); + } + htable_interacting_segments_iterator_next(&it); + } + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +scpr_intersector_create + (struct scpr_device* device, + struct scpr_intersector** out_intersector) +{ + res_T res = RES_OK; + struct scpr_intersector* intersector = NULL; + struct mem_allocator* allocator; + + if(!device || ! out_intersector) { + res = RES_BAD_ARG; + goto error; + } + + allocator = device->allocator; + intersector = (struct scpr_intersector*)MEM_ALLOC(allocator, sizeof(*intersector)); + if(!intersector) { + res = RES_MEM_ERR; + goto error; + } + ref_init(&intersector->ref); + intersector->device = device; + ERR(scpr_device_ref_get(device)); + darray_segments_init(allocator, &intersector->segments); + init_dimension(allocator, intersector->dimensions, OVERLAP_X, CONTACT_X); + init_dimension(allocator, intersector->dimensions + 1, OVERLAP_Y, CONTACT_Y); + htable_interacting_segments_init(allocator, &intersector->interacting_segments); + htable_registered_components_init(allocator, &intersector->registererd_components); + darray_components_init(allocator, &intersector->components); + +exit: + if(out_intersector) *out_intersector = intersector; + return res; +error: + if(intersector) { + SCPR(intersector_ref_put(intersector)); + intersector = NULL; + } + goto exit; +} + +res_T +scpr_intersector_ref_get + (struct scpr_intersector* intersector) +{ + if(!intersector) return RES_BAD_ARG; + ref_get(&intersector->ref); + return RES_OK; +} + +res_T +scpr_intersector_ref_put + (struct scpr_intersector* intersector) +{ + if(!intersector) return RES_BAD_ARG; + ref_put(&intersector->ref, intersector_release); + return RES_OK; +} + +res_T +scpr_intersector_register_polygon + (struct scpr_intersector* intersector, + struct scpr_polygon* polygon) +{ + res_T res = RES_OK; + size_t i; + + if(!intersector || !polygon) { + res = RES_BAD_ARG; + goto error; + } + + for(i = 0; i < polygon->paths.size(); i++) { + ERR(scpr_intersector_register_component(intersector, polygon, i)); + } + +exit: + return res; +error: + goto exit; +} + +res_T +scpr_intersector_register_component + (struct scpr_intersector* intersector, + struct scpr_polygon* polygon, + const size_t icomponent) +{ + res_T res = RES_OK; + uint32_t i; + size_t new_count, prev_count, c; + struct intersector_component comp; + char *found, one = 1; + + if(!intersector || !polygon || icomponent >= polygon->paths.size()) { + res = RES_BAD_ARG; + goto error; + } + + /* Check single registration */ + comp.polygon = polygon; + comp.component = (uint32_t)icomponent; + found = htable_registered_components_find(&intersector->registererd_components, + &comp); + if(found) { + logger_print(intersector->device->logger, LOG_ERROR, + "Component %zu already registered.\n", icomponent); + res = RES_BAD_ARG; + goto error; + } + c = htable_registered_components_size_get(&intersector->registererd_components); + if(c >= UINT32_MAX) { + logger_print(intersector->device->logger, LOG_ERROR, + "Too many components registered.\n"); + res = RES_BAD_ARG; + goto error; + } + ASSERT(c == darray_components_size_get(&intersector->components)); + ERR(htable_registered_components_set(&intersector->registererd_components, + &comp, &one)); + ERR(darray_components_push_back(&intersector->components, &comp)); + + prev_count = darray_segments_size_get(&intersector->segments); + ASSERT(prev_count <= UINT32_MAX); + new_count = polygon->paths[icomponent].size(); + if(prev_count + new_count > UINT32_MAX) { + logger_print(intersector->device->logger, LOG_ERROR, + "Too many segments registered.\n"); + res = RES_BAD_ARG; + goto error; + } + ERR(darray_segments_resize(&intersector->segments, + (uint32_t)(prev_count + new_count))); + + for(i = 0; i < new_count; i++) { + Clipper2Lib::Point64 *p0, *p1; + uint32_t seg_idx = (uint32_t)(prev_count + i); + struct intersector_segment* segment + = darray_segments_data_get(&intersector->segments) + seg_idx; + segment->component = (uint32_t)c; + segment->first_vertex = i; + p0 = &polygon->paths[icomponent][i]; + p1 = &polygon->paths[icomponent][(i+1)%new_count]; + ERR(register_point(intersector, 0, p0->x, seg_idx)); + ERR(register_point(intersector, 0, p1->x, seg_idx)); + ERR(register_point(intersector, 1, p0->y, seg_idx)); + ERR(register_point(intersector, 1, p1->y, seg_idx)); + } + +exit: + return res; +error: + goto exit; +} + +res_T +scpr_intersector_check + (struct scpr_intersector* intersector, + struct scpr_intersector_check_callbacks* callbacks, + void* data) +{ + int i; + res_T res = RES_OK; + + if(!intersector || !callbacks + || (!callbacks->simple_intersection && !callbacks->collinear_segments)) + { + res = RES_BAD_ARG; + goto error; + } + + /* Construct candidates for intersection */ + for(i = 0; i < 2; i++) { + sort_points(&intersector->dimensions[i]); + ERR(register_interaction(i, intersector)); + } + /* Check intersections */ + ERR(check_interacting_pairs(intersector, callbacks, data)); + +exit: + return res; +error: + goto exit; +} diff --git a/src/scpr_mesh.c b/src/scpr_mesh.c @@ -17,6 +17,7 @@ #include "scpr_c.h" #include <rsys/mem_allocator.h> +#include <rsys/logger.h> #include <polygon.h> #undef PI @@ -39,10 +40,10 @@ scpr_operation_to_clip_type(const enum scpr_operation op) static INLINE int aabb_intersect - (const double lower0[2], - const double upper0[2], - const double lower1[2], - const double upper1[2]) + (const int64_t lower0[2], + const int64_t upper0[2], + const int64_t lower1[2], + const int64_t upper1[2]) { ASSERT(lower0 && upper0 && lower1 && upper1); return lower0[0] < upper1[0] @@ -52,7 +53,7 @@ aabb_intersect } static INLINE int -aabb_is_degenerated(const double lower[2], const double upper[2]) +aabb_is_degenerated(const int64_t lower[2], const int64_t upper[2]) { ASSERT(lower && upper); return lower[0] >= upper[0] || lower[1] >= upper[1]; @@ -60,54 +61,58 @@ aabb_is_degenerated(const double lower[2], const double upper[2]) static void triangle_compute_aabb - (double tri[3][2], - double lower[2], - double upper[2]) + (int64_t tri[3][2], + int64_t lower[2], + int64_t upper[2]) { size_t ivert; ASSERT(tri && lower && upper); - d2_splat(lower, DBL_MAX); - d2_splat(upper,-DBL_MAX); + lower[0] = lower[1] = INT64_MAX; + upper[0] = upper[1] = INT64_MIN; FOR_EACH(ivert, 0, 3) { - d2_min(lower, lower, tri[ivert]); - d2_max(upper, upper, tri[ivert]); + int i; + for(i = 0; i < 2; i++) { + if(lower[i] > tri[ivert][i]) lower[i] = tri[ivert][i]; + if(upper[i] < tri[ivert][i]) upper[i] = tri[ivert][i]; + } } } static res_T register_vertex - (const double pos[2], - struct darray_double* coords, - struct darray_size_t* indices, + (const int64_t pos[2], + struct darray_int64* coords, + struct darray_uint32* indices, struct htable_vertex* vertices) { struct vertex v; - size_t* pid, id; + uint32_t* pid, id; + unsigned i; res_T res = RES_OK; ASSERT(pos && coords && indices && vertices); - d2_set(v.pos, pos); + for(i = 0; i < 2; i++) v.pos[i] = pos[i]; - /* FIXME dirty hack */ - v.pos[0] = (float)v.pos[0]; - v.pos[1] = (float)v.pos[1]; + v.pos[0] = v.pos[0]; + v.pos[1] = v.pos[1]; pid = htable_vertex_find(vertices, &v); if(pid) { id = *pid; } else { - const size_t ivert = darray_double_size_get(coords); + const size_t count = darray_int64_size_get(coords); - ERR(darray_double_resize(coords, ivert+2/*#coords*/)); - d2_set(darray_double_data_get(coords)+ivert, pos); + ASSERT(count <= UINT32_MAX); + ERR(darray_int64_resize(coords, count+2/*#coords*/)); + for(i = 0; i < 2; i++) darray_int64_data_get(coords)[count+i] = pos[i]; - id = ivert / 2; + id = (uint32_t)count / 2; ERR(htable_vertex_set(vertices, &v, &id)); } - ERR(darray_size_t_push_back(indices, &id)); + ERR(darray_uint32_push_back(indices, &id)); exit: return res; @@ -117,9 +122,9 @@ error: static FINLINE res_T register_triangle - (double tri[3][2], - struct darray_double* coords, /* Vertex buffer */ - struct darray_size_t* indices, /* Index buffer */ + (int64_t tri[3][2], + struct darray_int64* coords, /* Vertex buffer */ + struct darray_uint32* indices, /* Index buffer */ struct htable_vertex* vertices) /* Map a vertex to its index */ { size_t ivert; @@ -137,11 +142,12 @@ error: static res_T register_paths - (const Clipper2Lib::PathsD& paths, - struct darray_double* coords, /* Vertex buffer */ - struct darray_size_t* indices, /* Index buffer */ + (struct scpr_device* dev, + const Clipper2Lib::Paths64& paths, + struct darray_int64* coords, /* Vertex buffer */ + struct darray_uint32* indices, /* Index buffer */ struct htable_vertex* vertices, /* Map a vertex to its index */ - struct polygon* polygon) /* Use to triangulate the clipped polygons */ + struct polygon* polygon) /* Used to triangulate the clipped polygons */ { size_t ivert; size_t ipath; @@ -151,7 +157,7 @@ register_paths FOR_EACH(ipath, 0, paths.size()) { if(paths[ipath].size() == 3) { FOR_EACH(ivert, 0, 3) { - double pos[2]; + int64_t pos[2]; pos[0] = paths[ipath][ivert].x; pos[1] = paths[ipath][ivert].y; ERR(register_vertex(pos, coords, indices, vertices)); @@ -165,23 +171,27 @@ register_paths POLYGON(clear(polygon)); FOR_EACH(ivert, 0, paths[ipath].size()) { float fpos[3] = {0.f, 0.f, 0.f}; - double pos[2]; + double posd[2]; + int64_t pos64[2]; - pos[0] = paths[ipath][ivert].x; - pos[1] = paths[ipath][ivert].y; + pos64[0] = paths[ipath][ivert].x; + pos64[1] = paths[ipath][ivert].y; + SCPR(device_unscale(dev, pos64, 2, posd)); - fpos[0] = (float)pos[0], fpos[1] = (float)pos[1]; + fpos[0] = (float)posd[0], fpos[1] = (float)posd[1]; ERR(polygon_vertex_add(polygon, fpos)); } ERR(polygon_triangulate(polygon, &ids, &nids)); FOR_EACH(ivert, 0, nids) { float fpos[3]; + int64_t pos64[2]; double pos[2]; POLYGON(vertex_get(polygon, ids[ivert], fpos)); - pos[0] = (float)fpos[0]; - pos[1] = (float)fpos[1]; - ERR(register_vertex(pos, coords, indices, vertices)); + pos[0] = (double)fpos[0]; + pos[1] = (double)fpos[1]; + SCPR(device_scale(dev, pos, 2, pos64)); + ERR(register_vertex(pos64, coords, indices, vertices)); } } } @@ -192,22 +202,29 @@ error: } static void -mesh_compute_aabb(const struct scpr_mesh* mesh, double lower[2], double upper[2]) +mesh_compute_aabb + (const struct scpr_mesh* mesh, + int64_t lower[2], + int64_t upper[2]) { - size_t itri, ntris; + size_t itri, ntris, i; SCPR(mesh_get_triangles_count(mesh, &ntris)); - d2_splat(lower, DBL_MAX); - d2_splat(upper,-DBL_MAX); + lower[0] = lower[1] = INT64_MAX; + upper[0] = upper[1] = INT64_MIN; FOR_EACH(itri, 0, ntris) { size_t ids[3], ivert; SCPR(mesh_get_indices(mesh, itri, ids)); FOR_EACH(ivert, 0, 3) { double pos[2]; + int64_t pos64[2]; SCPR(mesh_get_position(mesh, ids[ivert], pos)); - d2_min(lower, lower, pos); - d2_max(upper, upper, pos); + SCPR(device_scale(mesh->device, pos, 2, pos64)); + for(i = 0; i < 2; i++) { + if(lower[i] > pos64[i]) lower[i] = pos64[i]; + if(upper[i] < pos64[i]) upper[i] = pos64[i]; + } } } } @@ -216,39 +233,43 @@ static void mesh_release(ref_T* ref) { struct scpr_mesh* mesh; + struct mem_allocator* allocator; ASSERT(ref); mesh = CONTAINER_OF(ref, struct scpr_mesh, ref); - darray_double_release(&mesh->coords); - darray_size_t_release(&mesh->indices); - MEM_RM(mesh->allocator, mesh); + allocator = mesh->device->allocator; + SCPR(device_ref_put(mesh->device)); + darray_int64_release(&mesh->coords); + darray_uint32_release(&mesh->indices); + MEM_RM(allocator, mesh); } /******************************************************************************* * Exported functions ******************************************************************************/ res_T -scpr_mesh_create(struct mem_allocator* mem_allocator, struct scpr_mesh** out_mesh) +scpr_mesh_create + (struct scpr_device* dev, + struct scpr_mesh** out_mesh) { struct scpr_mesh* mesh = NULL; - struct mem_allocator* allocator = NULL; res_T res = RES_OK; - if(!out_mesh) { + if(!dev || !out_mesh) { res = RES_BAD_ARG; goto error; } - allocator = mem_allocator ? mem_allocator : &mem_default_allocator; mesh = (struct scpr_mesh*) - MEM_CALLOC(allocator, 1, sizeof(struct scpr_mesh)); + MEM_CALLOC(dev->allocator, 1, sizeof(struct scpr_mesh)); if(!mesh) { res = RES_MEM_ERR; goto error; } - darray_double_init(mesh->allocator, &mesh->coords); - darray_size_t_init(mesh->allocator, &mesh->indices); ref_init(&mesh->ref); - mesh->allocator = allocator; + mesh->device = dev; + SCPR(device_ref_get(dev)); + darray_int64_init(dev->allocator, &mesh->coords); + darray_uint32_init(dev->allocator, &mesh->indices); exit: if(out_mesh) *out_mesh = mesh; @@ -287,9 +308,9 @@ scpr_mesh_setup_indexed_vertices void (*get_position)(const size_t ivert, double pos[2], void* ctx), void* data) { - double* pos = NULL; - size_t* ids = NULL; - size_t i; + int64_t* pos = NULL; + uint32_t* ids = NULL; + size_t i, j; res_T res = RES_OK; if(!mesh || !ntris || !get_indices || !nverts || !get_position || !data) { @@ -297,33 +318,49 @@ scpr_mesh_setup_indexed_vertices 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*/)); + if(ntris > UINT32_MAX) { + logger_print(mesh->device->logger, LOG_ERROR, + "Too many triangles.\n"); + res = RES_BAD_ARG; + goto error; + } + + if(nverts > UINT32_MAX) { + logger_print(mesh->device->logger, LOG_ERROR, + "Too many vertices.\n"); + res = RES_BAD_ARG; + goto error; + } + + ERR(darray_int64_resize(&mesh->coords, nverts*2/*#coords per vertex*/)); + ERR(darray_uint32_resize(&mesh->indices, ntris*3/*#vertices per triangle*/)); /* Fetch mesh positions */ - pos = darray_double_data_get(&mesh->coords); + pos = darray_int64_data_get(&mesh->coords); FOR_EACH(i, 0, nverts) { - get_position(i, pos+i*2, data); + double posd[2]; + get_position(i, posd, data); + ERR(scpr_device_scale(mesh->device, posd, 2, pos + i*2)); } /* Fetch mesh indices */ - ids = darray_size_t_data_get(&mesh->indices); + ids = darray_uint32_data_get(&mesh->indices); FOR_EACH(i, 0, ntris) { - get_indices(i, ids +i*3, data); - if(ids[i*3+0] >= nverts - || ids[i*3+1] >= nverts - || ids[i*3+2] >= nverts) { + size_t tmp[3]; + get_indices(i, tmp, data); + if(tmp[0] >= nverts || tmp[1] >= nverts || tmp[2] >= nverts) { res = RES_BAD_ARG; goto error; } + for(j = 0; j < 3; j++) ids[i*3 + j] = (uint32_t)tmp[j]; } exit: return res; error: if(mesh) { - darray_double_clear(&mesh->coords); - darray_size_t_clear(&mesh->indices); + darray_int64_clear(&mesh->coords); + darray_uint32_clear(&mesh->indices); } goto exit; } @@ -332,8 +369,8 @@ res_T scpr_mesh_get_vertices_count(const struct scpr_mesh* mesh, size_t* nverts) { if(!mesh || !nverts) return RES_BAD_ARG; - ASSERT((darray_double_size_get(&mesh->coords) % 2/*#coords per vertex*/)==0); - *nverts = darray_double_size_get(&mesh->coords) / 2/*#coords per vertex*/; + ASSERT((darray_int64_size_get(&mesh->coords) % 2/*#coords per vertex*/)==0); + *nverts = darray_int64_size_get(&mesh->coords) / 2/*#coords per vertex*/; return RES_OK; } @@ -341,8 +378,8 @@ res_T scpr_mesh_get_triangles_count(const struct scpr_mesh* mesh, size_t* ntris) { if(!mesh || !ntris) return RES_BAD_ARG; - ASSERT((darray_size_t_size_get(&mesh->indices)%3/*#vertices per triangle*/)==0); - *ntris = darray_size_t_size_get(&mesh->indices) / 3/*#vertices per triangle*/; + ASSERT((darray_uint32_size_get(&mesh->indices)%3/*#vertices per triangle*/)==0); + *ntris = darray_uint32_size_get(&mesh->indices) / 3/*#vertices per triangle*/; return RES_OK; } @@ -355,9 +392,9 @@ scpr_mesh_get_indices if(!mesh || !ids) return RES_BAD_ARG; SCPR(mesh_get_triangles_count(mesh, &ntris)); if(itri >= ntris) return RES_BAD_ARG; - ids[0] = darray_size_t_cdata_get(&mesh->indices)[i+0]; - ids[1] = darray_size_t_cdata_get(&mesh->indices)[i+1]; - ids[2] = darray_size_t_cdata_get(&mesh->indices)[i+2]; + ids[0] = darray_uint32_cdata_get(&mesh->indices)[i+0]; + ids[1] = darray_uint32_cdata_get(&mesh->indices)[i+1]; + ids[2] = darray_uint32_cdata_get(&mesh->indices)[i+2]; return RES_OK; } @@ -366,12 +403,14 @@ scpr_mesh_get_position (const struct scpr_mesh* mesh, const size_t ivert, double pos[2]) { size_t nverts; + int64_t pos64[2]; const size_t i = ivert * 2/*#coords per vertex*/; if(!mesh || !pos) return RES_BAD_ARG; SCPR(mesh_get_vertices_count(mesh, &nverts)); if(ivert >= nverts) return RES_BAD_ARG; - pos[0] = darray_double_cdata_get(&mesh->coords)[i+0]; - pos[1] = darray_double_cdata_get(&mesh->coords)[i+1]; + pos64[0] = darray_int64_cdata_get(&mesh->coords)[i+0]; + pos64[1] = darray_int64_cdata_get(&mesh->coords)[i+1]; + SCPR(device_unscale(mesh->device, pos64, 2, pos)); return RES_OK; } @@ -381,28 +420,31 @@ scpr_mesh_clip const enum scpr_operation op, struct scpr_polygon* poly_desc) { - double lower[2], upper[2]; + int64_t 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 darray_int64 coords; /* Coordinates of the clipped mesh */ + struct darray_uint32 indices; /* Indices of the clipped mesh */ struct htable_vertex vertices; /* Map a coordinate to its index */ - Clipper2Lib::ClipperD clipper; - Clipper2Lib::PathsD output; /* Contour of the clipped polgyon */ - Clipper2Lib::PathsD cand_path; /* Contour of the candidate polygon */ - Clipper2Lib::PathD tmp; + Clipper2Lib::Clipper64 clipper; + Clipper2Lib::Paths64 output; /* Contour of the clipped polgyon */ + Clipper2Lib::Paths64 cand_path; /* Contour of the candidate polygon */ + Clipper2Lib::Path64 tmp; Clipper2Lib::ClipType clip_type; /* Type of clipping to perform */ size_t itri, ntris, ivert; + struct mem_allocator* allocator; + int i; res_T res = RES_OK; if(!mesh || !poly_desc || (unsigned)op >= SCPR_OPERATIONS_COUNT__) return RES_BAD_ARG; + allocator = mesh->device->allocator; clip_type = scpr_operation_to_clip_type(op); - darray_double_init(mesh->allocator, &coords); - darray_size_t_init(mesh->allocator, &indices); - htable_vertex_init(mesh->allocator, &vertices); + darray_int64_init(allocator, &coords); + darray_uint32_init(allocator, &indices); + htable_vertex_init(allocator, &vertices); if(aabb_is_degenerated(poly_desc->lower, poly_desc->upper)) goto exit; @@ -410,29 +452,35 @@ scpr_mesh_clip if(aabb_is_degenerated(lower, upper)) goto exit; /* Compute the overall aabb of the candidate and the clip polygon */ - d2_min(lower, lower, poly_desc->lower); - d2_max(upper, upper, poly_desc->upper); + for(i = 0; i < 2; i++) { + if(lower[i] > poly_desc->lower[i]) lower[i] = poly_desc->lower[i]; + if(upper[i] < poly_desc->upper[i]) upper[i] = poly_desc->upper[i]; + } /* Create the polygon structure used to triangulate the clipped polygons */ - ERR(polygon_create(mesh->allocator, &polygon)); + ERR(polygon_create(allocator, &polygon)); /* Clip the triangles of the mesh */ SCPR(mesh_get_triangles_count(mesh, &ntris)); FOR_EACH(itri, 0, ntris) { size_t ids[3]; double tri[3][2]; + int64_t tri64[3][2]; /* Fetch the triangle vertices and compute its AABB */ SCPR(mesh_get_indices(mesh, itri, ids)); SCPR(mesh_get_position(mesh, ids[0], tri[0])); SCPR(mesh_get_position(mesh, ids[1], tri[1])); SCPR(mesh_get_position(mesh, ids[2], tri[2])); - triangle_compute_aabb(tri, lower, upper); + SCPR(device_scale(mesh->device, tri[0], 2, tri64[0])); + SCPR(device_scale(mesh->device, tri[1], 2, tri64[1])); + SCPR(device_scale(mesh->device, tri[2], 2, tri64[2])); + triangle_compute_aabb(tri64, lower, 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) { - ERR(register_triangle(tri, &coords, &indices, &vertices)); + ERR(register_triangle(tri64, &coords, &indices, &vertices)); } continue; } @@ -441,9 +489,9 @@ scpr_mesh_clip cand_path.clear(); tmp.clear(); FOR_EACH(ivert, 0, 3) { - Clipper2Lib::PointD pt; - pt.x = tri[ivert][0]; - pt.y = tri[ivert][1]; + Clipper2Lib::Point64 pt; + pt.x = tri64[ivert][0]; + pt.y = tri64[ivert][1]; tmp.push_back(pt); } cand_path.push_back(tmp); @@ -455,16 +503,16 @@ scpr_mesh_clip clipper.Execute(clip_type, Clipper2Lib::FillRule::EvenOdd, output); /* Register the resulting clipped polygons */ - ERR(register_paths (output, &coords, &indices, &vertices, polygon)); + ERR(register_paths(mesh->device, output, &coords, &indices, &vertices, polygon)); } - ERR(darray_double_copy_and_clear(&mesh->coords, &coords)); - ERR(darray_size_t_copy_and_clear(&mesh->indices, &indices)); + ERR(darray_int64_copy_and_clear(&mesh->coords, &coords)); + ERR(darray_uint32_copy_and_clear(&mesh->indices, &indices)); exit: if(polygon) POLYGON(ref_put(polygon)); - darray_double_release(&coords); - darray_size_t_release(&indices); + darray_int64_release(&coords); + darray_uint32_release(&indices); htable_vertex_release(&vertices); return res; error: diff --git a/src/scpr_polygon.c b/src/scpr_polygon.c @@ -13,32 +13,21 @@ * 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/logger.h> +#include <rsys/ref_count.h> #include <rsys/mem_allocator.h> #include <rsys/rsys.h> -#include <rsys/double2.h> #undef PI #include <clipper2/clipper.h> #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 ******************************************************************************/ @@ -59,22 +48,26 @@ static void polygon_release(ref_T* ref) { struct scpr_polygon* polygon; + struct mem_allocator* allocator; ASSERT(ref); polygon = CONTAINER_OF(ref, struct scpr_polygon, ref); + allocator = polygon->device->allocator; + SCPR(device_ref_put(polygon->device)); /* Call destructor for paths */ - polygon->paths.Clipper2Lib::PathsD::~PathsD(); - MEM_RM(polygon->allocator, polygon); + polygon->paths.Clipper2Lib::Paths64::~Paths64(); + MEM_RM(allocator, polygon); } static int path_is_eq - (const Clipper2Lib::PathD* p1, - const Clipper2Lib::PathD* p2) + (const Clipper2Lib::Path64* p1, + const Clipper2Lib::Path64* p2) { size_t i, first_vtx, sz; int opposite_cw; ASSERT(p1 && p2); sz = p1->size(); + if(sz != p2->size()) { return 0; } @@ -99,8 +92,8 @@ path_is_eq static int one_path_is_eq - (const Clipper2Lib::PathsD* pp, - const Clipper2Lib::PathD* p, + (const Clipper2Lib::Paths64* pp, + const Clipper2Lib::Path64* p, char* matched) { size_t i, sz; @@ -108,7 +101,7 @@ one_path_is_eq sz = pp->size(); FOR_EACH(i, 0, sz) { if(matched[i]) continue; - if(path_is_eq(&pp->at(i), p)) { + if(path_is_eq(&(*pp)[i], p)) { matched[i] = 1; return 1; } @@ -121,31 +114,30 @@ one_path_is_eq ******************************************************************************/ res_T scpr_polygon_create - (struct mem_allocator* mem_allocator, + (struct scpr_device* dev, struct scpr_polygon** out_polygon) { struct scpr_polygon* polygon = NULL; - struct mem_allocator* allocator = NULL; res_T res = RES_OK; - if(!out_polygon) { + if(!dev || !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)); + MEM_CALLOC(dev->allocator, 1, sizeof(struct scpr_polygon)); if(!polygon) { res = RES_MEM_ERR; goto error; } ref_init(&polygon->ref); - polygon->allocator = allocator; + polygon->device = dev; + SCPR(device_ref_get(dev)); /* Allocate paths the C++ way (placement new) */ new (&polygon->paths) Clipper2Lib::PathsD; - d2_splat(polygon->lower, DBL_MAX); - d2_splat(polygon->upper,-DBL_MAX); + polygon->lower[0] = polygon->lower[1] = INT64_MAX; + polygon->upper[0] = polygon->upper[1] = INT64_MIN; exit: if(out_polygon) *out_polygon = polygon; @@ -177,30 +169,19 @@ scpr_polygon_ref_put 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 */ + const size_t ncomponents, void (*get_nverts)(const size_t icomponent, size_t *nverts, void* ctx), void (*get_position) (const size_t icomponent, const size_t ivert, double pos[2], void* ctx), void* data) { size_t c; - double scale = pow(10, PRECISION); + struct scpr_device* dev; + Clipper2Lib::Paths64 paths; + int changedc, changedv = 0; res_T res = RES_OK; if(!polygon || !get_nverts || !get_position || !data) { @@ -208,42 +189,74 @@ scpr_polygon_setup_indexed_vertices goto error; } - TRY(polygon->paths.resize(ncomponents)); + if(ncomponents > UINT32_MAX) { + logger_print(polygon->device->logger, LOG_ERROR, + "Too many components.\n"); + res = RES_BAD_ARG; + goto error; + } - d2_splat(polygon->lower, DBL_MAX); - d2_splat(polygon->upper,-DBL_MAX); + dev = polygon->device; + TRY(paths.resize(ncomponents)); 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)); + if(nverts > UINT32_MAX) { + logger_print(polygon->device->logger, LOG_ERROR, + "Too many vertices for component %zu.\n", c); + res = RES_BAD_ARG; + goto error; + } + TRY(paths[c].resize(nverts)); /* Fetch polygon positions for connex component c */ FOR_EACH(i, 0, nverts) { double tmp[2]; - int64_t tmp2[2]; - Clipper2Lib::PointD pt; + int64_t tmp64[2]; + Clipper2Lib::Point64 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; + /* Check range and truncate precision to ensure further consistency */ + ERR(scpr_device_scale(dev, tmp, 2, tmp64)); + pt.x = tmp64[0]; + pt.y = tmp64[1]; + paths[c][i] = pt; + } + } - d2_min(polygon->lower, polygon->lower, tmp); - d2_max(polygon->upper, polygon->upper, tmp); + /* Merge vertices, ... */ + TRY(paths = Clipper2Lib::SimplifyPaths(paths, dev->inv_scale)); + polygon->paths = std::move(paths); + paths.Clipper2Lib::Paths64::~Paths64(); + changedc = (ncomponents != polygon->paths.size()); + for(c = 0; !changedv && c < polygon->paths.size(); c++) { + size_t nv; + get_nverts(c, &nv, data); + changedv |= (nv != polygon->paths[c].size()); + } + if(changedc || changedv) { + /* TODO: emit a warning log */ + logger_print(dev->logger, LOG_WARNING, + "Polygon has been simplified. Original counts are no longer valid.\n"); + } + + /* Build bounding box */ + polygon->lower[0] = polygon->lower[1] = INT64_MAX; + polygon->upper[0] = polygon->upper[1] = INT64_MIN; + FOR_EACH(c, 0, ncomponents) { + size_t i, nverts; + get_nverts(c, &nverts, data); + FOR_EACH(i, 0, nverts) { + int64_t tmp[2]; + int j; + tmp[0] = polygon->paths[c][i].x; + tmp[1] = polygon->paths[c][i].y; + for(j = 0; j < 2; j++) { + if(polygon->lower[j] > tmp[j]) polygon->lower[j] = tmp[j]; + if(polygon->upper[j] < tmp[j]) polygon->upper[j] = tmp[j]; + } } } @@ -252,42 +265,74 @@ exit: error: if(polygon) { polygon->paths.clear(); - d2_splat(polygon->lower, DBL_MAX); - d2_splat(polygon->upper,-DBL_MAX); + polygon->lower[0] = polygon->lower[1] = INT64_MAX; + polygon->upper[0] = polygon->upper[1] = INT64_MIN; } goto exit; } res_T +scpr_polygon_in_bbox + (struct scpr_polygon* polygon, + const double lower[2], + const double upper[2], + int* inside) +{ + int i, in = 1; + int64_t low[2], up[2]; + res_T res = RES_OK; + + if(!polygon || !lower || !upper || !inside) { + res = RES_BAD_ARG; + goto error; + } + + SCPR(device_scale(polygon->device, lower, 2, low)); + SCPR(device_scale(polygon->device, upper, 2, up)); + for(i = 0; i < 2; i++) { + if(polygon->lower[i] < low[i] || polygon->upper[i] > up[i]) { + in = 0; + break; + } + } + + *inside = in; +exit: + return res; +error: + goto exit; +} + +res_T scpr_polygon_create_copy - (struct mem_allocator* allocator, + (struct scpr_device* dev, const struct scpr_polygon* src_polygon, struct scpr_polygon** out_polygon) { - struct scpr_polygon* copy; - int copy_created = 0; + struct scpr_polygon* copy = NULL; + int i; res_T res = RES_OK; - if(!src_polygon || !out_polygon) { + if(!dev || !src_polygon || !out_polygon) { res = RES_BAD_ARG; goto error; } - ERR(scpr_polygon_create(allocator, &copy)); - copy_created = 1; + ERR(scpr_polygon_create(dev, &copy)); copy->paths = src_polygon->paths; - d2_set(copy->lower, src_polygon->lower); - d2_set(copy->upper, src_polygon->upper); + for(i = 0; i < 2; i++) { + copy->lower[i] = src_polygon->lower[i]; + copy->upper[i] = src_polygon->upper[i]; + } exit: if(out_polygon) *out_polygon = copy; return res; error: - if(copy_created) CHK(RES_OK == scpr_polygon_ref_put(copy)); + if(copy) SCPR(polygon_ref_put(copy)); copy = NULL; goto exit; } -#undef TRY res_T scpr_polygon_get_components_count @@ -323,15 +368,17 @@ scpr_polygon_get_position { size_t nverts; const size_t i = ivert; - const Clipper2Lib::PointD* pt; + const Clipper2Lib::Point64* pt; + int64_t pos64[2]; 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; + pos64[0] = pt->x; + pos64[1] = pt->y; + SCPR(device_unscale(polygon->device, pos64, 2, pos)); return RES_OK; } @@ -342,9 +389,11 @@ scpr_offset_polygon const enum scpr_join_type join_type) { size_t c; - Clipper2Lib::PathD tmp; + Clipper2Lib::Path64 tmp; + struct scpr_device* dev; Clipper2Lib::JoinType cjt; - Clipper2Lib::PathsD polygon; + Clipper2Lib::Paths64 polygon; + int64_t offset64; res_T res = RES_OK; if(!poly_desc) { @@ -352,6 +401,23 @@ scpr_offset_polygon goto error; } + dev = poly_desc->device; + + /* Check offset polygon will be in-range */ + if(offset > 0) { + int i; + double range[2]; + SCPR(device_get_range(dev, range)); + for(i = 0; i < 2; i++) { + if((double)poly_desc->lower[i] - offset < range[0] + || (double)poly_desc->upper[i] + offset > range[1]) + { + res = RES_BAD_ARG; + goto error; + } + } + } + /* Check join type */ switch(join_type) { case SCPR_JOIN_SQUARE: @@ -363,23 +429,27 @@ scpr_offset_polygon goto error; } - /* Some known problems when offset=0, not leaving polygon unchanged */ + ERR(scpr_device_scale(dev, &offset, 1, &offset64)); 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); + TRY(poly_desc->paths = Clipper2Lib::InflatePaths(poly_desc->paths, + (double)offset64, cjt, Clipper2Lib::EndType::Polygon)); /* Rebuild AABB */ - d2_splat(poly_desc->lower, DBL_MAX); - d2_splat(poly_desc->upper,-DBL_MAX); + poly_desc->lower[0] = poly_desc->lower[1] = INT64_MAX; + poly_desc->upper[0] = poly_desc->upper[1] = INT64_MIN; FOR_EACH(c, 0, poly_desc->paths.size()) { size_t i, nverts; nverts = poly_desc->paths[c].size(); 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); + int64_t pos64[2]; + int j; + pos64[0] = poly_desc->paths[c][i].x; + pos64[1] = poly_desc->paths[c][i].y; + for(j = 0; j < 2; j++) { + if(poly_desc->lower[j] > pos64[j]) poly_desc->lower[j] = pos64[j]; + if(poly_desc->upper[j] < pos64[j]) poly_desc->upper[j] = pos64[j]; + } } } @@ -409,8 +479,10 @@ scpr_polygon_eq *is_eq = 0; goto exit; } - if(!d2_eq(polygon1->lower, polygon2->lower) - || !d2_eq(polygon1->upper, polygon2->upper)) + if(polygon1->lower[0] != polygon2->lower[0] + || polygon1->lower[1] != polygon2->lower[1] + || polygon1->upper[0] != polygon2->upper[0] + || polygon1->upper[1] != polygon2->upper[1]) { *is_eq = 0; goto exit; @@ -419,7 +491,7 @@ scpr_polygon_eq /* Check actual coordinates */ matched = (char*)calloc(sz, sizeof(*matched)); FOR_EACH(i, 0, sz) { - if(!one_path_is_eq(&polygon1->paths, &polygon2->paths.at(i), matched)) { + if(!one_path_is_eq(&polygon1->paths, &polygon2->paths[i], matched)) { *is_eq = 0; goto exit; } @@ -433,4 +505,72 @@ error: goto exit; } +res_T +scpr_polygon_dump_to_obj + (struct scpr_polygon* polygon, + FILE* stream) +{ + res_T res = RES_OK; + size_t i, j, n = 1; + + if(!polygon || !stream) { + res = RES_BAD_ARG; + goto error; + } + + /* Vertice */ + for(i = 0; i < polygon->paths.size(); i++) { + for(j = 0; j < polygon->paths[i].size(); j++) { + Clipper2Lib::Point64& pt = polygon->paths[i][j]; + fprintf(stream, "v %zu %zu 0\n", pt.x, pt.y); + } + } + + /* Lines */ + for(i = 0; i < polygon->paths.size(); i++) { + size_t fst = n; + fprintf(stream, "l "); + for(j = 0; j < polygon->paths[i].size(); j++) { + fprintf(stream, " %zu", n++); + } + fprintf(stream, " %zu\n", fst); + } + +exit: + return res; +error: + goto exit; +} + +res_T +scpr_polygon_dump_component_to_obj + (struct scpr_polygon* polygon, + const size_t icomponent, + FILE* stream) +{ + res_T res = RES_OK; + size_t i; + if(!polygon || !stream || icomponent >= polygon->paths.size()) { + res = RES_BAD_ARG; + goto error; + } + + /* Vertice */ + for(i = 0; i < polygon->paths[icomponent].size(); i++) { + Clipper2Lib::Point64& pt = polygon->paths[icomponent][i]; + fprintf(stream, "v %zu %zu 0\n", pt.x, pt.y); + } + + /* Line */ + fprintf(stream, "l "); + for(i = 0; i < polygon->paths[icomponent].size(); i++) { + fprintf(stream, " %zu", ++i); + } + fprintf(stream, " 1\n"); + +exit: + return res; +error: + goto exit; +} diff --git a/src/test_scpr_clip.c b/src/test_scpr_clip.c @@ -13,6 +13,8 @@ * 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" @@ -29,17 +31,17 @@ dump_obj(FILE* stream, const struct scpr_mesh* mesh) CHK(stream != NULL); CHK(mesh != NULL); - CHK(scpr_mesh_get_vertices_count(mesh, &n) == RES_OK); + OK(scpr_mesh_get_vertices_count(mesh, &n)); FOR_EACH(i, 0, n) { double pos[2]; - CHK(scpr_mesh_get_position(mesh, i, pos) == RES_OK); + OK(scpr_mesh_get_position(mesh, i, pos)); fprintf(stream, "v %g %g 0\n", SPLIT2(pos)); } - CHK(scpr_mesh_get_triangles_count(mesh, &n) == RES_OK); + OK(scpr_mesh_get_triangles_count(mesh, &n)); FOR_EACH(i, 0, n) { size_t ids[3]; - CHK(scpr_mesh_get_indices(mesh, i, ids) == RES_OK); + OK(scpr_mesh_get_indices(mesh, i, ids)); fprintf(stream, "f %lu %lu %lu\n", (unsigned long)(ids[0] + 1), (unsigned long)(ids[1] + 1), @@ -49,12 +51,15 @@ dump_obj(FILE* stream, const struct scpr_mesh* mesh) static void test_triangle - (struct mem_allocator* allocator, + (struct scpr_device* dev, + 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 double triangle_pos1[] = { 0, 0, 0, 1, 1, 0 }; + double triangle_pos2[] = { 0, 0, 0, 1e20, 1e20, 0}; /* To be replaced */ const size_t triangle_ids[] = { 0, 1, 2 }; double** clip_pos; + double range[2]; size_t nverts[] = { 3 }; size_t ncomps = 1; const double clip_pos0[] = { -1.0, 0.25, 1.0, 0.75, 1, 0.25 }; @@ -63,46 +68,55 @@ test_triangle struct mesh_context mctx; size_t ntris; - clip_pos = MEM_CALLOC(allocator, ncomps, sizeof(*clip_pos)); - *clip_pos = MEM_CALLOC(allocator, nverts[0], 2*sizeof(**clip_pos)); + /* Set out-of-range value in triangle_pos2 */ + SCPR(device_get_range(dev, range)); + triangle_pos2[3] = triangle_pos2[4] = range[1] + 1; + + clip_pos = (double**)MEM_CALLOC(allocator, ncomps, sizeof(*clip_pos)); + *clip_pos = (double*)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); + OK(scpr_polygon_create(dev, &poly)); + OK(scpr_polygon_setup_indexed_vertices(poly, ncomps, pget_nverts, pget_pos, &pctx)); - mctx.coords = triangle_pos; + /* Check out-of-range */ + mctx.coords = triangle_pos2; mctx.nverts = 3; mctx.indices = triangle_ids; mctx.ntris = 1; - CHK(scpr_mesh_setup_indexed_vertices - (mesh, mctx.ntris, mget_ids, 3, mget_pos, &mctx) == RES_OK); - - 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_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); + BAD(scpr_mesh_setup_indexed_vertices + (mesh, mctx.ntris, mget_ids, 3, mget_pos, &mctx)); + + mctx.coords = triangle_pos1; + OK(scpr_mesh_setup_indexed_vertices + (mesh, mctx.ntris, mget_ids, 3, mget_pos, &mctx)); + + BAD(scpr_mesh_clip(NULL, SCPR_OPERATIONS_COUNT__, NULL)); + BAD(scpr_mesh_clip(mesh, SCPR_OPERATIONS_COUNT__, NULL)); + BAD(scpr_mesh_clip(NULL, SCPR_OPERATIONS_COUNT__, poly)); + BAD(scpr_mesh_clip(mesh, SCPR_OPERATIONS_COUNT__, poly)); + BAD(scpr_mesh_clip(NULL, SCPR_SUB, NULL)); + BAD(scpr_mesh_clip(mesh, SCPR_SUB, NULL)); + BAD(scpr_mesh_clip(NULL, SCPR_SUB, poly)); + OK(scpr_mesh_clip(mesh, SCPR_SUB, poly)); /*dump_obj(stdout, mesh);*/ - CHK(scpr_mesh_get_triangles_count(mesh, &ntris) == RES_OK); + OK(scpr_mesh_get_triangles_count(mesh, &ntris)); CHK(ntris == 3); MEM_RM(allocator, *clip_pos); MEM_RM(allocator, clip_pos); - CHK(scpr_polygon_ref_put(poly) == RES_OK); + OK(scpr_polygon_ref_put(poly)); } static void test_quad - (struct mem_allocator* allocator, + (struct scpr_device* dev, + 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 }; @@ -115,36 +129,36 @@ test_quad 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)); + clip_pos = (double**)MEM_CALLOC(allocator, ncomps, sizeof(*clip_pos)); + *clip_pos = (double*)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); + OK(scpr_polygon_create(dev, &poly)); + OK(scpr_polygon_setup_indexed_vertices(poly, ncomps, pget_nverts, pget_pos, &pctx)); 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, mctx.ntris, mget_ids, mctx.nverts, mget_pos, &mctx) == RES_OK); + OK(scpr_mesh_setup_indexed_vertices + (mesh, mctx.ntris, mget_ids, mctx.nverts, mget_pos, &mctx)); - CHK(scpr_mesh_clip(mesh, SCPR_AND, poly) == RES_OK); + OK(scpr_mesh_clip(mesh, SCPR_AND, poly)); /*dump_obj(stdout, mesh);*/ MEM_RM(allocator, *clip_pos); MEM_RM(allocator, clip_pos); - CHK(scpr_polygon_ref_put(poly) == RES_OK); + OK(scpr_polygon_ref_put(poly)); } static void test_disk - (struct mem_allocator* allocator, + (struct scpr_device* dev, + struct mem_allocator* allocator, struct scpr_mesh* mesh) { double** clip_pos; @@ -163,8 +177,8 @@ test_disk 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)); + clip_pos = (double**)MEM_CALLOC(allocator, ncomps, sizeof(*clip_pos)); + *clip_pos = (double*)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) { @@ -214,18 +228,17 @@ test_disk 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); + OK(scpr_polygon_create(dev, &poly)); + OK(scpr_polygon_setup_indexed_vertices(poly, ncomps, pget_nverts, pget_pos, &pctx)); 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, mctx.ntris, mget_ids, mctx.nverts, mget_pos, &mctx) == RES_OK); + OK(scpr_mesh_setup_indexed_vertices + (mesh, mctx.ntris, mget_ids, mctx.nverts, mget_pos, &mctx)); - CHK(scpr_mesh_clip(mesh, SCPR_SUB, poly) == RES_OK); + OK(scpr_mesh_clip(mesh, SCPR_SUB, poly)); dump_obj(stdout, mesh); @@ -233,25 +246,30 @@ test_disk MEM_RM(allocator, clip_pos); sa_release(pos); sa_release(ids); - CHK(scpr_polygon_ref_put(poly) == RES_OK); + OK(scpr_polygon_ref_put(poly)); } int main(int argc, char** argv) { struct mem_allocator allocator; + struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; + struct scpr_device* dev; struct scpr_mesh* mesh; (void)argc, (void)argv; mem_init_proxy_allocator(&allocator, &mem_default_allocator); - CHK(scpr_mesh_create(&allocator, &mesh) == RES_OK); + args.allocator = &allocator; + OK(scpr_device_create(&args, &dev)); + OK(scpr_mesh_create(dev, &mesh)); - test_triangle(&allocator, mesh); - test_quad(&allocator, mesh); - test_disk(&allocator, mesh); + test_triangle(dev, &allocator, mesh); + test_quad(dev, &allocator, mesh); + test_disk(dev, &allocator, mesh); - CHK(scpr_mesh_ref_put(mesh) == RES_OK); + OK(scpr_mesh_ref_put(mesh)); + OK(scpr_device_ref_put(dev)); check_memory_allocator(&allocator); mem_shutdown_proxy_allocator(&allocator); diff --git a/src/test_scpr_device.c b/src/test_scpr_device.c @@ -0,0 +1,108 @@ +/* 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> + +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; + struct scpr_device* dev; + double r[2]; + const double d[5] = { 0, 1, 2, 3, 4}; + double tmpd[5]; + const int64_t i64[5] = { 0, 1, 2, 3, 4}; + int64_t tmp64[5]; + int in; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + + args.allocator = &allocator; + + BAD(scpr_device_create(NULL, NULL)); + BAD(scpr_device_create(&args, NULL)); + BAD(scpr_device_create(NULL, &dev)); + + args.precision = -1; + BAD(scpr_device_create(&args, &dev)); + + args.precision = SCPR_DEVICE_CREATE_ARGS_DEFAULT.precision; + OK(scpr_device_create(&args, &dev)); + + BAD(scpr_device_ref_get(NULL)); + OK(scpr_device_ref_get(dev)); + + BAD(scpr_device_ref_put(NULL)); + OK(scpr_device_ref_put(dev)); + + BAD(scpr_device_get_range(NULL, NULL)); + BAD(scpr_device_get_range(NULL, r)); + BAD(scpr_device_get_range(dev, NULL)); + OK(scpr_device_get_range(dev, r)); + + BAD(scpr_device_in_range(NULL, NULL, 5, NULL)); + BAD(scpr_device_in_range(NULL, NULL, 5, &in)); + BAD(scpr_device_in_range(NULL, d, 5, NULL)); + BAD(scpr_device_in_range(dev, NULL, 5, NULL)); + BAD(scpr_device_in_range(NULL, d, 5, &in)); + BAD(scpr_device_in_range(dev, NULL, 5, &in)); + BAD(scpr_device_in_range(dev, d, 5, NULL)); + OK(scpr_device_in_range(dev, d, 5, &in)); + CHK(in); + /* With out_of_range value */ + memcpy(tmpd, d, sizeof(d)); + tmpd[3] = r[1] + 1; + OK(scpr_device_in_range(dev, tmpd, 5, &in)); + CHK(!in); + + BAD(scpr_device_scale(NULL, NULL, 5, NULL)); + BAD(scpr_device_scale(NULL, NULL, 5, tmp64)); + BAD(scpr_device_scale(NULL, d, 5, NULL)); + BAD(scpr_device_scale(dev, NULL, 5, NULL)); + BAD(scpr_device_scale(NULL, d, 5, tmp64)); + BAD(scpr_device_scale(dev, NULL, 5, tmp64)); + BAD(scpr_device_scale(dev, d, 5, NULL)); + OK(scpr_device_scale(dev, d, 5, tmp64)); + /* With out_of_range value */ + memcpy(tmpd, d, sizeof(d)); + tmpd[3] = r[1] + 1; + BAD(scpr_device_scale(dev, tmpd, 5, tmp64)); + + BAD(scpr_device_unscale(NULL, NULL, 5, NULL)); + BAD(scpr_device_unscale(NULL, NULL, 5, tmpd)); + BAD(scpr_device_unscale(NULL, i64, 5, NULL)); + BAD(scpr_device_unscale(dev, NULL, 5, NULL)); + BAD(scpr_device_unscale(NULL, i64, 5, tmpd)); + BAD(scpr_device_unscale(dev, NULL, 5, tmpd)); + BAD(scpr_device_unscale(dev, i64, 5, NULL)); + OK(scpr_device_unscale(dev, i64, 5, tmpd)); + + OK(scpr_device_ref_put(dev)); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +} + diff --git a/src/test_scpr_intersector.c b/src/test_scpr_intersector.c @@ -0,0 +1,206 @@ +/* Copyright (C) 2016-2018, 2021 |Meso|Star> (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#define _POSIX_C_SOURCE 200112L + +#include "scpr.h" +#include "test_scpr_utils.h" + +#include <rsys/rsys.h> + +#include <memory.h> + +struct cpt { + size_t simple, collinear; +}; + +static int +simple_intersection + (struct scpr_callback_segment* segment1, + struct scpr_callback_segment* segment2, + void* data) +{ + struct cpt* cpt = (struct cpt*)data; + (void)segment1; (void)segment2; + ASSERT(cpt); + cpt->simple++; + return 0; +} + +static int +collinear_segments + (struct scpr_callback_segment* segment1, + struct scpr_callback_segment* segment2, + void* data) +{ + struct cpt* cpt = (struct cpt*)data; + (void)segment1; (void)segment2; + ASSERT(cpt); + cpt->collinear++; + return 0; +} + +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct scpr_device* dev; + struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; + struct scpr_intersector* inter; + struct scpr_intersector_check_callbacks cb + = SCPR_INTERSECTOR_CHECK_CALLBACKS_NULL__ ; + struct polygon_context ctx; + struct scpr_polygon *p01, *p23, *p4; + size_t ncomps = 2; + double** coords; + /* 1,1 + * +-----------+----+ + * | / | / + * | / | / + * +-----------+-C2--+ + * | | | | + * | | | | + * | +-C0--+-+---+----+----+ + * | 0,0 |/ | | + * | +-C3-------+-C4-+ + * +-C1--------+ + * -0.5,-0.5 + */ + double coords0[] = { + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0 + }; + double coords1[] = { + -0.5, -0.5, + 0.5, -0.5, + 0.5, 0.5, + -0.5, 0.5 + }; + double coords2[] = { + 0.5, 0.5, + 1.0, 0.5, + 1.5, 1.0, + 1.0, 1.0 + }; + double coords3[] = { + 0.5, -0.3, + 1.5, -0.3, + 1.5, 0.0, + 0.7, 0.0 + }; + double coords4[] = { + 1.5, -0.3, + 2.0, -0.3, + 2.0, 0.0, + 1.5, 0 + }; + size_t nverts[] = { 4, 4 }; + struct cpt cpt = { 0, 0 }; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + args.allocator = &allocator; + args.verbosity_level = 3; + args.precision = 6; + OK(scpr_device_create(&args, &dev)); + + BAD(scpr_intersector_create(NULL, NULL)); + BAD(scpr_intersector_create(dev, NULL)); + BAD(scpr_intersector_create(NULL, &inter)); + OK(scpr_intersector_create(dev, &inter)); + + BAD(scpr_intersector_ref_get(NULL)); + OK(scpr_intersector_ref_get(inter)); + + BAD(scpr_intersector_ref_put(NULL)); + OK(scpr_intersector_ref_put(inter)); + + OK(scpr_polygon_create(dev, &p01)); + OK(scpr_polygon_create(dev, &p23)); + OK(scpr_polygon_create(dev, &p4)); + coords = (double**)MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); + coords[0] = (double*)MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords)); + coords[1] = (double*)MEM_CALLOC(&allocator, nverts[1], 2*sizeof(**coords)); + + ctx.coords = coords; + ctx.nverts = nverts; + ctx.ncomps = ncomps; + + #define SETUP scpr_polygon_setup_indexed_vertices + memcpy(coords[0], coords0, 2*nverts[0]*sizeof(**coords)); + memcpy(coords[1], coords1, 2*nverts[1]*sizeof(**coords)); + OK(SETUP(p01, ncomps, pget_nverts, pget_pos, &ctx)); + + memcpy(coords[0], coords2, 2*nverts[0]*sizeof(**coords)); + memcpy(coords[1], coords3, 2*nverts[1]*sizeof(**coords)); + OK(SETUP(p23, ncomps, pget_nverts, pget_pos, &ctx)); + + memcpy(coords[0], coords4, 2*nverts[0]*sizeof(**coords)); + OK(SETUP(p4, 1, pget_nverts, pget_pos, &ctx)); + + BAD(scpr_intersector_register_component(NULL, NULL, ncomps)); + BAD(scpr_intersector_register_component(NULL, NULL, 0)); + BAD(scpr_intersector_register_component(NULL, p01, ncomps)); + BAD(scpr_intersector_register_component(inter, NULL, ncomps)); + BAD(scpr_intersector_register_component(NULL, p01, 0)); + BAD(scpr_intersector_register_component(inter, NULL, 0)); + BAD(scpr_intersector_register_component(inter, p01, ncomps)); + OK(scpr_intersector_register_component(inter, p01, 0)); + OK(scpr_intersector_register_component(inter, p01, 1)); + OK(scpr_intersector_register_component(inter, p23, 0)); + OK(scpr_intersector_register_component(inter, p23, 1)); + BAD(scpr_intersector_register_component(inter, p23, 1)); /* Registered twice */ + + BAD(scpr_intersector_register_polygon(NULL, NULL)); + BAD(scpr_intersector_register_polygon(NULL, p01)); + BAD(scpr_intersector_register_polygon(inter, NULL)); + OK(scpr_intersector_register_polygon(inter, p4)); + BAD(scpr_intersector_register_polygon(inter, p4)); /* Registered twice */ + + BAD(scpr_intersector_check(NULL, NULL, NULL)); + BAD(scpr_intersector_check(NULL, &cb, NULL)); + BAD(scpr_intersector_check(inter, NULL, NULL)); + BAD(scpr_intersector_check(inter, &cb, NULL)); + BAD(scpr_intersector_check(inter, &cb, &cpt)); /* Callbacks are all NULL */ + /* Report only intersections */ + cb.simple_intersection = simple_intersection; + OK(scpr_intersector_check(inter, &cb, &cpt)); + CHK(cpt.simple == 2); + CHK(cpt.collinear == 0); + /* Report intersections and collinearity */ + cb.collinear_segments = collinear_segments; + cpt.simple = cpt.collinear = 0; + OK(scpr_intersector_check(inter, &cb, &cpt)); + CHK(cpt.simple == 2); + CHK(cpt.collinear == 2); + + OK(scpr_polygon_ref_put(p01)); + OK(scpr_polygon_ref_put(p23)); + OK(scpr_polygon_ref_put(p4)); + OK(scpr_intersector_ref_put(inter)); + OK(scpr_device_ref_put(dev)); + + MEM_RM(&allocator, coords[0]); + MEM_RM(&allocator, coords[1]); + MEM_RM(&allocator, coords); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +} + diff --git a/src/test_scpr_mesh.c b/src/test_scpr_mesh.c @@ -13,6 +13,8 @@ * 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" @@ -46,6 +48,8 @@ main(int argc, char** argv) size_t ids[3]; double pos[2]; size_t i, n; + struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; + struct scpr_device* dev; struct mem_allocator allocator; struct mesh_context ctx; struct scpr_mesh* mesh; @@ -53,17 +57,20 @@ main(int argc, char** argv) mem_init_proxy_allocator(&allocator, &mem_default_allocator); - CHK(scpr_mesh_create(NULL, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_create(&allocator, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_create(NULL, &mesh) == RES_OK); + args.allocator = &allocator; + OK(scpr_device_create(&args, &dev)); + + BAD(scpr_mesh_create(NULL, NULL)); + BAD(scpr_mesh_create(dev, NULL)); + OK(scpr_mesh_create(dev, &mesh)); - CHK(scpr_mesh_ref_get(NULL) == RES_BAD_ARG); - CHK(scpr_mesh_ref_get(mesh) == RES_OK); - CHK(scpr_mesh_ref_put(NULL) == RES_BAD_ARG); - CHK(scpr_mesh_ref_put(mesh) == RES_OK); - CHK(scpr_mesh_ref_put(mesh) == RES_OK); + BAD(scpr_mesh_ref_get(NULL)); + OK(scpr_mesh_ref_get(mesh)); + BAD(scpr_mesh_ref_put(NULL)); + OK(scpr_mesh_ref_put(mesh)); + OK(scpr_mesh_ref_put(mesh)); - CHK(scpr_mesh_create(&allocator, &mesh) == RES_OK); + OK(scpr_mesh_create(dev, &mesh)); ctx.coords = coords; ctx.nverts = nverts; @@ -71,122 +78,122 @@ 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, 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, 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); + BAD(SETUP(NULL, 0, NULL, 0, NULL, NULL)); + BAD(SETUP(mesh, 0, NULL, 0, NULL, NULL)); + BAD(SETUP(NULL, ntris, NULL, 0, NULL, NULL)); + BAD(SETUP(mesh, ntris, NULL, 0, NULL, NULL)); + BAD(SETUP(NULL, 0, mget_ids, 0, NULL, NULL)); + BAD(SETUP(mesh, 0, mget_ids, 0, NULL, NULL)); + BAD(SETUP(NULL, ntris, mget_ids, 0, NULL, NULL)); + BAD(SETUP(mesh, ntris, mget_ids, 0, NULL, NULL)); + BAD(SETUP(NULL, 0, NULL, nverts, NULL, NULL)); + BAD(SETUP(mesh, 0, NULL, nverts, NULL, NULL)); + BAD(SETUP(NULL, ntris, NULL, nverts, NULL, NULL)); + BAD(SETUP(mesh, ntris, NULL, nverts, NULL, NULL)); + BAD(SETUP(NULL, 0, mget_ids, nverts, NULL, NULL)); + BAD(SETUP(mesh, 0, mget_ids, nverts, NULL, NULL)); + BAD(SETUP(NULL, ntris, mget_ids, nverts, NULL, NULL)); + BAD(SETUP(mesh, ntris, mget_ids, nverts, NULL, NULL)); + BAD(SETUP(NULL, 0, NULL, 0, mget_pos, NULL)); + BAD(SETUP(mesh, 0, NULL, 0, mget_pos, NULL)); + BAD(SETUP(NULL, ntris, NULL, 0, mget_pos, NULL)); + BAD(SETUP(mesh, ntris, NULL, 0, mget_pos, NULL)); + BAD(SETUP(NULL, 0, mget_ids, 0, mget_pos, NULL)); + BAD(SETUP(mesh, 0, mget_ids, 0, mget_pos, NULL)); + BAD(SETUP(NULL, ntris, mget_ids, 0, mget_pos, NULL)); + BAD(SETUP(mesh, ntris, mget_ids, 0, mget_pos, NULL)); + BAD(SETUP(NULL, 0, NULL, nverts, mget_pos, NULL)); + BAD(SETUP(mesh, 0, NULL, nverts, mget_pos, NULL)); + BAD(SETUP(NULL, ntris, NULL, nverts, mget_pos, NULL)); + BAD(SETUP(mesh, ntris, NULL, nverts, mget_pos, NULL)); + BAD(SETUP(NULL, 0, mget_ids, nverts, mget_pos, NULL)); + BAD(SETUP(mesh, 0, mget_ids, nverts, mget_pos, NULL)); + BAD(SETUP(NULL, ntris, mget_ids, nverts, mget_pos, NULL)); + BAD(SETUP(mesh, ntris, mget_ids, nverts, mget_pos, NULL)); + BAD(SETUP(NULL, 0, NULL, 0, NULL, &ctx)); + BAD(SETUP(mesh, 0, NULL, 0, NULL, &ctx)); + BAD(SETUP(NULL, ntris, NULL, 0, NULL, &ctx)); + BAD(SETUP(mesh, ntris, NULL, 0, NULL, &ctx)); + BAD(SETUP(NULL, 0, mget_ids, 0, NULL, &ctx)); + BAD(SETUP(mesh, 0, mget_ids, 0, NULL, &ctx)); + BAD(SETUP(NULL, ntris, mget_ids, 0, NULL, &ctx)); + BAD(SETUP(mesh, ntris, mget_ids, 0, NULL, &ctx)); + BAD(SETUP(NULL, 0, NULL, nverts, NULL, &ctx)); + BAD(SETUP(mesh, 0, NULL, nverts, NULL, &ctx)); + BAD(SETUP(NULL, ntris, NULL, nverts, NULL, &ctx)); + BAD(SETUP(mesh, ntris, NULL, nverts, NULL, &ctx)); + BAD(SETUP(NULL, 0, mget_ids, nverts, NULL, &ctx)); + BAD(SETUP(mesh, 0, mget_ids, nverts, NULL, &ctx)); + BAD(SETUP(NULL, ntris, mget_ids, nverts, NULL, &ctx)); + BAD(SETUP(mesh, ntris, mget_ids, nverts, NULL, &ctx)); + BAD(SETUP(NULL, 0, NULL, 0, mget_pos, &ctx)); + BAD(SETUP(mesh, 0, NULL, 0, mget_pos, &ctx)); + BAD(SETUP(NULL, ntris, NULL, 0, mget_pos, &ctx)); + BAD(SETUP(mesh, ntris, NULL, 0, mget_pos, &ctx)); + BAD(SETUP(NULL, 0, mget_ids, 0, mget_pos, &ctx)); + BAD(SETUP(mesh, 0, mget_ids, 0, mget_pos, &ctx)); + BAD(SETUP(NULL, ntris, mget_ids, 0, mget_pos, &ctx)); + BAD(SETUP(mesh, ntris, mget_ids, 0, mget_pos, &ctx)); + BAD(SETUP(NULL, 0, NULL, nverts, mget_pos, &ctx)); + BAD(SETUP(mesh, 0, NULL, nverts, mget_pos, &ctx)); + BAD(SETUP(NULL, ntris, NULL, nverts, mget_pos, &ctx)); + BAD(SETUP(mesh, ntris, NULL, nverts, mget_pos, &ctx)); + BAD(SETUP(NULL, 0, mget_ids, nverts, mget_pos, &ctx)); + BAD(SETUP(mesh, 0, mget_ids, nverts, mget_pos, &ctx)); + BAD(SETUP(NULL, ntris, mget_ids, nverts, mget_pos, &ctx)); + OK(SETUP(mesh, ntris, mget_ids, nverts, mget_pos, &ctx)); ctx.indices = indices_bad; - CHK(SETUP(mesh, 1, mget_ids, nverts, mget_pos, &ctx) == RES_BAD_ARG); + BAD(SETUP(mesh, 1, mget_ids, nverts, mget_pos, &ctx)); #undef SETUP - CHK(scpr_mesh_get_triangles_count(NULL, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_triangles_count(mesh, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_triangles_count(NULL, &n) == RES_BAD_ARG); - CHK(scpr_mesh_get_triangles_count(mesh, &n) == RES_OK); + BAD(scpr_mesh_get_triangles_count(NULL, NULL)); + BAD(scpr_mesh_get_triangles_count(mesh, NULL)); + BAD(scpr_mesh_get_triangles_count(NULL, &n)); + OK(scpr_mesh_get_triangles_count(mesh, &n)); CHK(n == 0); - CHK(scpr_mesh_get_vertices_count(NULL, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_vertices_count(mesh, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_vertices_count(NULL, &n) == RES_BAD_ARG); - CHK(scpr_mesh_get_vertices_count(mesh, &n) == RES_OK); + BAD(scpr_mesh_get_vertices_count(NULL, NULL)); + BAD(scpr_mesh_get_vertices_count(mesh, NULL)); + BAD(scpr_mesh_get_vertices_count(NULL, &n)); + OK(scpr_mesh_get_vertices_count(mesh, &n)); CHK(n == 0); ctx.indices = indices; - CHK(scpr_mesh_setup_indexed_vertices - (mesh, ntris, mget_ids, nverts, mget_pos, &ctx) == RES_OK); - CHK(scpr_mesh_get_triangles_count(mesh, &n) == RES_OK); + OK(scpr_mesh_setup_indexed_vertices(mesh, ntris, mget_ids, nverts, mget_pos, &ctx)); + OK(scpr_mesh_get_triangles_count(mesh, &n)); CHK(n == ntris); - CHK(scpr_mesh_get_vertices_count(mesh, &n) == RES_OK); + OK(scpr_mesh_get_vertices_count(mesh, &n)); CHK(n == nverts); - CHK(scpr_mesh_get_indices(NULL, ntris, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_indices(mesh, ntris, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_indices(NULL, 0, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_indices(mesh, 0, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_indices(NULL, ntris, ids) == RES_BAD_ARG); - CHK(scpr_mesh_get_indices(mesh, ntris, ids) == RES_BAD_ARG); - CHK(scpr_mesh_get_indices(NULL, 0, ids) == RES_BAD_ARG); + BAD(scpr_mesh_get_indices(NULL, ntris, NULL)); + BAD(scpr_mesh_get_indices(mesh, ntris, NULL)); + BAD(scpr_mesh_get_indices(NULL, 0, NULL)); + BAD(scpr_mesh_get_indices(mesh, 0, NULL)); + BAD(scpr_mesh_get_indices(NULL, ntris, ids)); + BAD(scpr_mesh_get_indices(mesh, ntris, ids)); + BAD(scpr_mesh_get_indices(NULL, 0, ids)); FOR_EACH(i, 0, ntris) { - CHK(scpr_mesh_get_indices(mesh, i, ids) == RES_OK); + OK(scpr_mesh_get_indices(mesh, i, ids)); CHK(ids[0] == indices[i*3+0]); CHK(ids[1] == indices[i*3+1]); CHK(ids[2] == indices[i*3+2]); } - CHK(scpr_mesh_get_position(NULL, nverts, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_position(mesh, nverts, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_position(NULL, 0, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_position(mesh, 0, NULL) == RES_BAD_ARG); - CHK(scpr_mesh_get_position(NULL, nverts, pos) == RES_BAD_ARG); - CHK(scpr_mesh_get_position(mesh, nverts, pos) == RES_BAD_ARG); - CHK(scpr_mesh_get_position(NULL, 0, pos) == RES_BAD_ARG); + BAD(scpr_mesh_get_position(NULL, nverts, NULL)); + BAD(scpr_mesh_get_position(mesh, nverts, NULL)); + BAD(scpr_mesh_get_position(NULL, 0, NULL)); + BAD(scpr_mesh_get_position(mesh, 0, NULL)); + BAD(scpr_mesh_get_position(NULL, nverts, pos)); + BAD(scpr_mesh_get_position(mesh, nverts, pos)); + CHK(scpr_mesh_get_position(NULL, 0, pos)); FOR_EACH(i, 0, nverts) { - CHK(scpr_mesh_get_position(mesh, i, pos) == RES_OK); + OK(scpr_mesh_get_position(mesh, i, pos)); CHK(pos[0] == coords[i*2+0]); CHK(pos[1] == coords[i*2+1]); } - CHK(scpr_mesh_ref_put(mesh) == RES_OK); + OK(scpr_mesh_ref_put(mesh)); + OK(scpr_device_ref_put(dev)); check_memory_allocator(&allocator); mem_shutdown_proxy_allocator(&allocator); diff --git a/src/test_scpr_offset.c b/src/test_scpr_offset.c @@ -13,6 +13,8 @@ * 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" @@ -33,102 +35,123 @@ test_single(void) 2.0, 2.0, 2.0, -1.0 }; - const double coords2[] = { + double coords2[] = { 0.12345678901234, 0.0, 0.0, 1.0, - 1.0, 9223372036854, + 1.0, 1000000000000, /* To be replaced */ 1.0, 0.0 }; - const double coords3[] = { - 9223372036855, 0.0, - 0.0, 1.0, - 1.0, 9223372036854, - 1.0, 0.0 + double coords2_reverse[] = { + 0.12345678901234, 0.0, + 1.0, 0.0, + 1.0, 1000000000000, /* To be replaced */ + 0.0, 1.0 }; double** coords; + double range[2]; size_t nverts[] = { 4 }; size_t ncomps = 1; struct mem_allocator allocator; struct polygon_context ctx; struct scpr_polygon* polygon; struct scpr_polygon* expected; + struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; + struct scpr_device* dev; int eq; mem_init_proxy_allocator(&allocator, &mem_default_allocator); + args.allocator = &allocator; + OK(scpr_device_create(&args, &dev)); - coords = MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); - *coords = MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords)); + /* Set barely in-range value in coords2 */ + SCPR(device_get_range(dev, range)); + coords2[5] = coords2_reverse[5] = range[1]; + + coords = (double**)MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); + *coords = (double*)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); + OK(scpr_polygon_create(dev, &polygon)); + OK(scpr_polygon_create(dev, &expected)); 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); + OK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); /* Offset 0 = unchanged */ - CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); CHK(eq); + CHK(check_stability(dev, polygon)); /* 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); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); - CHK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); CHK(eq); + CHK(check_stability(dev, polygon)); /* 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); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); + + OK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); + CHK(eq); + CHK(check_stability(dev, polygon)); - CHK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + /* Non representable offset: truncation will ensure stability */ + OK(scpr_offset_polygon(polygon, 0.123456789, SCPR_JOIN_MITER)); + OK(scpr_offset_polygon(expected, 0.123457, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); CHK(eq); + CHK(check_stability(dev, polygon)); /* Offset -5: empty polygon */ ncomps = 0; ctx.ncomps = ncomps; - CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) - == RES_OK); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); - CHK(scpr_offset_polygon(polygon, -5, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, -5, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); CHK(eq); + CHK(check_stability(dev, polygon)); - /* Check consistency with a non representable coordinate */ + /* Check coordinates barely in-range */ 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); + OK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); /* Offset 0 = unchanged */ - CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); CHK(eq); + CHK(check_stability(dev, polygon)); - /* 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); + /* Check non-effect of CW/CCW */ + memcpy(*coords, coords2_reverse, 2*nverts[0]*sizeof(**coords)); + OK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)); + OK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); + CHK(eq); + CHK(check_stability(dev, polygon)); + + /* Check out of range after offset being detected */ + BAD(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER)); /* Cleanup */ - CHK(scpr_polygon_ref_put(polygon) == RES_OK); - CHK(scpr_polygon_ref_put(expected) == RES_OK); + OK(scpr_polygon_ref_put(polygon)); + OK(scpr_device_ref_put(dev)); + OK(scpr_polygon_ref_put(expected)); MEM_RM(&allocator, *coords); MEM_RM(&allocator, coords); @@ -178,76 +201,75 @@ test_double(void) struct polygon_context ctx; struct scpr_polygon* polygon; struct scpr_polygon* expected; + struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; + struct scpr_device* dev; int eq; mem_init_proxy_allocator(&allocator, &mem_default_allocator); + args.allocator = &allocator; + OK(scpr_device_create(&args, &dev)); - 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)); + coords = (double**)MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); + *coords = (double*)MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords)); + *(coords+1) = (double*)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); + OK(scpr_polygon_create(dev, &polygon)); + OK(scpr_polygon_create(dev, &expected)); 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); + OK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); /* Offset 0 = unchanged */ - CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); 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); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); - CHK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); 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); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); - CHK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); 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); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); - CHK(scpr_offset_polygon(polygon, 4.5, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, 4.5, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); 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); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); - CHK(scpr_offset_polygon(polygon, -5, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, -5, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); CHK(eq); - CHK(scpr_polygon_ref_put(polygon) == RES_OK); - CHK(scpr_polygon_ref_put(expected) == RES_OK); + OK(scpr_polygon_ref_put(polygon)); + OK(scpr_device_ref_put(dev)); + OK(scpr_polygon_ref_put(expected)); MEM_RM(&allocator, *coords); MEM_RM(&allocator, *(coords+1)); @@ -298,62 +320,61 @@ test_internal(void) struct polygon_context ctx; struct scpr_polygon* polygon; struct scpr_polygon* expected; + struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; + struct scpr_device* dev; int eq; mem_init_proxy_allocator(&allocator, &mem_default_allocator); + args.allocator = &allocator; + OK(scpr_device_create(&args, &dev)); - 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)); + coords = (double**)MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); + *coords = (double*)MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords)); + *(coords+1) = (double*)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); + OK(scpr_polygon_create(dev, &polygon)); + OK(scpr_polygon_create(dev, &expected)); 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); + OK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); /* Offset 0 = unchanged */ - CHK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); 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); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); - CHK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, -1, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); 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); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); - CHK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, 1, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); 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); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); - CHK(scpr_offset_polygon(polygon, 5, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, 5, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); CHK(eq); /* From the original polygon, offset -2.5: empty polygon */ @@ -361,20 +382,19 @@ test_internal(void) 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); + OK(scpr_polygon_setup_indexed_vertices(polygon, ncomps, pget_nverts, pget_pos, &ctx)); ncomps = 0; ctx.ncomps = ncomps; - CHK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx) - == RES_OK); + OK(scpr_polygon_setup_indexed_vertices(expected, ncomps, pget_nverts, pget_pos, &ctx)); - CHK(scpr_offset_polygon(polygon, -2.5, SCPR_JOIN_MITER) == RES_OK); - CHK(scpr_polygon_eq(polygon, expected, &eq) == RES_OK); + OK(scpr_offset_polygon(polygon, -2.5, SCPR_JOIN_MITER)); + OK(scpr_polygon_eq(polygon, expected, &eq)); CHK(eq); - CHK(scpr_polygon_ref_put(polygon) == RES_OK); - CHK(scpr_polygon_ref_put(expected) == RES_OK); + OK(scpr_polygon_ref_put(polygon)); + OK(scpr_polygon_ref_put(expected)); + OK(scpr_device_ref_put(dev)); MEM_RM(&allocator, *coords); MEM_RM(&allocator, *(coords+1)); diff --git a/src/test_scpr_polygon.c b/src/test_scpr_polygon.c @@ -13,6 +13,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <stdio.h> +#define _POSIX_C_SOURCE 200112L + #include "scpr.h" #include "test_scpr_utils.h" @@ -24,48 +27,71 @@ 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 + 1.0, 1.0, + 1.0, 1.0, + 0.0, 1.0 + }; + double coords0_reduced[] = { + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0 + }; + double coords1[] = { + 0.0, 0.0, + 0.5, 0.0, + 0.0, 1.0, + 1.0, 1.0, + 1.0, 1000000000001, /* To be replaced */ + 1.0, 0.0 }; double** coords; - size_t nverts[] = { 9 }; + size_t nverts[] = { 6 }; size_t ncomps = 1; - double pos[2]; + double pos[2], range[2]; size_t i, c, n; struct mem_allocator allocator; struct polygon_context ctx; struct scpr_polygon* polygon; struct scpr_polygon* copy; - int eq; + struct scpr_device* dev; + struct scpr_device_create_args args = SCPR_DEVICE_CREATE_ARGS_DEFAULT; + int eq, in; + double low[2] = {0, 0}, up[2] = {1, 1}; + FILE* stream = NULL; (void)argc, (void)argv; mem_init_proxy_allocator(&allocator, &mem_default_allocator); - coords = MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); - *coords = MEM_CALLOC(&allocator, nverts[0], 2*sizeof(**coords)); + coords = (double**)MEM_CALLOC(&allocator, ncomps, sizeof(*coords)); + *coords = (double*)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); + args.allocator = &allocator; + args.precision = SCPR_DEVICE_CREATE_ARGS_DEFAULT.precision; + OK(scpr_device_create(&args, &dev)); + + /* Set out-of-range value in coords1 */ + SCPR(device_get_range(dev, range)); + coords1[9] = range[1] + 1; - 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); + BAD(scpr_polygon_create(NULL, NULL)); + BAD(scpr_polygon_create(dev, NULL)); + OK(scpr_polygon_create(dev, &polygon)); + OK(scpr_polygon_create(dev, &copy)); + OK(scpr_polygon_ref_put(copy)); - CHK(scpr_polygon_create(&allocator, &polygon) == RES_OK); + BAD(scpr_polygon_ref_get(NULL)); + OK(scpr_polygon_ref_get(polygon)); + BAD(scpr_polygon_ref_put(NULL)); + OK(scpr_polygon_ref_put(polygon)); + OK(scpr_polygon_ref_put(polygon)); - CHK(scpr_polygon_get_components_count(polygon, &n) == RES_OK); + OK(scpr_polygon_create(dev, &polygon)); + + OK(scpr_polygon_get_components_count(polygon, &n)); CHK(n == 0); ctx.coords = coords; @@ -73,79 +99,137 @@ main(int argc, char** argv) 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); + BAD(SETUP(NULL, ncomps, NULL, NULL, NULL)); + BAD(SETUP(polygon, ncomps, NULL, NULL, NULL)); + BAD(SETUP(NULL, ncomps, pget_nverts, NULL, NULL)); + BAD(SETUP(polygon, ncomps, pget_nverts, NULL, NULL)); + BAD(SETUP(NULL, ncomps, NULL, pget_pos, NULL)); + BAD(SETUP(polygon, ncomps, NULL, pget_pos, NULL)); + BAD(SETUP(NULL, ncomps, pget_nverts, pget_pos, NULL)); + BAD(SETUP(polygon, ncomps, pget_nverts, pget_pos, NULL)); + BAD(SETUP(NULL, ncomps, NULL, NULL, &ctx)); + BAD(SETUP(polygon, ncomps, NULL, NULL, &ctx)); + BAD(SETUP(NULL, ncomps, pget_nverts, NULL, &ctx)); + BAD(SETUP(polygon, ncomps, pget_nverts, NULL, &ctx)); + BAD(SETUP(NULL, ncomps, NULL, pget_pos, &ctx)); + BAD(SETUP(polygon, ncomps, NULL, pget_pos, &ctx)); + BAD(SETUP(NULL, ncomps, pget_nverts, pget_pos, &ctx)); + /* Check out of range data being detected at setup */ + memcpy(*coords, coords1, 2*nverts[0]*sizeof(**coords)); + BAD(SETUP(polygon, ncomps, pget_nverts, pget_pos, &ctx)); + /* Check polygon was simplified to 4 vertices */ + memcpy(*coords, coords0, 2*nverts[0]*sizeof(**coords)); + OK(SETUP(polygon, ncomps, pget_nverts, pget_pos, &ctx)); + OK(scpr_polygon_get_vertices_count(polygon, 0, &n)); + CHK(n == sizeof(coords0_reduced)/(2*sizeof(*coords0_reduced))); #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); + BAD(scpr_polygon_in_bbox(NULL, NULL, NULL, NULL)); + BAD(scpr_polygon_in_bbox(NULL, NULL, NULL, &in)); + BAD(scpr_polygon_in_bbox(NULL, NULL, up, NULL)); + BAD(scpr_polygon_in_bbox(NULL, NULL, up, &in)); + BAD(scpr_polygon_in_bbox(NULL, low, NULL, NULL)); + BAD(scpr_polygon_in_bbox(NULL, low, NULL, &in)); + BAD(scpr_polygon_in_bbox(NULL, low, up, NULL)); + BAD(scpr_polygon_in_bbox(NULL, low, up, &in)); + BAD(scpr_polygon_in_bbox(polygon, NULL, NULL, NULL)); + BAD(scpr_polygon_in_bbox(polygon, NULL, NULL, &in)); + BAD(scpr_polygon_in_bbox(polygon, NULL, up, NULL)); + BAD(scpr_polygon_in_bbox(polygon, NULL, up, &in)); + BAD(scpr_polygon_in_bbox(polygon, low, NULL, NULL)); + BAD(scpr_polygon_in_bbox(polygon, low, NULL, &in)); + OK(scpr_polygon_in_bbox(polygon, low, up, &in)); + CHK(in); + /* With smaller box */ + up[0] = 0.5; + OK(scpr_polygon_in_bbox(polygon, low, up, &in)); + CHK(!in); + + BAD(scpr_polygon_get_components_count(NULL, NULL)); + BAD(scpr_polygon_get_components_count(polygon, NULL)); + BAD(scpr_polygon_get_components_count(NULL, &n)); + OK(scpr_polygon_get_components_count(polygon, &n)); 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); + BAD(scpr_polygon_get_vertices_count(NULL, ncomps, NULL)); + BAD(scpr_polygon_get_vertices_count(polygon, ncomps, NULL)); + BAD(scpr_polygon_get_vertices_count(NULL, 0, NULL)); + BAD(scpr_polygon_get_vertices_count(polygon, 0, NULL)); + BAD(scpr_polygon_get_vertices_count(NULL, ncomps, &n)); + BAD(scpr_polygon_get_vertices_count(polygon, ncomps, &n)); + BAD(scpr_polygon_get_vertices_count(NULL, 0, &n)); + OK(scpr_polygon_get_vertices_count(polygon, 0, &n)); + + BAD(scpr_polygon_eq(NULL, NULL, NULL)); + BAD(scpr_polygon_eq(polygon, NULL, NULL)); + BAD(scpr_polygon_eq(NULL, polygon, NULL)); + BAD(scpr_polygon_eq(polygon, polygon, NULL)); + BAD(scpr_polygon_eq(NULL, NULL, &eq)); + BAD(scpr_polygon_eq(polygon, NULL, &eq)); + BAD(scpr_polygon_eq(polygon, 0, &eq)); + OK(scpr_polygon_eq(polygon, polygon, &eq)); 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); + BAD(scpr_polygon_get_position(NULL, ncomps, nverts[0], NULL)); + BAD(scpr_polygon_get_position(NULL, ncomps, nverts[0], pos)); + BAD(scpr_polygon_get_position(NULL, ncomps, 0, NULL)); + BAD(scpr_polygon_get_position(NULL, ncomps, 0, pos)); + BAD(scpr_polygon_get_position(NULL, 0, nverts[0], NULL)); + BAD(scpr_polygon_get_position(NULL, 0, nverts[0], pos)); + BAD(scpr_polygon_get_position(NULL, 0, 0, NULL)); + BAD(scpr_polygon_get_position(NULL, 0, 0, pos)); + BAD(scpr_polygon_get_position(polygon, ncomps, nverts[0], NULL)); + BAD(scpr_polygon_get_position(polygon, ncomps, nverts[0], pos)); + BAD(scpr_polygon_get_position(polygon, ncomps, 0, NULL)); + BAD(scpr_polygon_get_position(polygon, ncomps, 0, pos)); + BAD(scpr_polygon_get_position(polygon, 0, nverts[0], NULL)); + BAD(scpr_polygon_get_position(polygon, 0, nverts[0], pos)); + BAD(scpr_polygon_get_position(polygon, 0, 0, NULL)); + memcpy(*coords, coords0_reduced, sizeof(coords0_reduced)); FOR_EACH(c, 0, ncomps) { - FOR_EACH(i, 0, nverts[c]) { - CHK(scpr_polygon_get_position(polygon, c, i, pos) == RES_OK); + size_t count; + OK(scpr_polygon_get_vertices_count(polygon, c, &count)); + FOR_EACH(i, 0, count) { + OK(scpr_polygon_get_position(polygon, c, i, pos)); 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); + BAD(scpr_polygon_create_copy(NULL, NULL, NULL)); + BAD(scpr_polygon_create_copy(NULL, NULL, &copy)); + BAD(scpr_polygon_create_copy(NULL, polygon, NULL)); + OK(scpr_polygon_create_copy(dev, polygon, &copy)); + OK(scpr_polygon_eq(polygon, copy, &eq)); 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); + BAD(scpr_offset_polygon(NULL, 0, SCPR_JOIN_TYPES_COUNT__)); + BAD(scpr_offset_polygon(polygon, 0, SCPR_JOIN_TYPES_COUNT__)); + BAD(scpr_offset_polygon(NULL, 0, SCPR_JOIN_MITER)); + OK(scpr_offset_polygon(polygon, 0, SCPR_JOIN_MITER)); + + BAD(scpr_polygon_dump_to_obj(NULL, NULL)); + BAD(scpr_polygon_dump_to_obj(polygon, NULL)); + BAD(scpr_polygon_dump_to_obj(NULL, stream)); + BAD(scpr_polygon_dump_to_obj(polygon, stream)); + stream = tmpfile(); + OK(scpr_polygon_dump_to_obj(polygon, stream)); + fclose(stream); + stream = NULL; + + BAD(scpr_polygon_dump_component_to_obj(NULL, ncomps, NULL)); + BAD(scpr_polygon_dump_component_to_obj(polygon, ncomps, NULL)); + BAD(scpr_polygon_dump_component_to_obj(NULL, 0, NULL)); + BAD(scpr_polygon_dump_component_to_obj(NULL, ncomps, stream)); + BAD(scpr_polygon_dump_component_to_obj(polygon, ncomps, stream)); + BAD(scpr_polygon_dump_component_to_obj(polygon, 0, stream)); + stream = tmpfile(); + OK(scpr_polygon_dump_component_to_obj(polygon, 0, stream)); + fclose(stream); + + OK(scpr_device_ref_put(dev)); + OK(scpr_polygon_ref_put(polygon)); + OK(scpr_polygon_ref_put(copy)); MEM_RM(&allocator, *coords); MEM_RM(&allocator, coords); diff --git a/src/test_scpr_utils.h b/src/test_scpr_utils.h @@ -16,8 +16,16 @@ #ifndef TEST_CPR_UTILS_H #define TEST_CPR_UTILS_H +#define _POSIX_C_SOURCE 200112L + +#include "scpr.h" #include <rsys/mem_allocator.h> #include <stdio.h> +#include <math.h> + +#define ERR(Expr) { if((res = (Expr)) != RES_OK) goto error; } (void)0 +#define BAD(Expr) CHK((Expr) == RES_BAD_ARG); +#define OK(Expr) CHK((Expr) == RES_OK); struct polygon_context { double** coords; @@ -28,7 +36,7 @@ struct polygon_context { static INLINE void pget_nverts(const size_t icomp, size_t* nverts, void* context) { - const struct polygon_context* ctx = context; + const struct polygon_context* ctx = (const struct polygon_context*)context; CHK(nverts != NULL); CHK(context != NULL); CHK(icomp < ctx->ncomps); @@ -38,7 +46,7 @@ pget_nverts(const size_t icomp, size_t* nverts, void* context) static INLINE void pget_pos(const size_t icomp, const size_t ivert, double pos[2], void* context) { - const struct polygon_context* ctx = context; + const struct polygon_context* ctx = (const struct polygon_context*)context; CHK(pos != NULL); CHK(context != NULL); CHK(ctx->coords != NULL); @@ -58,7 +66,7 @@ struct mesh_context { static INLINE void mget_pos(const size_t ivert, double pos[2], void* context) { - const struct mesh_context* ctx = context; + const struct mesh_context* ctx = (const struct mesh_context*)context; CHK(pos != NULL); CHK(context != NULL); CHK(ctx->coords != NULL); @@ -70,7 +78,7 @@ mget_pos(const size_t ivert, double pos[2], void* context) static INLINE void mget_ids(const size_t itri, size_t ids[3], void* context) { - const struct mesh_context* ctx = context; + const struct mesh_context* ctx = (const struct mesh_context*)context; CHK(ids != NULL); CHK(context != NULL); CHK(ctx->indices != NULL); @@ -91,4 +99,35 @@ check_memory_allocator(struct mem_allocator* allocator) } } +INLINE int +check_stability + (struct scpr_device* dev, + const struct scpr_polygon* polygon) +{ + res_T res = RES_OK; + size_t i, j, ccount, vcount; + ASSERT(dev && polygon); + + ERR(scpr_polygon_get_components_count(polygon, &ccount)); + + for(i = 0; i < ccount; i++) { + ERR(scpr_polygon_get_vertices_count(polygon, i, &vcount)); + for(j = 0; j < vcount; j++) { + double pt[2], tmp[2]; + int64_t tmp2[2]; + ERR(scpr_polygon_get_position(polygon, i, j, pt)); + ERR(scpr_device_scale(dev, pt, 2, tmp2)); + ERR(scpr_device_unscale(dev, tmp2, 2, tmp)); + if(tmp[0] != pt[0] || tmp[1] != pt[1]) { + res = RES_BAD_ARG; + goto error; + } + } + } +end: + return (res == RES_OK); +error: + goto end; +} + #endif /* TEST_CPR_UTILS_H */