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, ©)); 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 }