star-cpr

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

scpr_polygon.c (18741B)


      1 /* Copyright (C) 2016-2018, 2021-2024 |Méso|Star> (contact@meso-star.com)
      2  *
      3  * This program is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published by
      5  * the Free Software Foundation, either version 3 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     11  * GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License
     14  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     15 
     16 #include "scpr.h"
     17 #include "scpr_c.h"
     18 
     19 #include <polygon.h>
     20 #include <rsys/logger.h>
     21 #include <rsys/ref_count.h>
     22 #include <rsys/mem_allocator.h>
     23 #include <rsys/rsys.h>
     24 #include <rsys/float2.h>
     25 
     26 /*******************************************************************************
     27  * Helper functions
     28  ******************************************************************************/
     29 static FINLINE Clipper2Lib::JoinType
     30 scpr_join_type_to_clipper_join_type(const enum scpr_join_type t)
     31 {
     32   Clipper2Lib::JoinType jtype;
     33   switch(t) {
     34     case SCPR_JOIN_SQUARE: jtype = Clipper2Lib::JoinType::Square; break;
     35     case SCPR_JOIN_ROUND: jtype = Clipper2Lib::JoinType::Round; break;
     36     case SCPR_JOIN_MITER: jtype = Clipper2Lib::JoinType::Miter; break;
     37     default: FATAL("Unreachable code\n"); break;
     38   }
     39   return jtype;
     40 }
     41 
     42 static void
     43 polygon_release(ref_T* ref)
     44 {
     45   struct scpr_polygon* polygon;
     46   struct mem_allocator* allocator;
     47   ASSERT(ref);
     48   polygon = CONTAINER_OF(ref, struct scpr_polygon, ref);
     49   allocator = polygon->device->allocator;
     50   SCPR(device_ref_put(polygon->device));
     51   /* Call destructor for paths */
     52   polygon->paths.Clipper2Lib::Paths64::~Paths64();
     53   MEM_RM(allocator, polygon);
     54 }
     55 
     56 static int
     57 path_is_eq
     58   (const Clipper2Lib::Path64* p1,
     59    const Clipper2Lib::Path64* p2)
     60 {
     61   size_t i, first_vtx, sz;
     62   int opposite_cw;
     63   ASSERT(p1 && p2);
     64   sz = p1->size();
     65 
     66   if(sz != p2->size()) {
     67     return 0;
     68   }
     69   FOR_EACH(opposite_cw, 0, 2) {
     70     FOR_EACH(first_vtx, 0, sz) {
     71       int eq = 1;
     72       FOR_EACH(i, 0, sz) {
     73        size_t n;
     74        if(opposite_cw) n = sz - 1 - (i + first_vtx) % sz;
     75        else n = (i + first_vtx) % sz;
     76         if((*p1)[i] != (*p2)[n]) {
     77           eq = 0;
     78           break; /* This opposite_cw/fstv failed: try next */
     79         }
     80       }
     81       /* Could prove p1 == p2 */
     82       if(eq) return 1;
     83     }
     84   }
     85   return 0;
     86 }
     87 
     88 static int
     89 one_path_is_eq
     90   (const Clipper2Lib::Paths64* pp,
     91    const Clipper2Lib::Path64* p,
     92    char* matched)
     93 {
     94   size_t i, sz;
     95   ASSERT(pp && p && matched);
     96   sz = pp->size();
     97   FOR_EACH(i, 0, sz) {
     98     if(matched[i]) continue;
     99     if(path_is_eq(&(*pp)[i], p)) {
    100      matched[i] = 1;
    101      return 1;
    102     }
    103   }
    104   return 0;
    105 }
    106 
    107 /*******************************************************************************
    108  * Exported functions
    109  ******************************************************************************/
    110 res_T
    111 scpr_polygon_create
    112   (struct scpr_device* dev,
    113    struct scpr_polygon** out_polygon)
    114 {
    115   struct scpr_polygon* polygon = NULL;
    116   res_T res = RES_OK;
    117 
    118   if(!dev || !out_polygon) {
    119     res = RES_BAD_ARG;
    120     goto error;
    121   }
    122 
    123   polygon = (struct scpr_polygon*)
    124     MEM_CALLOC(dev->allocator, 1, sizeof(struct scpr_polygon));
    125   if(!polygon) {
    126     res = RES_MEM_ERR;
    127     goto error;
    128   }
    129   ref_init(&polygon->ref);
    130   polygon->device = dev;
    131   SCPR(device_ref_get(dev));
    132   /* Allocate paths the C++ way (placement new) */
    133   new (&polygon->paths) Clipper2Lib::PathsD;
    134   polygon->lower[0] = polygon->lower[1] = INT64_MAX;
    135   polygon->upper[0] = polygon->upper[1] = INT64_MIN;
    136 
    137 exit:
    138   if(out_polygon) *out_polygon = polygon;
    139   return res;
    140 
    141 error:
    142   if(polygon) {
    143     SCPR(polygon_ref_put(polygon));
    144     polygon = NULL;
    145   }
    146   goto exit;
    147 }
    148 
    149 res_T
    150 scpr_polygon_ref_get
    151   (struct scpr_polygon* polygon)
    152 {
    153   if(!polygon) return RES_BAD_ARG;
    154   ref_get(&polygon->ref);
    155   return RES_OK;
    156 }
    157 
    158 res_T
    159 scpr_polygon_ref_put
    160   (struct scpr_polygon* polygon)
    161 {
    162   if(!polygon) return RES_BAD_ARG;
    163   ref_put(&polygon->ref, polygon_release);
    164   return RES_OK;
    165 }
    166 
    167 res_T
    168 scpr_polygon_setup_indexed_vertices
    169   (struct scpr_polygon* polygon,
    170    const size_t ncomponents,
    171    void (*get_nverts)(const size_t icomponent, size_t *nverts, void* ctx),
    172    void (*get_position)
    173      (const size_t icomponent, const size_t ivert, double pos[2], void* ctx),
    174    void* data)
    175 {
    176   size_t c;
    177   struct scpr_device* dev;
    178   Clipper2Lib::Paths64 paths;
    179   int changedc, changedv = 0;
    180   res_T res = RES_OK;
    181 
    182   if(!polygon || !get_nverts || !get_position || !data) {
    183     res = RES_BAD_ARG;
    184     goto error;
    185   }
    186 
    187   if(ncomponents > UINT32_MAX) {
    188     logger_print(polygon->device->logger, LOG_ERROR,
    189         "Too many components.\n");
    190     res = RES_BAD_ARG;
    191     goto error;
    192   }
    193 
    194   dev = polygon->device;
    195   TRY(paths.resize(ncomponents));
    196 
    197   FOR_EACH(c, 0, ncomponents) {
    198     size_t i, nverts;
    199 
    200     /* Get count for connex component c */
    201     get_nverts(c, &nverts, data);
    202     if(nverts > UINT32_MAX) {
    203       logger_print(polygon->device->logger, LOG_ERROR,
    204           "Too many vertices for component %zu.\n", c);
    205       res = RES_BAD_ARG;
    206       goto error;
    207     }
    208     TRY(paths[c].resize(nverts));
    209 
    210     /* Fetch polygon positions for connex component c */
    211     FOR_EACH(i, 0, nverts) {
    212       double tmp[2];
    213       int64_t tmp64[2];
    214       Clipper2Lib::Point64 pt;
    215       get_position(c, i, tmp, data);
    216       /* Check range and truncate precision to ensure further consistency */
    217       ERR(scpr_device_scale(dev, tmp, 2, tmp64));
    218       pt.x = tmp64[0];
    219       pt.y = tmp64[1];
    220       paths[c][i] = pt;
    221     }
    222   }
    223 
    224   /* Merge vertices, ... */
    225   TRY(polygon->paths = Clipper2Lib::SimplifyPaths(paths, dev->inv_scale));
    226   changedc = (ncomponents != polygon->paths.size());
    227   for(c = 0; !changedv && c < polygon->paths.size(); c++) {
    228     size_t nv;
    229     get_nverts(c, &nv, data);
    230     changedv |= (nv != polygon->paths[c].size());
    231   }
    232   if(changedc || changedv) {
    233     /* TODO: emit a warning log */
    234     logger_print(dev->logger, LOG_WARNING,
    235         "Polygon has been simplified. Original counts are no longer valid.\n");
    236   }
    237 
    238   /* Build bounding box (don't use ncomponents nor get_nverts just in case
    239    * counts are no longer valid!) */
    240   polygon->lower[0] = polygon->lower[1] = INT64_MAX;
    241   polygon->upper[0] = polygon->upper[1] = INT64_MIN;
    242   FOR_EACH(c, 0, polygon->paths.size()) {
    243     size_t i;
    244     FOR_EACH(i, 0, polygon->paths[c].size()) {
    245       int64_t tmp[2];
    246       int j;
    247       tmp[0] = polygon->paths[c][i].x;
    248       tmp[1] = polygon->paths[c][i].y;
    249       for(j = 0; j < 2; j++) {
    250         if(polygon->lower[j] > tmp[j]) polygon->lower[j] = tmp[j];
    251         if(polygon->upper[j] < tmp[j]) polygon->upper[j] = tmp[j];
    252       }
    253     }
    254   }
    255 
    256 exit:
    257   return res;
    258 error:
    259   if(polygon) {
    260     polygon->paths.clear();
    261     polygon->lower[0] = polygon->lower[1] = INT64_MAX;
    262     polygon->upper[0] = polygon->upper[1] = INT64_MIN;
    263   }
    264   goto exit;
    265 }
    266 
    267 res_T
    268 scpr_polygon_in_bbox
    269   (struct scpr_polygon* polygon,
    270    const double lower[2],
    271    const double upper[2],
    272    int* inside)
    273 {
    274   int i, in = 1;
    275   int64_t low[2], up[2];
    276   res_T res = RES_OK;
    277 
    278   if(!polygon || !lower || !upper || !inside) {
    279     res = RES_BAD_ARG;
    280     goto error;
    281   }
    282 
    283   SCPR(device_scale(polygon->device, lower, 2, low));
    284   SCPR(device_scale(polygon->device, upper, 2, up));
    285   for(i = 0; i < 2; i++) {
    286     if(polygon->lower[i] < low[i] || polygon->upper[i] > up[i]) {
    287       in = 0;
    288       break;
    289     }
    290   }
    291 
    292   *inside = in;
    293 exit:
    294   return res;
    295 error:
    296   goto exit;
    297 }
    298 
    299 res_T
    300 scpr_polygon_create_copy
    301   (struct scpr_device* dev,
    302    const struct scpr_polygon* src_polygon,
    303    struct scpr_polygon** out_polygon)
    304 {
    305   struct scpr_polygon* copy = NULL;
    306   int i;
    307   res_T res = RES_OK;
    308 
    309   if(!dev || !src_polygon || !out_polygon) {
    310     res = RES_BAD_ARG;
    311     goto error;
    312   }
    313   ERR(scpr_polygon_create(dev, &copy));
    314 
    315   copy->paths = src_polygon->paths;
    316   for(i = 0; i < 2; i++) {
    317     copy->lower[i] = src_polygon->lower[i];
    318     copy->upper[i] = src_polygon->upper[i];
    319   }
    320 
    321 exit:
    322   if(out_polygon) *out_polygon = copy;
    323   return res;
    324 error:
    325   if(copy) SCPR(polygon_ref_put(copy));
    326   copy = NULL;
    327   goto exit;
    328 }
    329 
    330 res_T
    331 scpr_polygon_get_components_count
    332   (const struct scpr_polygon* polygon,
    333    size_t* ncomps)
    334 {
    335   if(!polygon || !ncomps) {
    336     return RES_BAD_ARG;
    337   }
    338   *ncomps = polygon->paths.size();
    339   return RES_OK;
    340 }
    341 
    342 res_T
    343 scpr_polygon_get_vertices_count
    344   (const struct scpr_polygon* polygon,
    345    const size_t icomponent,
    346    size_t* nverts)
    347 {
    348   if(!polygon || !nverts || icomponent >= polygon->paths.size()) {
    349     return RES_BAD_ARG;
    350   }
    351   *nverts = polygon->paths[icomponent].size();
    352   return RES_OK;
    353 }
    354 
    355 res_T
    356 scpr_polygon_get_position
    357   (const struct scpr_polygon* polygon,
    358    const size_t icomponent,
    359    const size_t ivert,
    360    double pos[2])
    361 {
    362   size_t nverts;
    363   const size_t i = ivert;
    364   const Clipper2Lib::Point64* pt;
    365   int64_t pos64[2];
    366   if(!polygon || !pos || icomponent >= polygon->paths.size()) {
    367     return RES_BAD_ARG;
    368   }
    369   SCPR(polygon_get_vertices_count(polygon, icomponent, &nverts));
    370   if(ivert >= nverts) return RES_BAD_ARG;
    371   pt = &polygon->paths[icomponent][i];
    372   pos64[0] = pt->x;
    373   pos64[1] = pt->y;
    374   SCPR(device_unscale(polygon->device, pos64, 2, pos));
    375   return RES_OK;
    376 }
    377 
    378 res_T
    379 scpr_offset_polygon
    380   (struct scpr_polygon* poly_desc,
    381    const double offset, /* Can be either positive or negative */
    382    const enum scpr_join_type join_type)
    383 {
    384   size_t c;
    385   Clipper2Lib::Path64 tmp;
    386   struct scpr_device* dev;
    387   Clipper2Lib::JoinType cjt;
    388   Clipper2Lib::Paths64 polygon;
    389   int64_t offset64;
    390   res_T res = RES_OK;
    391 
    392   if(!poly_desc) {
    393     res = RES_BAD_ARG;
    394     goto error;
    395   }
    396 
    397   dev = poly_desc->device;
    398 
    399   /* Check offset polygon will be in-range */
    400   if(offset > 0) {
    401     int i;
    402     double range[2];
    403     SCPR(device_get_range(dev, range));
    404     for(i = 0; i < 2; i++) {
    405       if((double)poly_desc->lower[i] - offset < range[0]
    406           || (double)poly_desc->upper[i] + offset > range[1])
    407       {
    408         res = RES_BAD_ARG;
    409         goto error;
    410       }
    411     }
    412   }
    413 
    414   /* Check join type */
    415   switch(join_type) {
    416     case SCPR_JOIN_SQUARE:
    417     case SCPR_JOIN_ROUND:
    418     case SCPR_JOIN_MITER:
    419       break;
    420     default:
    421       res = RES_BAD_ARG;
    422       goto error;
    423   }
    424 
    425   ERR(scpr_device_scale(dev, &offset, 1, &offset64));
    426   cjt = scpr_join_type_to_clipper_join_type(join_type);
    427   TRY(poly_desc->paths = Clipper2Lib::InflatePaths(poly_desc->paths,
    428         (double)offset64, cjt, Clipper2Lib::EndType::Polygon));
    429 
    430   /* Rebuild AABB */
    431   poly_desc->lower[0] = poly_desc->lower[1] = INT64_MAX;
    432   poly_desc->upper[0] = poly_desc->upper[1] = INT64_MIN;
    433   FOR_EACH(c, 0, poly_desc->paths.size()) {
    434     size_t i, nverts;
    435     nverts = poly_desc->paths[c].size();
    436 
    437     FOR_EACH(i, 0, nverts) {
    438       int64_t pos64[2];
    439       int j;
    440       pos64[0] = poly_desc->paths[c][i].x;
    441       pos64[1] = poly_desc->paths[c][i].y;
    442       for(j = 0; j < 2; j++) {
    443         if(poly_desc->lower[j] > pos64[j]) poly_desc->lower[j] = pos64[j];
    444         if(poly_desc->upper[j] < pos64[j]) poly_desc->upper[j] = pos64[j];
    445       }
    446     }
    447   }
    448 
    449 exit:
    450   return res;
    451 error:
    452   goto exit;
    453 }
    454 
    455 res_T
    456 scpr_polygon_is_component_cw
    457   (const struct scpr_polygon* polygon,
    458    const size_t icomponent,
    459    int* cw)
    460 {
    461   res_T res = RES_OK;
    462 
    463   if(!polygon || !cw || icomponent > polygon->paths.size()) {
    464     res = RES_BAD_ARG;
    465     goto error;
    466   }
    467 
    468   *cw = !Clipper2Lib::IsPositive(polygon->paths[icomponent]);
    469 
    470 exit:
    471   return res;
    472 error:
    473   goto exit;
    474 }
    475 
    476 res_T
    477 scpr_polygon_reverse_component
    478   (struct scpr_polygon* polygon,
    479    const size_t icomponent)
    480 {
    481   res_T res = RES_OK;
    482   Clipper2Lib::Point64* data;
    483   size_t i, j;
    484 
    485   if(!polygon || icomponent > polygon->paths.size()) {
    486     res = RES_BAD_ARG;
    487     goto error;
    488   }
    489 
    490   i = 0;
    491   j = polygon->paths[icomponent].size();
    492   data = polygon->paths[icomponent].data();
    493   while(i != j && i != --j) {
    494     SWAP(Clipper2Lib::Point64, data[i], data[j]);
    495     i++; /* Not in SWAP macro! */
    496   }
    497 
    498 exit:
    499   return res;
    500 error:
    501   goto exit;
    502 }
    503 
    504 res_T
    505 scpr_polygon_eq
    506   (const struct scpr_polygon* polygon1,
    507    const struct scpr_polygon* polygon2,
    508    int* is_eq)
    509 {
    510   size_t i, sz;
    511   char* matched = NULL;
    512   res_T res = RES_OK;
    513 
    514   if(!polygon1 || !polygon2 || !is_eq) {
    515     res = RES_BAD_ARG;
    516     goto error;
    517   }
    518 
    519   sz = polygon1->paths.size();
    520   if(sz != polygon2->paths.size()) {
    521     *is_eq = 0;
    522     goto exit;
    523   }
    524   if(polygon1->lower[0] != polygon2->lower[0]
    525       || polygon1->lower[1] != polygon2->lower[1]
    526       || polygon1->upper[0] != polygon2->upper[0]
    527       || polygon1->upper[1] != polygon2->upper[1])
    528   {
    529     *is_eq = 0;
    530     goto exit;
    531   }
    532 
    533   /* Check actual coordinates */
    534   matched = (char*)calloc(sz, sizeof(*matched));
    535   FOR_EACH(i, 0, sz) {
    536     if(!one_path_is_eq(&polygon1->paths, &polygon2->paths[i], matched)) {
    537       *is_eq = 0;
    538       goto exit;
    539     }
    540   }
    541   *is_eq = 1;
    542 
    543 exit:
    544   free(matched);
    545   return res;
    546 error:
    547   goto exit;
    548 }
    549 
    550 res_T
    551 scpr_get_vertex_in_component
    552   (const struct scpr_polygon* polygon,
    553    const size_t icomponent,
    554    double vertex[2])
    555 {
    556   size_t i, p1sz;
    557   Clipper2Lib::Point64 p0, c;
    558   Clipper2Lib::PointInPolygonResult in;
    559   res_T res = RES_OK;
    560 
    561   if(!polygon || !vertex || icomponent >= polygon->paths.size()) {
    562     res = RES_BAD_ARG;
    563     goto error;
    564   }
    565 
    566   /* Find the center of the segment between vertex #0 and vertex #i
    567    * If it is inside the polygon return it */
    568   p0 = polygon->paths[icomponent][0];
    569   p1sz = polygon->paths[icomponent].size();
    570   for(i = 2; i < polygon->paths[0].size(); i++) {
    571     Clipper2Lib::Point64 pi = polygon->paths[icomponent][i];
    572     if(p1sz == 3) {
    573       /* Special case of a triangle: get the barycenter */
    574       Clipper2Lib::Point64 p1 = polygon->paths[icomponent][1];
    575       c = (p0 + p1 + pi) * 0.3333333;
    576     } else {
    577       c = (p0 + pi) * 0.5;
    578     }
    579     TRY(in = Clipper2Lib::PointInPolygon(c, polygon->paths[icomponent]));
    580     if(in == Clipper2Lib::PointInPolygonResult::IsOn) {
    581       int64_t tmp64[2];
    582       tmp64[0] = c.x; tmp64[1] = c.y;
    583       ERR(scpr_device_unscale(polygon->device, tmp64, 2, vertex));
    584       break; /* Found! */
    585     }
    586   }
    587   /* Should not be there: the component is either ill-formed or too thin to host
    588    * a vertex in its inside! */
    589   res = RES_BAD_ARG;
    590 
    591 exit:
    592   return res;
    593 error:
    594   goto exit;
    595 }
    596 
    597 res_T
    598 scpr_is_vertex_in_component
    599   (const struct scpr_polygon* polygon,
    600    const size_t icomponent,
    601    const double vertex[2],
    602    int* situation)
    603 {
    604   res_T res = RES_OK;
    605   int64_t tmp64[2];
    606   Clipper2Lib::Point64 pt;
    607   Clipper2Lib::PointInPolygonResult in;
    608 
    609   if(!polygon || !vertex || !situation || icomponent >= polygon->paths.size()) {
    610     res = RES_BAD_ARG;
    611     goto error;
    612   }
    613 
    614   ERR(scpr_device_scale(polygon->device, vertex, 2, tmp64));
    615   TRY(pt.Init(SPLIT2(tmp64)));
    616   TRY(in = Clipper2Lib::PointInPolygon(pt, polygon->paths[icomponent]));
    617   switch(in) {
    618     case Clipper2Lib::PointInPolygonResult::IsOn:
    619       *situation = 0;
    620       break;
    621     case Clipper2Lib::PointInPolygonResult::IsOutside:
    622       *situation = -1;
    623       break;
    624     case Clipper2Lib::PointInPolygonResult::IsInside:
    625       *situation = +1;
    626       break;
    627   }
    628 
    629 exit:
    630   return res;
    631 error:
    632   goto exit;
    633 }
    634 
    635 res_T
    636 scpr_is_component_in_component
    637   (const struct scpr_polygon* polygon1,
    638    const size_t icomponent1,
    639    const struct scpr_polygon* polygon2,
    640    const size_t icomponent2,
    641    int* c1_is_in_c2)
    642 {
    643   size_t i, p1sz;
    644   int is_in = -1;
    645   Clipper2Lib::PointInPolygonResult in;
    646   const Clipper2Lib::Path64* comp1;
    647   const Clipper2Lib::Path64* comp2;
    648   res_T res = RES_OK;
    649 
    650   if(!polygon1 || icomponent1 >= polygon1->paths.size()
    651      || !polygon2 || icomponent2 >= polygon2->paths.size()
    652      || ! c1_is_in_c2)
    653   {
    654     res = RES_BAD_ARG;
    655     goto error;
    656   }
    657 
    658   /* comp1 == comp2 is reported as bad arg.
    659    * This API requires comp1 and comp2 to not overlap (they can be adjoining),
    660    * this allows to exit early as soon as 1 vertex is either inside or outside */
    661   comp1 = &polygon1->paths[icomponent1];
    662   comp2 = &polygon2->paths[icomponent2];
    663   p1sz = comp1->size();
    664   for(i = 0; i < p1sz; i++) {
    665     Clipper2Lib::Point64 p = (*comp1)[i];
    666     TRY(in = Clipper2Lib::PointInPolygon(p, (*comp2)));
    667     switch(in) {
    668       case Clipper2Lib::PointInPolygonResult::IsOutside:
    669         is_in = 0;
    670         break;
    671       case Clipper2Lib::PointInPolygonResult::IsOn:
    672         /* Cannot decide based on this */
    673         break;
    674       case Clipper2Lib::PointInPolygonResult::IsInside:
    675         is_in = 1;
    676         break;
    677     }
    678     if(is_in >= 0) break; /* Early exit */
    679   }
    680   if(is_in == -1) {
    681     /* Every vertex of comp1 is on comp2: comp1 is either equal to comp2, or it
    682      * is inside */
    683     int is_eq = path_is_eq(comp1, comp2);
    684     if(!is_eq) {
    685       is_in = 1;
    686     } else {
    687       res = RES_BAD_ARG;
    688       goto error;
    689     }
    690   }
    691 
    692   * c1_is_in_c2 = is_in;
    693 
    694 exit:
    695   return res;
    696 error:
    697   goto exit;
    698 }
    699 
    700 res_T
    701 scpr_polygon_dump_to_obj
    702   (struct scpr_polygon* polygon,
    703    FILE* stream)
    704 {
    705   res_T res = RES_OK;
    706   size_t i, j, n = 1;
    707 
    708   if(!polygon || !stream) {
    709     res = RES_BAD_ARG;
    710     goto error;
    711   }
    712 
    713   /* Vertice */
    714   for(i = 0; i < polygon->paths.size(); i++) {
    715     for(j = 0; j < polygon->paths[i].size(); j++) {
    716       double tmpd[2];
    717       float tmpf[2];
    718       int64_t tmp64[2];
    719       Clipper2Lib::Point64& pt = polygon->paths[i][j];
    720       tmp64[0] = pt.x;
    721       tmp64[1] = pt.y;
    722       ERR(scpr_device_unscale(polygon->device, tmp64, 2, tmpd));
    723       f2_set_d2(tmpf, tmpd);
    724       fprintf(stream, "v %.16g %.16g 0\n", tmpf[0], tmpf[1]);
    725     }
    726   }
    727 
    728   /* Lines */
    729   for(i = 0; i < polygon->paths.size(); i++) {
    730     size_t fst = n;
    731     fprintf(stream, "l ");
    732     for(j = 0; j < polygon->paths[i].size(); j++) {
    733       fprintf(stream, " %zu", n++);
    734     }
    735     fprintf(stream, " %zu\n", fst);
    736   }
    737 
    738 exit:
    739   return res;
    740 error:
    741   goto exit;
    742 }
    743 
    744 res_T
    745 scpr_polygon_dump_component_to_obj
    746   (struct scpr_polygon* polygon,
    747    const size_t icomponent,
    748    FILE* stream)
    749 {
    750   res_T res = RES_OK;
    751   size_t i;
    752 
    753   if(!polygon || !stream || icomponent >= polygon->paths.size()) {
    754     res = RES_BAD_ARG;
    755     goto error;
    756   }
    757 
    758   /* Vertice */
    759   for(i = 0; i < polygon->paths[icomponent].size(); i++) {
    760     double tmpd[2];
    761     float tmpf[2];
    762     int64_t tmp64[2];
    763     Clipper2Lib::Point64& pt = polygon->paths[icomponent][i];
    764     tmp64[0] = pt.x;
    765     tmp64[1] = pt.y;
    766     ERR(scpr_device_unscale(polygon->device, tmp64, 2, tmpd));
    767     f2_set_d2(tmpf, tmpd);
    768     fprintf(stream, "v %.16g %.16g 0\n", tmpf[0], tmpf[1]);
    769   }
    770 
    771   /* Line */
    772   fprintf(stream, "l ");
    773   for(i = 0; i < polygon->paths[icomponent].size(); i++) {
    774     fprintf(stream, " %zu", ++i);
    775   }
    776   fprintf(stream, " 1\n");
    777 
    778 exit:
    779   return res;
    780 error:
    781   goto exit;
    782 }