KiCad PCB EDA Suite
Loading...
Searching...
No Matches
polygon_triangulation.h
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 *
23 * Based on Uniform Plane Subdivision algorithm from Lamot, Marko, and Borut Žalik.
24 * "A fast polygon triangulation algorithm based on uniform plane subdivision."
25 * Computers & graphics 27, no. 2 (2003): 239-253.
26 *
27 * Code derived from:
28 * K-3D which is Copyright (c) 2005-2006, Romain Behar, GPL-2, license above
29 * earcut which is Copyright (c) 2016, Mapbox, ISC
30 *
31 * ISC License:
32 * Permission to use, copy, modify, and/or distribute this software for any purpose
33 * with or without fee is hereby granted, provided that the above copyright notice
34 * and this permission notice appear in all copies.
35 *
36 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
37 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
38 * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
39 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
40 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
41 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
42 * THIS SOFTWARE.
43 *
44 */
45
46#ifndef __POLYGON_TRIANGULATION_H
47#define __POLYGON_TRIANGULATION_H
48
49#include <algorithm>
50#include <array>
51#include <deque>
52#include <cmath>
53#include <vector>
54
55#include <advanced_config.h>
58#include <geometry/vertex_set.h>
59#include <math/box2.h>
60#include <math/vector2d.h>
61
62#include <wx/log.h>
63
64// ADVANCED_CFG::GetCfg() cannot be used on msys2/mingw builds (link failure)
65// So we use the ADVANCED_CFG default values
66#if defined( __MINGW32__ )
67 #define TRIANGULATESIMPLIFICATIONLEVEL 50
68 #define TRIANGULATEMINIMUMAREA 1000
69#else
70 #define TRIANGULATESIMPLIFICATIONLEVEL ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel
71 #define TRIANGULATEMINIMUMAREA ADVANCED_CFG::GetCfg().m_TriangulateMinimumArea
72#endif
73
74#define TRIANGULATE_TRACE "triangulate"
75
77{
78public:
83
90 {
91 if( aPolygon.empty() )
92 return true;
93
94 if( aPolygon.size() == 1 )
95 return TesselatePolygon( aPolygon[0], aHintData );
96
97 const SHAPE_LINE_CHAIN& outline = aPolygon[0];
98
99 m_bbox = outline.BBox();
100
101 for( size_t i = 1; i < aPolygon.size(); i++ )
102 m_bbox.Merge( aPolygon[i].BBox() );
103
104 m_result.Clear();
105
106 if( !m_bbox.GetWidth() || !m_bbox.GetHeight() )
107 return true;
108
109 for( const SHAPE_LINE_CHAIN& chain : aPolygon )
110 {
111 for( const VECTOR2I& pt : chain.CPoints() )
112 m_result.AddVertex( pt );
113 }
114
115 int baseIndex = 0;
116 VERTEX* outerRing = createRing( outline, baseIndex, true );
117 baseIndex += outline.PointCount();
118
119 if( !outerRing || outerRing->prev == outerRing->next )
120 return true;
121
122 std::vector<VERTEX*> holeRings;
123
124 for( size_t i = 1; i < aPolygon.size(); i++ )
125 {
126 VERTEX* holeRing = createRing( aPolygon[i], baseIndex, false );
127 baseIndex += aPolygon[i].PointCount();
128
129 if( holeRing && holeRing->next != holeRing )
130 holeRings.push_back( holeRing );
131 }
132
134
135 if( !holeRings.empty() )
136 {
137 outerRing = eliminateHoles( outerRing, holeRings );
138
139 if( !outerRing )
140 {
141 wxLogTrace( TRIANGULATE_TRACE, "Hole elimination failed" );
142 return false;
143 }
144 }
145
146 if( VERTEX* simplified = simplifyList( outerRing ) )
147 outerRing = simplified;
148
149 outerRing->updateList();
150
151 auto retval = earcutList( outerRing );
152
153 if( !retval )
154 {
155 wxLogTrace( TRIANGULATE_TRACE, "Tesselation with holes failed, logging remaining vertices" );
156 logRemaining();
157 }
158
159 m_vertices.clear();
160 return retval;
161 }
162
165 {
166 m_bbox = aPoly.BBox();
167 m_result.Clear();
168
169 if( !m_bbox.GetWidth() || !m_bbox.GetHeight() )
170 return true;
171
175 VERTEX* firstVertex = createList( aPoly );
176
177 for( const VECTOR2I& pt : aPoly.CPoints() )
178 m_result.AddVertex( pt );
179
180 if( !firstVertex || firstVertex->prev == firstVertex->next )
181 return true;
182
183 wxLogTrace( TRIANGULATE_TRACE, "Created list with %f area", firstVertex->area() );
184
186
187 if( VERTEX* simplified = simplifyList( firstVertex ) )
188 firstVertex = simplified;
189
190 firstVertex->updateList();
191
198 if( aHintData && aHintData->Vertices().size() == m_result.GetVertexCount() )
199 {
200 m_result.SetTriangles( aHintData->Triangles() );
201 return true;
202 }
203 else
204 {
205 auto retval = earcutList( firstVertex );
206
207 if( !retval )
208 {
209 wxLogTrace( TRIANGULATE_TRACE, "Tesselation failed, logging remaining vertices" );
210 logRemaining();
211 }
212
213 m_vertices.clear();
214 return retval;
215 }
216 }
217
218 std::vector<double> PartitionAreaFractionsForTesting( const SHAPE_LINE_CHAIN& aPoly,
219 size_t aTargetLeaves ) const
220 {
221 std::vector<SHAPE_LINE_CHAIN> partitions = partitionPolygonBalanced( aPoly, aTargetLeaves );
222 std::vector<double> fractions;
223 double totalArea = std::abs( aPoly.Area() );
224
225 if( totalArea <= 0.0 )
226 return fractions;
227
228 fractions.reserve( partitions.size() );
229
230 for( const SHAPE_LINE_CHAIN& part : partitions )
231 fractions.push_back( std::abs( part.Area() ) / totalArea );
232
233 return fractions;
234 }
235private:
237 friend class SHAPE_POLY_SET;
238
240 {
243 };
244
245 bool collectScanlineHits( const SHAPE_LINE_CHAIN& aPoly, bool aVertical, int aCut,
246 std::array<SCANLINE_HIT, 2>& aHits ) const
247 {
248 int count = 0;
249
250 for( int ii = 0; ii < aPoly.PointCount(); ++ii )
251 {
252 const VECTOR2I& a = aPoly.CPoint( ii );
253 const VECTOR2I& b = aPoly.CPoint( ( ii + 1 ) % aPoly.PointCount() );
254
255 if( aVertical )
256 {
257 if( a.x == b.x || aCut <= std::min( a.x, b.x ) || aCut >= std::max( a.x, b.x ) )
258 continue;
259
260 if( count >= 2 )
261 return false;
262
263 double t = static_cast<double>( aCut - a.x ) / static_cast<double>( b.x - a.x );
264 int y = static_cast<int>( std::lround( a.y + t * ( b.y - a.y ) ) );
265 aHits[count++] = { ii, VECTOR2I( aCut, y ) };
266 }
267 else
268 {
269 if( a.y == b.y || aCut <= std::min( a.y, b.y ) || aCut >= std::max( a.y, b.y ) )
270 continue;
271
272 if( count >= 2 )
273 return false;
274
275 double t = static_cast<double>( aCut - a.y ) / static_cast<double>( b.y - a.y );
276 int x = static_cast<int>( std::lround( a.x + t * ( b.x - a.x ) ) );
277 aHits[count++] = { ii, VECTOR2I( x, aCut ) };
278 }
279 }
280
281 return count == 2;
282 }
283
284 SHAPE_LINE_CHAIN createSplitChild( const SHAPE_LINE_CHAIN& aPoly, int aStart, int aEnd ) const
285 {
286 SHAPE_LINE_CHAIN child;
287 int idx = aStart;
288 int guard = 0;
289 const int count = aPoly.PointCount();
290
291 do
292 {
293 child.Append( aPoly.CPoint( idx ) );
294 idx = ( idx + 1 ) % count;
295 ++guard;
296 } while( idx != ( aEnd + 1 ) % count && guard <= count + 2 );
297
298 child.SetClosed( true );
299 child.Simplify2( true );
300 return child;
301 }
302
303 bool splitPolygonAtCoordinate( const SHAPE_LINE_CHAIN& aPoly, bool aVertical, int aCut,
304 std::array<SHAPE_LINE_CHAIN, 2>& aChildren, double& aAreaA,
305 double& aAreaB ) const
306 {
307 std::array<SCANLINE_HIT, 2> hits;
308
309 if( !collectScanlineHits( aPoly, aVertical, aCut, hits ) )
310 return false;
311
312 SHAPE_LINE_CHAIN augmented( aPoly );
313 augmented.Split( hits[0].point, true );
314 augmented.Split( hits[1].point, true );
315
316 int idxA = augmented.Find( hits[0].point );
317 int idxB = augmented.Find( hits[1].point );
318
319 if( idxA < 0 || idxB < 0 || idxA == idxB )
320 return false;
321
322 aChildren[0] = createSplitChild( augmented, idxA, idxB );
323 aChildren[1] = createSplitChild( augmented, idxB, idxA );
324
325 if( aChildren[0].PointCount() < 3 || aChildren[1].PointCount() < 3 )
326 return false;
327
328 aAreaA = std::abs( aChildren[0].Area() );
329 aAreaB = std::abs( aChildren[1].Area() );
330 return aAreaA > 0.0 && aAreaB > 0.0;
331 }
332
334 std::array<SHAPE_LINE_CHAIN, 2>& aChildren ) const
335 {
336 const BOX2I bbox = aPoly.BBox();
337 const bool verticalFirst = bbox.GetWidth() >= bbox.GetHeight();
338 const double totalArea = std::abs( aPoly.Area() );
339
340 if( totalArea <= 0.0 )
341 return false;
342
343 auto tryAxis =
344 [&]( bool aVertical ) -> bool
345 {
346 const int low = ( aVertical ? bbox.GetX() : bbox.GetY() ) + 1;
347 const int high = ( aVertical ? bbox.GetRight() : bbox.GetBottom() ) - 1;
348
349 if( high <= low )
350 return false;
351
352 double bestImbalance = std::numeric_limits<double>::infinity();
353 std::array<SHAPE_LINE_CHAIN, 2> bestChildren;
354
355 constexpr int kSamples = 15;
356
357 for( int ii = 1; ii <= kSamples; ++ii )
358 {
359 int cut = low + ( ( high - low ) * ii ) / ( kSamples + 1 );
360 std::array<SHAPE_LINE_CHAIN, 2> candidate;
361 double areaA = 0.0;
362 double areaB = 0.0;
363
364 if( !splitPolygonAtCoordinate( aPoly, aVertical, cut, candidate, areaA, areaB ) )
365 continue;
366
367 double imbalance = std::abs( areaA - areaB ) / totalArea;
368
369 if( imbalance < bestImbalance )
370 {
371 bestImbalance = imbalance;
372 bestChildren = std::move( candidate );
373 }
374 }
375
376 if( !std::isfinite( bestImbalance ) || bestImbalance > 0.35 )
377 return false;
378
379 aChildren = std::move( bestChildren );
380 return true;
381 };
382
383 return tryAxis( verticalFirst ) || tryAxis( !verticalFirst );
384 }
385
386 std::vector<SHAPE_LINE_CHAIN> partitionPolygonBalanced( const SHAPE_LINE_CHAIN& aPoly,
387 size_t aTargetLeaves ) const
388 {
389 std::vector<SHAPE_LINE_CHAIN> leaves = { aPoly };
390
391 if( aTargetLeaves < 2 )
392 return leaves;
393
394 while( leaves.size() < aTargetLeaves )
395 {
396 int bestLeaf = -1;
397 double bestArea = 0.0;
398
399 for( size_t ii = 0; ii < leaves.size(); ++ii )
400 {
401 double area = std::abs( leaves[ii].Area() );
402
403 if( area > bestArea )
404 {
405 bestArea = area;
406 bestLeaf = static_cast<int>( ii );
407 }
408 }
409
410 if( bestLeaf < 0 )
411 break;
412
413 std::array<SHAPE_LINE_CHAIN, 2> children;
414
415 if( !splitPolygonBalanced( leaves[bestLeaf], children ) )
416 break;
417
418 leaves[bestLeaf] = std::move( children[0] );
419 leaves.push_back( std::move( children[1] ) );
420 }
421
422 return leaves;
423 }
424
426 {
427 constexpr size_t kVerticesPerLeaf = 50000;
428 constexpr size_t kMaxLeaves = 8;
429 size_t leaves = 1;
430
431 while( leaves < kMaxLeaves
432 && static_cast<size_t>( aPoly.PointCount() ) / leaves > kVerticesPerLeaf )
433 {
434 leaves *= 2;
435 }
436
437 return leaves;
438 }
439
444 {
445 std::set<VERTEX*> seen;
446 wxLog::EnableLogging();
447 for( VERTEX& p : m_vertices )
448 {
449 if( !p.next || p.next == &p || seen.find( &p ) != seen.end() )
450 continue;
451
452 logVertices( &p, &seen );
453 }
454 }
455
456 void logVertices( VERTEX* aStart, std::set<VERTEX*>* aSeen )
457 {
458 if( aSeen && aSeen->count( aStart ) )
459 return;
460
461 if( aSeen )
462 aSeen->insert( aStart );
463
464 int count = 1;
465 VERTEX* p = aStart->next;
466 wxString msg = wxString::Format( "Vertices: %d,%d,", static_cast<int>( aStart->x ),
467 static_cast<int>( aStart->y ) );
468
469 do
470 {
471 msg += wxString::Format( "%d,%d,", static_cast<int>( p->x ), static_cast<int>( p->y ) );
472
473 if( aSeen )
474 aSeen->insert( p );
475
476 p = p->next;
477 count++;
478 } while( p != aStart );
479
480 if( count < 3 ) // Don't log anything that only has 2 or fewer points
481 return;
482
483 msg.RemoveLast();
484 wxLogTrace( TRIANGULATE_TRACE, msg );
485 }
486
492 {
493 if( !aStart || aStart->next == aStart->prev )
494 return aStart;
495
496 VERTEX* p = aStart;
497 VERTEX* next = p->next;
498 VERTEX* retval = aStart;
499 int count = 0;
500
501 double sq_dist = TRIANGULATESIMPLIFICATIONLEVEL;
502 sq_dist *= sq_dist;
503
504 do
505 {
506 VECTOR2D diff = VECTOR2D( next->x - p->x, next->y - p->y );
507
508 if( diff.SquaredEuclideanNorm() < sq_dist )
509 {
510 if( next == aStart )
511 {
512 retval = p;
513 aStart->remove();
514 count++;
515 break;
516 }
517
518 next = next->next;
519 p->next->remove();
520 count++;
521 retval = p;
522 }
523 else
524 {
525 p = next;
526 next = next->next;
527 }
528 } while( p != aStart && next && p );
529
530 wxLogTrace( TRIANGULATE_TRACE, "Removed %d points in simplifyList", count );
531
532 if( count )
533 return retval;
534
535 return nullptr;
536 }
537
546 {
547 VERTEX* retval = nullptr;
548 size_t count = 0;
549
550 if( ( retval = simplifyList( aStart ) ) )
551 aStart = retval;
552
553 wxASSERT( aStart->next && aStart->prev );
554
555 VERTEX* p = aStart->next;
556
557 while( p != aStart && p->next && p->prev )
558 {
559 // We make a dummy triangle that is actually part of the existing line segment
560 // and measure its area. This will not be exactly zero due to floating point
561 // errors. We then look for areas that are less than 4 times the area of the
562 // dummy triangle. For small triangles, this is a small number
563 VERTEX tmp( 0, 0.5 * ( p->prev->x + p->next->x ), 0.5 * ( p->prev->y + p->next->y ), this );
564 double null_area = 4.0 * std::abs( area( p->prev, &tmp, p->next ) );
565
566 if( *p == *( p->next ) || std::abs( area( p->prev, p, p->next ) ) <= null_area )
567 {
568 // This is a spike, remove it, leaving only one point
569 if( *( p->next ) == *( p->prev ) )
570 p->next->remove();
571
572 p = p->prev;
573 p->next->remove();
574 retval = p;
575 ++count;
576
577 if( p == p->next )
578 break;
579
580 // aStart was removed above, so we need to reset it
581 if( !aStart->next )
582 aStart = p->prev;
583
584 continue;
585 }
586
587 p = p->next;
588 };
589
591 if( !p->next || p->next == p || p->next == p->prev )
592 return p;
593
594 // We needed an end point above that wouldn't be removed, so
595 // here we do the final check for this as a Steiner point
596 VERTEX tmp( 0, 0.5 * ( p->prev->x + p->next->x ),
597 0.5 * ( p->prev->y + p->next->y ), this );
598 double null_area = 4.0 * std::abs( area( p->prev, &tmp, p->next ) );
599
600 if( std::abs( area( p->prev, p, p->next ) ) <= null_area )
601 {
602 retval = p->next;
603 p->remove();
604 ++count;
605 }
606
607 wxLogTrace( TRIANGULATE_TRACE, "Removed %zu NULL triangles", count );
608
609 return retval;
610 }
611
625 bool earcutList( VERTEX* aPoint, int pass = 0 )
626 {
627 constexpr int kMaxRecursion = 64;
628
629 if( pass >= kMaxRecursion )
630 {
631 wxLogTrace( TRIANGULATE_TRACE, "earcutList recursion limit reached; aborting triangulation", pass );
632 return false;
633 }
634
635 wxLogTrace( TRIANGULATE_TRACE, "earcutList starting at %p for pass %d", aPoint, pass );
636
637 if( !aPoint )
638 return true;
639
640 VERTEX* stop = aPoint;
641 VERTEX* prev;
642 VERTEX* next;
643 int internal_pass = 1;
644 constexpr int kEarLookahead = 2;
645
646 while( aPoint->prev != aPoint->next )
647 {
648 prev = aPoint->prev;
649 next = aPoint->next;
650
651 VERTEX* bestEar = nullptr;
652 double bestScore = -1.0;
653 int lookahead = 0;
654
655 for( VERTEX* candidate = aPoint; candidate && lookahead < kEarLookahead;
656 candidate = candidate->next, ++lookahead )
657 {
658 if( !candidate->isEar() || isTooSmall( candidate ) )
659 continue;
660
661 const double score = earScore( candidate->prev, candidate, candidate->next );
662
663 if( !bestEar || score > bestScore )
664 {
665 bestEar = candidate;
666 bestScore = score;
667 }
668 }
669
670 if( bestEar )
671 {
672 prev = bestEar->prev;
673 next = bestEar->next;
674 m_result.AddTriangle( prev->i, bestEar->i, next->i );
675 bestEar->remove();
676
677 // Skip one vertex as the triangle will account for the prev node
678 aPoint = next->next;
679 stop = next->next;
680 continue;
681 }
682
683 VERTEX* nextNext = next->next;
684
685 if( *prev != *nextNext && intersects( prev, aPoint, next, nextNext ) &&
686 locallyInside( prev, nextNext ) &&
687 locallyInside( nextNext, prev ) )
688 {
689 wxLogTrace( TRIANGULATE_TRACE,
690 "Local intersection detected. Merging minor triangle with area %f",
691 area( prev, aPoint, nextNext ) );
692 m_result.AddTriangle( prev->i, aPoint->i, nextNext->i );
693
694 // remove two nodes involved
695 next->remove();
696 aPoint->remove();
697
698 aPoint = nextNext;
699 stop = nextNext;
700
701 continue;
702 }
703
704 aPoint = next;
705
706 /*
707 * We've searched the entire polygon for available ears and there are still
708 * un-sliced nodes remaining.
709 */
710 if( aPoint == stop && aPoint->prev != aPoint->next )
711 {
712 VERTEX* newPoint;
713
714 // Removing null triangles will remove steiner points as well as colinear points
715 // that are three in a row. Because our next step is to subdivide the polygon,
716 // we need to allow it to add the subdivided points first. This is why we only
717 // run the RemoveNullTriangles function after the first pass.
718 if( ( internal_pass == 2 ) && ( newPoint = removeNullTriangles( aPoint ) ) )
719 {
720 // There are no remaining triangles in the list
721 if( newPoint->next == newPoint->prev )
722 break;
723
724 aPoint = newPoint;
725 stop = newPoint;
726 continue;
727 }
728
729 ++internal_pass;
730
731 // This will subdivide the polygon 2 times. The first pass will add enough points
732 // such that each edge is less than the average edge length. If this doesn't work
733 // The next pass will remove the null triangles (above) and subdivide the polygon
734 // again, this time adding one point to each long edge (and thereby changing the locations)
735 if( internal_pass < 4 )
736 {
737 wxLogTrace( TRIANGULATE_TRACE, "Subdividing polygon" );
738 subdividePolygon( aPoint, internal_pass );
739 continue;
740 }
741
742 // If we don't have any NULL triangles left, cut the polygon in two and try again
743 wxLogTrace( TRIANGULATE_TRACE, "Splitting polygon" );
744
745 if( !splitPolygon( aPoint, pass + 1 ) )
746 return false;
747
748 break;
749 }
750 }
751
752 // Check to see if we are left with only three points in the polygon
753 if( aPoint->next && aPoint->prev == aPoint->next->next )
754 {
755 // Three concave points will never be able to be triangulated because they were
756 // created by an intersecting polygon, so just drop them.
757 if( area( aPoint->prev, aPoint, aPoint->next ) >= 0 )
758 return true;
759 }
760
761 /*
762 * At this point, our polygon should be fully tessellated.
763 */
764 if( aPoint->prev != aPoint->next )
765 return std::abs( aPoint->area() ) > TRIANGULATEMINIMUMAREA;
766
767 return true;
768 }
769
770
774
775 bool isTooSmall( const VERTEX* aPoint ) const
776 {
777 double min_area = TRIANGULATEMINIMUMAREA;
778 double prev_sq_len = ( aPoint->prev->x - aPoint->x ) * ( aPoint->prev->x - aPoint->x ) +
779 ( aPoint->prev->y - aPoint->y ) * ( aPoint->prev->y - aPoint->y );
780 double next_sq_len = ( aPoint->next->x - aPoint->x ) * ( aPoint->next->x - aPoint->x ) +
781 ( aPoint->next->y - aPoint->y ) * ( aPoint->next->y - aPoint->y );
782 double opp_sq_len = ( aPoint->next->x - aPoint->prev->x ) * ( aPoint->next->x - aPoint->prev->x ) +
783 ( aPoint->next->y - aPoint->prev->y ) * ( aPoint->next->y - aPoint->prev->y );
784
785 return ( prev_sq_len < min_area || next_sq_len < min_area || opp_sq_len < min_area );
786 }
787
788 double earScore( const VERTEX* a, const VERTEX* b, const VERTEX* c ) const
789 {
790 const double ab_sq = ( a->x - b->x ) * ( a->x - b->x ) + ( a->y - b->y ) * ( a->y - b->y );
791 const double bc_sq = ( b->x - c->x ) * ( b->x - c->x ) + ( b->y - c->y ) * ( b->y - c->y );
792 const double ca_sq = ( c->x - a->x ) * ( c->x - a->x ) + ( c->y - a->y ) * ( c->y - a->y );
793 const double norm = ab_sq + bc_sq + ca_sq;
794
795 if( norm <= 0.0 )
796 return 0.0;
797
798 return std::abs( area( a, b, c ) ) / norm;
799 }
800
805 VERTEX* createRing( const SHAPE_LINE_CHAIN& aPoints, int aBaseIndex, bool aWantCCW )
806 {
807 VERTEX* tail = nullptr;
808 double sum = 0.0;
809 VECTOR2L last_pt;
810 bool first = true;
811
812 for( int i = 0; i < aPoints.PointCount(); i++ )
813 {
814 VECTOR2D p1 = aPoints.CPoint( i );
815 VECTOR2D p2 = aPoints.CPoint( ( i + 1 ) % aPoints.PointCount() );
816 sum += ( ( p2.x - p1.x ) * ( p2.y + p1.y ) );
817 }
818
819 bool isCW = sum > 0.0;
820 bool needReverse = ( aWantCCW == isCW );
821
822 auto addVertex = [&]( int i )
823 {
824 const VECTOR2I& pt = aPoints.CPoint( i );
825
826 if( first || pt.SquaredDistance( last_pt ) > m_simplificationLevel )
827 {
828 tail = insertVertex( aBaseIndex + i, pt, tail );
829 last_pt = pt;
830 first = false;
831 }
832 };
833
834 if( needReverse )
835 {
836 for( int i = aPoints.PointCount() - 1; i >= 0; i-- )
837 addVertex( i );
838 }
839 else
840 {
841 for( int i = 0; i < aPoints.PointCount(); i++ )
842 addVertex( i );
843 }
844
845 if( tail && ( *tail == *tail->next ) )
846 tail->next->remove();
847
848 return tail;
849 }
850
856 VERTEX* eliminateHoles( VERTEX* aOuterRing, std::vector<VERTEX*>& aHoleRings )
857 {
858 struct HoleInfo
859 {
860 VERTEX* leftmost;
861 double leftX;
862 };
863
864 std::vector<HoleInfo> holes;
865 holes.reserve( aHoleRings.size() );
866
867 for( VERTEX* hole : aHoleRings )
868 {
869 VERTEX* leftmost = hole;
870 VERTEX* p = hole->next;
871
872 while( p != hole )
873 {
874 if( p->x < leftmost->x || ( p->x == leftmost->x && p->y < leftmost->y ) )
875 leftmost = p;
876
877 p = p->next;
878 }
879
880 holes.push_back( { leftmost, leftmost->x } );
881 }
882
883 std::sort( holes.begin(), holes.end(),
884 []( const HoleInfo& a, const HoleInfo& b ) { return a.leftX < b.leftX; } );
885
886 for( const HoleInfo& hi : holes )
887 {
888 VERTEX* bridge = findHoleBridge( hi.leftmost, aOuterRing );
889
890 if( bridge )
891 {
892 VERTEX* bridgeReverse = bridge->split( hi.leftmost );
893 filterPoints( aOuterRing, aOuterRing->next );
894 filterPoints( bridgeReverse, bridgeReverse->next );
895 }
896 else
897 {
898 wxLogTrace( TRIANGULATE_TRACE, "Failed to find bridge for hole at (%f, %f)",
899 hi.leftmost->x, hi.leftmost->y );
900 }
901 }
902
903 return aOuterRing;
904 }
905
909 void filterPoints( VERTEX* aStart, VERTEX* aEnd = nullptr )
910 {
911 if( !aStart )
912 return;
913
914 if( !aEnd )
915 aEnd = aStart;
916
917 VERTEX* p = aStart;
918 bool again;
919
920 do
921 {
922 again = false;
923
924 if( *p == *p->next )
925 {
926 p->next->remove();
927
928 if( p == p->next )
929 return;
930
931 p = p->prev;
932 again = true;
933 }
934 else
935 {
936 p = p->next;
937 }
938 } while( again || p != aEnd );
939 }
940
945 VERTEX* findHoleBridge( VERTEX* aHole, VERTEX* aOuterStart )
946 {
947 VERTEX* p = aOuterStart;
948 double hx = aHole->x;
949 double hy = aHole->y;
950 double qx = -std::numeric_limits<double>::infinity();
951 VERTEX* m = nullptr;
952
953 do
954 {
955 if( hy <= p->y && hy >= p->next->y && p->next->y != p->y )
956 {
957 double x = p->x + ( hy - p->y ) * ( p->next->x - p->x )
958 / ( p->next->y - p->y );
959
960 if( x <= hx && x > qx )
961 {
962 qx = x;
963
964 if( x == hx )
965 {
966 if( hy == p->y )
967 return p;
968
969 if( hy == p->next->y )
970 return p->next;
971 }
972
973 m = ( p->x < p->next->x ) ? p : p->next;
974 }
975 }
976
977 p = p->next;
978 } while( p != aOuterStart );
979
980 if( !m )
981 return nullptr;
982
983 if( hx == qx )
984 return m;
985
986 // Pick the vertex inside the visibility triangle closest to the ray
987 const VERTEX* stop = m;
988 double mx = m->x;
989 double my = m->y;
990 double tanMin = std::numeric_limits<double>::infinity();
991
992 p = m;
993
994 do
995 {
996 if( hx >= p->x && p->x >= mx && hx != p->x )
997 {
998 bool inside;
999
1000 if( hy < my )
1001 inside = triArea( hx, hy, mx, my, p->x, p->y ) >= 0
1002 && triArea( mx, my, qx, hy, p->x, p->y ) >= 0
1003 && triArea( qx, hy, hx, hy, p->x, p->y ) >= 0;
1004 else
1005 inside = triArea( qx, hy, mx, my, p->x, p->y ) >= 0
1006 && triArea( mx, my, hx, hy, p->x, p->y ) >= 0
1007 && triArea( hx, hy, qx, hy, p->x, p->y ) >= 0;
1008
1009 if( inside )
1010 {
1011 double t = std::abs( hy - p->y ) / ( hx - p->x );
1012
1013 if( locallyInside( p, aHole )
1014 && ( t < tanMin
1015 || ( t == tanMin
1016 && ( p->x > m->x
1017 || ( p->x == m->x
1018 && sectorContainsSector( m, p ) ) ) ) ) )
1019 {
1020 m = p;
1021 tanMin = t;
1022 }
1023 }
1024 }
1025
1026 p = p->next;
1027 } while( p != stop );
1028
1029 return m;
1030 }
1031
1035 static double triArea( double ax, double ay, double bx, double by,
1036 double cx, double cy )
1037 {
1038 return ( bx - ax ) * ( cy - ay ) - ( by - ay ) * ( cx - ax );
1039 }
1040
1045 bool sectorContainsSector( const VERTEX* m, const VERTEX* p ) const
1046 {
1047 return area( m->prev, m, p->prev ) < 0 && area( p->next, m, m->next ) < 0;
1048 }
1049
1053 void subdividePolygon( VERTEX* aStart, int pass = 0 )
1054 {
1055 VERTEX* p = aStart;
1056
1057 struct VertexComparator {
1058 bool operator()(const std::pair<VERTEX*,double>& a, const std::pair<VERTEX*,double>& b) const {
1059 return a.second > b.second;
1060 }
1061 };
1062
1063 std::set<std::pair<VERTEX*,double>, VertexComparator> longest;
1064 double avg = 0.0;
1065
1066 do
1067 {
1068 double len = ( p->x - p->next->x ) * ( p->x - p->next->x ) +
1069 ( p->y - p->next->y ) * ( p->y - p->next->y );
1070 longest.emplace( p, len );
1071
1072 avg += len;
1073 p = p->next;
1074 } while (p != aStart);
1075
1076 avg /= longest.size();
1077 wxLogTrace( TRIANGULATE_TRACE, "Average length: %f", avg );
1078
1079 constexpr double kSubdivideThresholdFactor = 1.1;
1080 const double subdivideThreshold = avg * kSubdivideThresholdFactor;
1081
1082 for( auto it = longest.begin(); it != longest.end() && it->second > subdivideThreshold;
1083 ++it )
1084 {
1085 wxLogTrace( TRIANGULATE_TRACE, "Subdividing edge with length %f", it->second );
1086 VERTEX* a = it->first;
1087 VERTEX* b = a->next;
1088 VERTEX* last = a;
1089
1090 // We adjust the number of divisions based on the pass in order to progressively
1091 // subdivide the polygon when triangulation fails
1092 int divisions = avg / it->second + 2 + pass;
1093 double step = 1.0 / divisions;
1094
1095 for( int i = 1; i < divisions; i++ )
1096 {
1097 double x = a->x * ( 1.0 - step * i ) + b->x * ( step * i );
1098 double y = a->y * ( 1.0 - step * i ) + b->y * ( step * i );
1099 last = insertTriVertex( VECTOR2I( x, y ), last );
1100 }
1101 }
1102
1103 // update z-order of the vertices
1104 aStart->updateList();
1105 }
1106
1113 bool splitPolygon( VERTEX* start, int aPass )
1114 {
1115 VERTEX* origPoly = start;
1116
1117 // If we have fewer than 4 points, we cannot split the polygon
1118 if( !start || !start->next || start->next == start->prev
1119 || start->next->next == start->prev )
1120 {
1121 return true;
1122 }
1123
1124 // Our first attempts to split the polygon will be at overlapping points.
1125 // These are natural split points and we only need to switch the loop directions
1126 // to generate two new loops. Since they are overlapping, we are do not
1127 // need to create a new segment to disconnect the two loops.
1128 do
1129 {
1130 std::vector<VERTEX*> overlapPoints;
1131 VERTEX* z_pt = origPoly;
1132
1133 while ( z_pt->prevZ && *z_pt->prevZ == *origPoly )
1134 z_pt = z_pt->prevZ;
1135
1136 overlapPoints.push_back( z_pt );
1137
1138 while( z_pt->nextZ && *z_pt->nextZ == *origPoly )
1139 {
1140 z_pt = z_pt->nextZ;
1141 overlapPoints.push_back( z_pt );
1142 }
1143
1144 if( overlapPoints.size() != 2 || overlapPoints[0]->next == overlapPoints[1]
1145 || overlapPoints[0]->prev == overlapPoints[1] )
1146 {
1147 origPoly = origPoly->next;
1148 continue;
1149 }
1150
1151 if( overlapPoints[0]->area( overlapPoints[1] ) < 0 || overlapPoints[1]->area( overlapPoints[0] ) < 0 )
1152 {
1153 wxLogTrace( TRIANGULATE_TRACE, "Split generated a hole, skipping" );
1154 origPoly = origPoly->next;
1155 continue;
1156 }
1157
1158 wxLogTrace( TRIANGULATE_TRACE, "Splitting at overlap point %f, %f", overlapPoints[0]->x, overlapPoints[0]->y );
1159 std::swap( overlapPoints[0]->next, overlapPoints[1]->next );
1160 overlapPoints[0]->next->prev = overlapPoints[0];
1161 overlapPoints[1]->next->prev = overlapPoints[1];
1162
1163 overlapPoints[0]->updateList();
1164 overlapPoints[1]->updateList();
1165 logVertices( overlapPoints[0], nullptr );
1166 logVertices( overlapPoints[1], nullptr );
1167 bool retval = earcutList( overlapPoints[0], aPass )
1168 && earcutList( overlapPoints[1], aPass );
1169
1170 wxLogTrace( TRIANGULATE_TRACE, "%s at first overlap split", retval ? "Success" : "Failed" );
1171 return retval;
1172
1173
1174 } while ( origPoly != start );
1175
1176 // If we've made it through the split algorithm and we still haven't found a
1177 // set of overlapping points, we need to create a new segment to split the polygon
1178 // into two separate polygons. We do this by finding the two vertices that form
1179 // a valid line (does not cross the existing polygon)
1180 do
1181 {
1182 VERTEX* marker = origPoly->next->next;
1183
1184 while( marker != origPoly->prev )
1185 {
1186 // Find a diagonal line that is wholly enclosed by the polygon interior
1187 if( origPoly->next && origPoly->i != marker->i && goodSplit( origPoly, marker ) )
1188 {
1189 VERTEX* newPoly = origPoly->split( marker );
1190
1191 origPoly->updateList();
1192 newPoly->updateList();
1193
1194 bool retval = earcutList( origPoly, aPass ) && earcutList( newPoly, aPass );
1195
1196 wxLogTrace( TRIANGULATE_TRACE, "%s at split", retval ? "Success" : "Failed" );
1197 return retval;
1198 }
1199
1200 marker = marker->next;
1201 }
1202
1203 origPoly = origPoly->next;
1204 } while( origPoly != start );
1205
1206 wxLogTrace( TRIANGULATE_TRACE, "Could not find a valid split point" );
1207 return false;
1208 }
1209
1219 bool goodSplit( const VERTEX* a, const VERTEX* b ) const
1220 {
1221 bool a_on_edge = ( a->nextZ && *a == *a->nextZ ) || ( a->prevZ && *a == *a->prevZ );
1222 bool b_on_edge = ( b->nextZ && *b == *b->nextZ ) || ( b->prevZ && *b == *b->prevZ );
1223 bool no_intersect = a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon( a, b );
1224 bool local_split = locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );
1225 bool same_dir = area( a->prev, a, b->prev ) != 0.0 || area( a, b->prev, b ) != 0.0;
1226 bool has_len = ( *a == *b ) && area( a->prev, a, a->next ) > 0 && area( b->prev, b, b->next ) > 0;
1227 bool pos_area = a->area( b ) > 0 && b->area( a ) > 0;
1228
1229 return no_intersect && local_split && ( same_dir || has_len ) && !a_on_edge && !b_on_edge && pos_area;
1230
1231 }
1232
1233
1234 constexpr int sign( double aVal ) const
1235 {
1236 return ( aVal > 0 ) - ( aVal < 0 );
1237 }
1238
1242 constexpr bool overlapping( const VERTEX* p, const VERTEX* q, const VERTEX* r ) const
1243 {
1244 return q->x <= std::max( p->x, r->x ) &&
1245 q->x >= std::min( p->x, r->x ) &&
1246 q->y <= std::max( p->y, r->y ) &&
1247 q->y >= std::min( p->y, r->y );
1248 }
1249
1255 bool intersects( const VERTEX* p1, const VERTEX* q1, const VERTEX* p2, const VERTEX* q2 ) const
1256 {
1257 int sign1 = sign( area( p1, q1, p2 ) );
1258 int sign2 = sign( area( p1, q1, q2 ) );
1259 int sign3 = sign( area( p2, q2, p1 ) );
1260 int sign4 = sign( area( p2, q2, q1 ) );
1261
1262 if( sign1 != sign2 && sign3 != sign4 )
1263 return true;
1264
1265 if( sign1 == 0 && overlapping( p1, p2, q1 ) )
1266 return true;
1267
1268 if( sign2 == 0 && overlapping( p1, q2, q1 ) )
1269 return true;
1270
1271 if( sign3 == 0 && overlapping( p2, p1, q2 ) )
1272 return true;
1273
1274 if( sign4 == 0 && overlapping( p2, q1, q2 ) )
1275 return true;
1276
1277
1278 return false;
1279 }
1280
1287 bool intersectsPolygon( const VERTEX* a, const VERTEX* b ) const
1288 {
1289 for( size_t ii = 0; ii < m_vertices_original_size; ii++ )
1290 {
1291 const VERTEX* p = &m_vertices[ii];
1292 const VERTEX* q = &m_vertices[( ii + 1 ) % m_vertices_original_size];
1293
1294 if( p->i == a->i || p->i == b->i || q->i == a->i || q->i == b->i )
1295 continue;
1296
1297 if( intersects( p, q, a, b ) )
1298 return true;
1299 }
1300
1301 return false;
1302 }
1303
1311 {
1312 m_result.AddVertex( pt );
1313 return insertVertex( m_result.GetVertexCount() - 1, pt, last );
1314 }
1315
1316private:
1319};
1320
1321#endif //__POLYGON_TRIANGULATION_H
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr coord_type GetY() const
Definition box2.h:208
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr coord_type GetX() const
Definition box2.h:207
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr coord_type GetBottom() const
Definition box2.h:222
SHAPE_POLY_SET::TRIANGULATED_POLYGON & m_result
VERTEX * createRing(const SHAPE_LINE_CHAIN &aPoints, int aBaseIndex, bool aWantCCW)
Create a VERTEX linked list from a SHAPE_LINE_CHAIN with a global index offset.
bool intersects(const VERTEX *p1, const VERTEX *q1, const VERTEX *p2, const VERTEX *q2) const
Check for intersection between two segments, end points included.
VERTEX * eliminateHoles(VERTEX *aOuterRing, std::vector< VERTEX * > &aHoleRings)
Bridge all hole rings into the outer ring by sorting holes left-to-right and connecting each hole's l...
friend struct POLYGON_TRIANGULATION_TEST_ACCESS
constexpr int sign(double aVal) const
static double triArea(double ax, double ay, double bx, double by, double cx, double cy)
Signed area of triangle (ax,ay), (bx,by), (cx,cy).
bool splitPolygon(VERTEX *start, int aPass)
If we cannot find an ear to slice in the current polygon list, we use this to split the polygon into ...
void logVertices(VERTEX *aStart, std::set< VERTEX * > *aSeen)
bool TesselatePolygon(const SHAPE_POLY_SET::POLYGON &aPolygon, SHAPE_POLY_SET::TRIANGULATED_POLYGON *aHintData)
Triangulate a polygon with holes by bridging holes directly into the outer ring's VERTEX linked list,...
VERTEX * insertTriVertex(const VECTOR2I &pt, VERTEX *last)
Create an entry in the vertices lookup and optionally inserts the newly created vertex into an existi...
bool goodSplit(const VERTEX *a, const VERTEX *b) const
Check if a segment joining two vertices lies fully inside the polygon.
double earScore(const VERTEX *a, const VERTEX *b, const VERTEX *c) const
std::vector< SHAPE_LINE_CHAIN > partitionPolygonBalanced(const SHAPE_LINE_CHAIN &aPoly, size_t aTargetLeaves) const
VERTEX * simplifyList(VERTEX *aStart)
Simplify the line chain by removing points that are too close to each other.
POLYGON_TRIANGULATION(SHAPE_POLY_SET::TRIANGULATED_POLYGON &aResult)
bool collectScanlineHits(const SHAPE_LINE_CHAIN &aPoly, bool aVertical, int aCut, std::array< SCANLINE_HIT, 2 > &aHits) const
constexpr bool overlapping(const VERTEX *p, const VERTEX *q, const VERTEX *r) const
If p, q, and r are collinear and r lies between p and q, then return true.
bool splitPolygonBalanced(const SHAPE_LINE_CHAIN &aPoly, std::array< SHAPE_LINE_CHAIN, 2 > &aChildren) const
void logRemaining()
Outputs a list of vertices that have not yet been triangulated.
VERTEX * removeNullTriangles(VERTEX *aStart)
Iterate through the list to remove NULL triangles if they exist.
bool intersectsPolygon(const VERTEX *a, const VERTEX *b) const
Check whether the segment from vertex a -> vertex b crosses any of the segments of the polygon of whi...
bool isTooSmall(const VERTEX *aPoint) const
Check whether a given vertex is too small to matter.
bool earcutList(VERTEX *aPoint, int pass=0)
Walk through a circular linked list starting at aPoint.
bool splitPolygonAtCoordinate(const SHAPE_LINE_CHAIN &aPoly, bool aVertical, int aCut, std::array< SHAPE_LINE_CHAIN, 2 > &aChildren, double &aAreaA, double &aAreaB) const
size_t suggestedPartitionLeafCount(const SHAPE_LINE_CHAIN &aPoly) const
std::vector< double > PartitionAreaFractionsForTesting(const SHAPE_LINE_CHAIN &aPoly, size_t aTargetLeaves) const
void filterPoints(VERTEX *aStart, VERTEX *aEnd=nullptr)
Remove consecutive duplicate vertices from the linked list.
bool sectorContainsSector(const VERTEX *m, const VERTEX *p) const
Whether sector in vertex m contains sector in vertex p in the same coordinate frame.
SHAPE_LINE_CHAIN createSplitChild(const SHAPE_LINE_CHAIN &aPoly, int aStart, int aEnd) const
void subdividePolygon(VERTEX *aStart, int pass=0)
Inserts a new vertex halfway between each existing pair of vertices.
bool TesselatePolygon(const SHAPE_LINE_CHAIN &aPoly, SHAPE_POLY_SET::TRIANGULATED_POLYGON *aHintData)
VERTEX * findHoleBridge(VERTEX *aHole, VERTEX *aOuterStart)
Find a vertex on the outer ring visible from the hole's leftmost vertex by casting a horizontal ray t...
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int Split(const VECTOR2I &aP, bool aExact=false)
Insert the point aP belonging to one of the our segments, splitting the adjacent segment in two.
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
double Area(bool aAbsolute=true) const
Return the area of this chain.
SHAPE_LINE_CHAIN & Simplify2(bool aRemoveColinear=true)
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
int Find(const VECTOR2I &aP, int aThreshold=0) const
Search for point aP.
const std::vector< VECTOR2I > & CPoints() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
const std::deque< VECTOR2I > & Vertices() const
const std::deque< TRI > & Triangles() const
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:307
constexpr extended_type SquaredDistance(const VECTOR2< T > &aVector) const
Compute the squared distance between two vectors.
Definition vector2d.h:561
std::deque< VERTEX > m_vertices
Definition vertex_set.h:343
friend class VERTEX
Definition vertex_set.h:255
bool middleInside(const VERTEX *a, const VERTEX *b) const
Check if the middle of the segment from a to b is inside the polygon.
VERTEX * createList(const SHAPE_LINE_CHAIN &points, VERTEX *aTail=nullptr, void *aUserData=nullptr)
Create a list of vertices from a line chain.
bool locallyInside(const VERTEX *a, const VERTEX *b) const
Check whether the segment from vertex a -> vertex b is inside the polygon around the immediate area o...
BOX2I m_bbox
Definition vertex_set.h:342
double area(const VERTEX *p, const VERTEX *q, const VERTEX *r) const
Return the twice the signed area of the triangle formed by vertices p, q, and r.
VERTEX * insertVertex(int aIndex, const VECTOR2I &pt, VERTEX *last, void *aUserData=nullptr)
Insert a vertex into the vertex set.
VERTEX_SET(int aSimplificationLevel)
Definition vertex_set.h:258
VECTOR2I::extended_type m_simplificationLevel
Definition vertex_set.h:344
VERTEX * split(VERTEX *b)
Split the referenced polygon between the reference point and vertex b, assuming they are in the same ...
const double x
Definition vertex_set.h:235
VERTEX * next
Definition vertex_set.h:241
VERTEX * prevZ
Definition vertex_set.h:247
void updateList()
After inserting or changing nodes, this function should be called to remove duplicate vertices and en...
Definition vertex_set.h:121
VERTEX * nextZ
Definition vertex_set.h:248
VERTEX * prev
Definition vertex_set.h:240
const int i
Definition vertex_set.h:234
void remove()
Remove the node from the linked list and z-ordered linked list.
Definition vertex_set.h:84
double area(const VERTEX *aEnd=nullptr) const
Returns the signed area of the polygon connected to the current vertex, optionally ending at a specif...
Definition vertex_set.h:202
const double y
Definition vertex_set.h:236
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
#define TRIANGULATE_TRACE
#define TRIANGULATEMINIMUMAREA
#define TRIANGULATESIMPLIFICATIONLEVEL
CITER next(CITER it)
Definition ptree.cpp:124
const SHAPE_LINE_CHAIN chain
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
VECTOR2< double > VECTOR2D
Definition vector2d.h:686
VECTOR2< int64_t > VECTOR2L
Definition vector2d.h:688