Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / basegfx / source / polygon / b2dtrapezoid.cxx
blobec3d037e6aa884a6d858c6a9798e3e3f1c4cc316
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <basegfx/polygon/b2dtrapezoid.hxx>
21 #include <basegfx/range/b1drange.hxx>
22 #include <basegfx/polygon/b2dpolygontools.hxx>
23 #include <basegfx/polygon/b2dpolypolygon.hxx>
25 #include <osl/diagnose.h>
27 #include <list>
29 namespace basegfx
31 namespace trapezoidhelper
34 // helper class to hold a simple edge. This is only used for horizontal edges
35 // currently, thus the YPositions will be equal. I did not create a special
36 // class for this since holding the pointers is more effective and also can be
37 // used as baseclass for the traversing edges
39 class TrDeSimpleEdge
41 protected:
42 // pointers to start and end point
43 const B2DPoint* mpStart;
44 const B2DPoint* mpEnd;
46 public:
47 // constructor
48 TrDeSimpleEdge(
49 const B2DPoint* pStart,
50 const B2DPoint* pEnd)
51 : mpStart(pStart),
52 mpEnd(pEnd)
56 // data read access
57 const B2DPoint& getStart() const { return *mpStart; }
58 const B2DPoint& getEnd() const { return *mpEnd; }
61 // define vector of simple edges
63 typedef std::vector< TrDeSimpleEdge > TrDeSimpleEdges;
65 // helper class for holding a traversing edge. It will always have some
66 // distance in YPos. The slope (in a numerically useful form, see comments) is
67 // hold and used in SortValue to allow sorting traversing edges by Y, X and slope
68 // (in that order)
70 class TrDeEdgeEntry : public TrDeSimpleEdge
72 private:
73 // the slope in a numerical useful form for sorting
74 sal_uInt32 mnSortValue;
76 public:
77 // convenience data read access
78 double getDeltaX() const { return mpEnd->getX() - mpStart->getX(); }
79 double getDeltaY() const { return mpEnd->getY() - mpStart->getY(); }
81 // convenience data read access. SortValue is created on demand since
82 // it is not always used
83 sal_uInt32 getSortValue() const
85 if(mnSortValue != 0)
86 return mnSortValue;
88 // get radiant; has to be in the range ]0.0 .. pi[, thus scale to full
89 // sal_uInt32 range for maximum precision
90 const double fRadiant(atan2(getDeltaY(), getDeltaX()) * (SAL_MAX_UINT32 / F_PI));
92 // convert to sal_uInt32 value
93 const_cast< TrDeEdgeEntry* >(this)->mnSortValue = sal_uInt32(fRadiant);
95 return mnSortValue;
98 // constructor. SortValue can be given when known, use zero otherwise
99 TrDeEdgeEntry(
100 const B2DPoint* pStart,
101 const B2DPoint* pEnd,
102 sal_uInt32 nSortValue)
103 : TrDeSimpleEdge(pStart, pEnd),
104 mnSortValue(nSortValue)
106 // force traversal of deltaY downward
107 if(mpEnd->getY() < mpStart->getY())
109 std::swap(mpStart, mpEnd);
112 // no horizontal edges allowed, all need to traverse vertically
113 OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
116 // data write access to StartPoint
117 void setStart( const B2DPoint* pNewStart)
119 OSL_ENSURE(pNewStart != nullptr, "No null pointer allowed here (!)");
121 if(mpStart != pNewStart)
123 mpStart = pNewStart;
125 // no horizontal edges allowed, all need to traverse vertically
126 OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
130 // data write access to EndPoint
131 void setEnd( const B2DPoint* pNewEnd)
133 OSL_ENSURE(pNewEnd != nullptr, "No null pointer allowed here (!)");
135 if(mpEnd != pNewEnd)
137 mpEnd = pNewEnd;
139 // no horizontal edges allowed, all need to traverse vertically
140 OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
144 // operator for sort support. Sort by Y, X and slope (in that order)
145 bool operator<(const TrDeEdgeEntry& rComp) const
147 if(fTools::equal(getStart().getY(), rComp.getStart().getY()))
149 if(fTools::equal(getStart().getX(), rComp.getStart().getX()))
151 // when start points are equal, use the direction the edge is pointing
152 // to. That value is created on demand and derived from atan2 in the
153 // range ]0.0 .. pi[ (without extremas, we always have a deltaY in this
154 // class) and scaled to sal_uInt32 range for best precision. 0 means no angle,
155 // while SAL_MAX_UINT32 means pi. Thus, the higher the value, the more left
156 // the edge traverses.
157 return (getSortValue() > rComp.getSortValue());
159 else
161 return fTools::less(getStart().getX(), rComp.getStart().getX());
164 else
166 return fTools::less(getStart().getY(), rComp.getStart().getY());
170 // method for cut support
171 B2DPoint getCutPointForGivenY(double fGivenY) const
173 // Calculate cut point locally (do not use interpolate) since it is numerically
174 // necessary to guarantee the new, equal Y-coordinate
175 const double fFactor((fGivenY - getStart().getY()) / getDeltaY());
176 const double fDeltaXNew(fFactor * getDeltaX());
178 return B2DPoint(getStart().getX() + fDeltaXNew, fGivenY);
182 // define double linked list of edges (for fast random insert)
184 typedef std::list< TrDeEdgeEntry > TrDeEdgeEntries;
186 } // end of anonymous namespace
187 } // end of namespace basegfx
189 namespace basegfx
191 namespace trapezoidhelper
193 // FIXME: templatize this and use it for TrDeEdgeEntries too ...
195 /// Class to allow efficient allocation and release of B2DPoints
196 class PointBlockAllocator
198 static const size_t nBlockSize = 32;
199 size_t nCurPoint;
200 B2DPoint *mpPointBase;
201 /// Special case the first allocation to avoid it.
202 B2DPoint maFirstStackBlock[nBlockSize];
203 std::vector< B2DPoint * > maBlocks;
204 public:
205 PointBlockAllocator() :
206 nCurPoint( nBlockSize ),
207 mpPointBase( maFirstStackBlock )
211 ~PointBlockAllocator()
213 while(!maBlocks.empty())
215 delete [] maBlocks.back();
216 maBlocks.pop_back();
220 B2DPoint *allocatePoint()
222 if(nCurPoint >= nBlockSize)
224 mpPointBase = new B2DPoint[nBlockSize];
225 maBlocks.push_back(mpPointBase);
226 nCurPoint = 0;
228 return mpPointBase + nCurPoint++;
231 B2DPoint *allocatePoint(const B2DTuple &rPoint)
233 B2DPoint *pPoint = allocatePoint();
234 *pPoint = rPoint;
235 return pPoint;
238 /// This is a very uncommon case but why not ...
239 void freeIfLast(B2DPoint const *pPoint)
241 // just re-use the last point if we can.
242 if ( nCurPoint > 0 && pPoint == mpPointBase + nCurPoint - 1 )
243 nCurPoint--;
247 // helper class to handle the complete trapezoid subdivision of a PolyPolygon
248 class TrapezoidSubdivider
250 private:
251 // local data
252 sal_uInt32 mnInitialEdgeEntryCount;
253 TrDeEdgeEntries maTrDeEdgeEntries;
254 std::vector< B2DPoint > maPoints;
255 /// new points allocated for cuts
256 PointBlockAllocator maNewPoints;
258 void addEdgeSorted(
259 TrDeEdgeEntries::iterator aCurrent,
260 const TrDeEdgeEntry& rNewEdge)
262 // Loop while new entry is bigger, use operator<
263 while(aCurrent != maTrDeEdgeEntries.end() && (*aCurrent) < rNewEdge)
265 ++aCurrent;
268 // Insert before first which is smaller or equal or at end
269 maTrDeEdgeEntries.insert(aCurrent, rNewEdge);
272 bool splitEdgeAtGivenPoint(
273 TrDeEdgeEntries::reference aEdge,
274 const B2DPoint& rCutPoint,
275 const TrDeEdgeEntries::iterator& aCurrent)
277 // do not create edges without deltaY: do not split when start is identical
278 if(aEdge.getStart().equal(rCutPoint))
280 return false;
283 // do not create edges without deltaY: do not split when end is identical
284 if(aEdge.getEnd().equal(rCutPoint))
286 return false;
289 const double fOldDeltaYStart(rCutPoint.getY() - aEdge.getStart().getY());
291 if(fTools::lessOrEqual(fOldDeltaYStart, 0.0))
293 // do not split: the resulting edge would be horizontal
294 // correct it to new start point
295 aEdge.setStart(&rCutPoint);
296 return false;
299 const double fNewDeltaYStart(aEdge.getEnd().getY() - rCutPoint.getY());
301 if(fTools::lessOrEqual(fNewDeltaYStart, 0.0))
303 // do not split: the resulting edge would be horizontal
304 // correct it to new end point
305 aEdge.setEnd(&rCutPoint);
306 return false;
309 // Create new entry
310 const TrDeEdgeEntry aNewEdge(
311 &rCutPoint,
312 &aEdge.getEnd(),
313 aEdge.getSortValue());
315 // Correct old entry
316 aEdge.setEnd(&rCutPoint);
318 // Insert sorted (to avoid new sort)
319 addEdgeSorted(aCurrent, aNewEdge);
321 return true;
324 bool testAndCorrectEdgeIntersection(
325 TrDeEdgeEntries::reference aEdgeA,
326 TrDeEdgeEntries::reference aEdgeB,
327 const TrDeEdgeEntries::iterator& aCurrent)
329 // Exclude simple cases: same start or end point
330 if(aEdgeA.getStart().equal(aEdgeB.getStart()))
332 return false;
335 if(aEdgeA.getStart().equal(aEdgeB.getEnd()))
337 return false;
340 if(aEdgeA.getEnd().equal(aEdgeB.getStart()))
342 return false;
345 if(aEdgeA.getEnd().equal(aEdgeB.getEnd()))
347 return false;
350 // Exclude simple cases: one of the edges has no length anymore
351 if(aEdgeA.getStart().equal(aEdgeA.getEnd()))
353 return false;
356 if(aEdgeB.getStart().equal(aEdgeB.getEnd()))
358 return false;
361 // check if one point is on the other edge (a touch, not a cut)
362 const B2DVector aDeltaB(aEdgeB.getDeltaX(), aEdgeB.getDeltaY());
364 if(utils::isPointOnEdge(aEdgeA.getStart(), aEdgeB.getStart(), aDeltaB))
366 return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getStart(), aCurrent);
369 if(utils::isPointOnEdge(aEdgeA.getEnd(), aEdgeB.getStart(), aDeltaB))
371 return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getEnd(), aCurrent);
374 const B2DVector aDeltaA(aEdgeA.getDeltaX(), aEdgeA.getDeltaY());
376 if(utils::isPointOnEdge(aEdgeB.getStart(), aEdgeA.getStart(), aDeltaA))
378 return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getStart(), aCurrent);
381 if(utils::isPointOnEdge(aEdgeB.getEnd(), aEdgeA.getStart(), aDeltaA))
383 return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getEnd(), aCurrent);
386 // check for cut inside edges. Use both t-values to choose the more precise
387 // one later
388 double fCutA(0.0);
389 double fCutB(0.0);
391 if(utils::findCut(
392 aEdgeA.getStart(), aDeltaA,
393 aEdgeB.getStart(), aDeltaB,
394 CutFlagValue::LINE,
395 &fCutA,
396 &fCutB) != CutFlagValue::NONE)
398 // use a simple metric (length criteria) for choosing the numerically
399 // better cut
400 const double fSimpleLengthA(aDeltaA.getX() + aDeltaA.getY());
401 const double fSimpleLengthB(aDeltaB.getX() + aDeltaB.getY());
402 const bool bAIsLonger(fSimpleLengthA > fSimpleLengthB);
403 B2DPoint* pNewPoint = bAIsLonger
404 ? maNewPoints.allocatePoint(aEdgeA.getStart() + (fCutA * aDeltaA))
405 : maNewPoints.allocatePoint(aEdgeB.getStart() + (fCutB * aDeltaB));
407 // try to split both edges
408 bool bRetval = splitEdgeAtGivenPoint(aEdgeA, *pNewPoint, aCurrent);
409 bRetval |= splitEdgeAtGivenPoint(aEdgeB, *pNewPoint, aCurrent);
411 if(!bRetval)
412 maNewPoints.freeIfLast(pNewPoint);
414 return bRetval;
417 return false;
420 void solveHorizontalEdges(TrDeSimpleEdges& rTrDeSimpleEdges)
422 if(!rTrDeSimpleEdges.empty() && !maTrDeEdgeEntries.empty())
424 // there were horizontal edges. These can be excluded, but
425 // cuts with other edges need to be solved and added before
426 // ignoring them
427 for(const TrDeSimpleEdge & rHorEdge : rTrDeSimpleEdges)
429 // get horizontal edge as candidate; prepare its range and fixed Y
430 const B1DRange aRange(rHorEdge.getStart().getX(), rHorEdge.getEnd().getX());
431 const double fFixedY(rHorEdge.getStart().getY());
433 // loop over traversing edges
434 TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin());
438 // get compare edge
439 TrDeEdgeEntries::reference aCompare(*aCurrent++);
441 if(fTools::lessOrEqual(aCompare.getEnd().getY(), fFixedY))
443 // edge ends above horizontal edge, continue
444 continue;
447 if(fTools::moreOrEqual(aCompare.getStart().getY(), fFixedY))
449 // edge starts below horizontal edge, continue
450 continue;
453 // vertical overlap, get horizontal range
454 const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX());
456 if(aRange.overlaps(aCompareRange))
458 // possible cut, get cut point
459 const B2DPoint aSplit(aCompare.getCutPointForGivenY(fFixedY));
461 if(fTools::more(aSplit.getX(), aRange.getMinimum())
462 && fTools::less(aSplit.getX(), aRange.getMaximum()))
464 // cut is in XRange of horizontal edge, potentially needed cut
465 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aSplit);
467 if(!splitEdgeAtGivenPoint(aCompare, *pNewPoint, aCurrent))
469 maNewPoints.freeIfLast(pNewPoint);
474 while(aCurrent != maTrDeEdgeEntries.end()
475 && fTools::less(aCurrent->getStart().getY(), fFixedY));
480 public:
481 explicit TrapezoidSubdivider(
482 const B2DPolyPolygon& rSourcePolyPolygon)
483 : mnInitialEdgeEntryCount(0),
484 maTrDeEdgeEntries(),
485 maPoints(),
486 maNewPoints()
488 B2DPolyPolygon aSource(rSourcePolyPolygon);
489 TrDeSimpleEdges aTrDeSimpleEdges;
490 sal_uInt32 nAllPointCount(0);
492 // ensure there are no curves used
493 if(aSource.areControlPointsUsed())
495 aSource = aSource.getDefaultAdaptiveSubdivision();
498 for(const auto& aPolygonCandidate : aSource)
500 // 1st run: count points
501 const sal_uInt32 nCount(aPolygonCandidate.count());
503 if(nCount > 2)
505 nAllPointCount += nCount;
509 if(nAllPointCount)
511 // reserve needed points. CAUTION: maPoints size is NOT to be changed anymore
512 // after 2nd loop since pointers to it are used in the edges
513 maPoints.reserve(nAllPointCount);
515 for(const auto& aPolygonCandidate : aSource)
517 // 2nd run: add points
518 const sal_uInt32 nCount(aPolygonCandidate.count());
520 if(nCount > 2)
522 for(sal_uInt32 b = 0; b < nCount; b++)
524 maPoints.push_back(aPolygonCandidate.getB2DPoint(b));
529 // Moved the edge construction to a 3rd run: doing it in the 2nd run is
530 // possible (and I used it), but requires a working vector::reserve()
531 // implementation, else the vector will be reallocated and the pointers
532 // in the edges may be wrong. Security first here.
533 sal_uInt32 nStartIndex(0);
535 for(const auto& aPolygonCandidate : aSource)
537 const sal_uInt32 nCount(aPolygonCandidate.count());
539 if(nCount > 2)
541 // get the last point of the current polygon
542 B2DPoint* pPrev(&maPoints[nCount + nStartIndex - 1]);
544 for(sal_uInt32 b = 0; b < nCount; b++)
546 // get next point
547 B2DPoint* pCurr(&maPoints[nStartIndex++]);
549 if(fTools::equal(pPrev->getY(), pCurr->getY()))
551 // horizontal edge, check for single point
552 if(!fTools::equal(pPrev->getX(), pCurr->getX()))
554 // X-order not needed, just add
555 aTrDeSimpleEdges.emplace_back(pPrev, pCurr);
557 const double fMiddle((pPrev->getY() + pCurr->getY()) * 0.5);
558 pPrev->setY(fMiddle);
559 pCurr->setY(fMiddle);
562 else
564 // vertical edge. Positive Y-direction is guaranteed by the
565 // TrDeEdgeEntry constructor
566 maTrDeEdgeEntries.emplace_back(pPrev, pCurr, 0);
567 mnInitialEdgeEntryCount++;
570 // prepare next step
571 pPrev = pCurr;
577 if(!maTrDeEdgeEntries.empty())
579 // single and initial sort of traversing edges
580 maTrDeEdgeEntries.sort();
582 // solve horizontal edges if there are any detected
583 solveHorizontalEdges(aTrDeSimpleEdges);
587 void Subdivide(B2DTrapezoidVector& ro_Result)
589 // This is the central subdivider. The strategy is to use the first two entries
590 // from the traversing edges as a potential trapezoid and do the needed corrections
591 // and adaptations on the way.
593 // There always must be two edges with the same YStart value: When adding the polygons
594 // in the constructor, there is always a topmost point from which two edges start; when
595 // the topmost is an edge, there is a start and end of this edge from which two edges
596 // start. All cases have two edges with same StartY (QED).
598 // Based on this these edges get corrected when:
599 // - one is longer than the other
600 // - they intersect
601 // - they intersect with other edges
602 // - another edge starts inside the thought trapezoid
604 // All this cases again produce a valid state so that the first two edges have a common
605 // Ystart again. Some cases lead to a restart of the process, some allow consuming the
606 // edges and create the intended trapezoid.
608 // Be careful when doing changes here: it is essential to keep all possible paths
609 // in valid states and to be numerically correct. This is especially needed e.g.
610 // by using fTools::equal(..) in the more robust small-value incarnation.
611 B1DRange aLeftRange;
612 B1DRange aRightRange;
614 if(!maTrDeEdgeEntries.empty())
616 // measuring shows that the relation between edges and created trapezoids is
617 // mostly in the 1:1 range, thus reserve as much trapezoids as edges exist. Do
618 // not use maTrDeEdgeEntries.size() since that may be a non-constant time
619 // operation for Lists. Instead, use mnInitialEdgeEntryCount which will contain
620 // the roughly counted adds to the List
621 ro_Result.reserve(ro_Result.size() + mnInitialEdgeEntryCount);
624 while(!maTrDeEdgeEntries.empty())
626 // Prepare current operator and get first edge
627 TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin());
628 TrDeEdgeEntries::reference aLeft(*aCurrent++);
630 if(aCurrent == maTrDeEdgeEntries.end())
632 // Should not happen: No 2nd edge; consume the single edge
633 // to not have an endless loop and start next. During development
634 // I constantly had breakpoints here, so I am sure enough to add an
635 // assertion here
636 OSL_FAIL("Trapezoid decomposer in illegal state (!)");
637 maTrDeEdgeEntries.pop_front();
638 continue;
641 // get second edge
642 TrDeEdgeEntries::reference aRight(*aCurrent++);
644 if(!fTools::equal(aLeft.getStart().getY(), aRight.getStart().getY()))
646 // Should not happen: We have a 2nd edge, but YStart is on another
647 // line; consume the single edge to not have an endless loop and start
648 // next. During development I constantly had breakpoints here, so I am
649 // sure enough to add an assertion here
650 OSL_FAIL("Trapezoid decomposer in illegal state (!)");
651 maTrDeEdgeEntries.pop_front();
652 continue;
655 // aLeft and aRight build a thought trapezoid now. They have a common
656 // start line (same Y for start points). Potentially, one of the edges
657 // is longer than the other. It is only needed to look at the shorter
658 // length which build the potential trapezoid. To do so, get the end points
659 // locally and adapt the evtl. longer one. Use only aLeftEnd and aRightEnd
660 // from here on, not the aLeft.getEnd() or aRight.getEnd() accesses.
661 B2DPoint aLeftEnd(aLeft.getEnd());
662 B2DPoint aRightEnd(aRight.getEnd());
664 // check if end points are on the same line. If yes, no adaptation
665 // needs to be prepared. Also remember which one actually is longer.
666 const bool bEndOnSameLine(fTools::equal(aLeftEnd.getY(), aRightEnd.getY()));
667 bool bLeftIsLonger(false);
669 if(!bEndOnSameLine)
671 // check which edge is longer and correct accordingly
672 bLeftIsLonger = fTools::more(aLeftEnd.getY(), aRightEnd.getY());
674 if(bLeftIsLonger)
676 aLeftEnd = aLeft.getCutPointForGivenY(aRightEnd.getY());
678 else
680 aRightEnd = aRight.getCutPointForGivenY(aLeftEnd.getY());
684 // check for same start and end points
685 const bool bSameStartPoint(aLeft.getStart().equal(aRight.getStart()));
686 const bool bSameEndPoint(aLeftEnd.equal(aRightEnd));
688 // check the simple case that the edges form a 'blind' edge (deadend)
689 if(bSameStartPoint && bSameEndPoint)
691 // correct the longer edge if prepared
692 if(!bEndOnSameLine)
694 if(bLeftIsLonger)
696 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aLeftEnd);
698 if(!splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent))
700 maNewPoints.freeIfLast(pNewPoint);
703 else
705 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aRightEnd);
707 if(!splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent))
709 maNewPoints.freeIfLast(pNewPoint);
714 // consume both edges and start next run
715 maTrDeEdgeEntries.pop_front();
716 maTrDeEdgeEntries.pop_front();
718 continue;
721 // check if the edges self-intersect. This can only happen when
722 // start and end point are different
723 bool bRangesSet(false);
725 if(!(bSameStartPoint || bSameEndPoint))
727 // get XRanges of edges
728 aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX());
729 aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX());
730 bRangesSet = true;
732 // use fast range test first
733 if(aLeftRange.overlaps(aRightRange))
735 // real cut test and correction. If correction was needed,
736 // start new run
737 if(testAndCorrectEdgeIntersection(aLeft, aRight, aCurrent))
739 continue;
744 // now we need to check if there are intersections with other edges
745 // or if other edges start inside the candidate trapezoid
746 if(aCurrent != maTrDeEdgeEntries.end()
747 && fTools::less(aCurrent->getStart().getY(), aLeftEnd.getY()))
749 // get XRanges of edges
750 if(!bRangesSet)
752 aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX());
753 aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX());
756 // build full XRange for fast check
757 B1DRange aAllRange(aLeftRange);
758 aAllRange.expand(aRightRange);
760 // prepare loop iterator; aCurrent needs to stay unchanged for
761 // possibly sorted insertions of new EdgeNodes. Also prepare stop flag
762 TrDeEdgeEntries::iterator aLoop(aCurrent);
763 bool bDone(false);
767 // get compare edge and its XRange
768 TrDeEdgeEntries::reference aCompare(*aLoop++);
770 // avoid edges using the same start point as one of
771 // the edges. These can neither have their start point
772 // in the thought trapezoid nor cut with one of the edges
773 if(aCompare.getStart().equal(aRight.getStart()))
775 continue;
778 // get compare XRange
779 const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX());
781 // use fast range test first
782 if(aAllRange.overlaps(aCompareRange))
784 // check for start point inside thought trapezoid
785 if(fTools::more(aCompare.getStart().getY(), aLeft.getStart().getY()))
787 // calculate the two possible split points at compare's Y
788 const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aCompare.getStart().getY()));
789 const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aCompare.getStart().getY()));
791 // check for start point of aCompare being inside thought
792 // trapezoid
793 if(aCompare.getStart().getX() >= aSplitLeft.getX() &&
794 aCompare.getStart().getX() <= aSplitRight.getX())
796 // is inside, correct and restart loop
797 B2DPoint* pNewLeft = maNewPoints.allocatePoint(aSplitLeft);
799 if(splitEdgeAtGivenPoint(aLeft, *pNewLeft, aCurrent))
801 bDone = true;
803 else
805 maNewPoints.freeIfLast(pNewLeft);
808 B2DPoint* pNewRight = maNewPoints.allocatePoint(aSplitRight);
810 if(splitEdgeAtGivenPoint(aRight, *pNewRight, aCurrent))
812 bDone = true;
814 else
816 maNewPoints.freeIfLast(pNewRight);
821 if(!bDone && aLeftRange.overlaps(aCompareRange))
823 // test for concrete cut of compare edge with left edge
824 bDone = testAndCorrectEdgeIntersection(aLeft, aCompare, aCurrent);
827 if(!bDone && aRightRange.overlaps(aCompareRange))
829 // test for concrete cut of compare edge with Right edge
830 bDone = testAndCorrectEdgeIntersection(aRight, aCompare, aCurrent);
834 while(!bDone
835 && aLoop != maTrDeEdgeEntries.end()
836 && fTools::less(aLoop->getStart().getY(), aLeftEnd.getY()));
838 if(bDone)
840 // something needed to be changed; start next loop
841 continue;
845 // when we get here, the intended trapezoid can be used. It needs to
846 // be corrected possibly (if prepared); but this is no reason not to
847 // use it in the same loop iteration
848 if(!bEndOnSameLine)
850 if(bLeftIsLonger)
852 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aLeftEnd);
854 if(!splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent))
856 maNewPoints.freeIfLast(pNewPoint);
859 else
861 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aRightEnd);
863 if(!splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent))
865 maNewPoints.freeIfLast(pNewPoint);
870 // the two edges start at the same Y, they use the same DeltaY, they
871 // do not cut themselves and not any other edge in range. Create a
872 // B2DTrapezoid and consume both edges
873 ro_Result.emplace_back(
874 aLeft.getStart().getX(),
875 aRight.getStart().getX(),
876 aLeft.getStart().getY(),
877 aLeftEnd.getX(),
878 aRightEnd.getX(),
879 aLeftEnd.getY());
881 maTrDeEdgeEntries.pop_front();
882 maTrDeEdgeEntries.pop_front();
886 } // end of anonymous namespace
887 } // end of namespace basegfx
889 namespace basegfx
891 B2DTrapezoid::B2DTrapezoid(
892 const double& rfTopXLeft,
893 const double& rfTopXRight,
894 const double& rfTopY,
895 const double& rfBottomXLeft,
896 const double& rfBottomXRight,
897 const double& rfBottomY)
898 : mfTopXLeft(rfTopXLeft),
899 mfTopXRight(rfTopXRight),
900 mfTopY(rfTopY),
901 mfBottomXLeft(rfBottomXLeft),
902 mfBottomXRight(rfBottomXRight),
903 mfBottomY(rfBottomY)
905 // guarantee mfTopXRight >= mfTopXLeft
906 if(mfTopXLeft > mfTopXRight)
908 std::swap(mfTopXLeft, mfTopXRight);
911 // guarantee mfBottomXRight >= mfBottomXLeft
912 if(mfBottomXLeft > mfBottomXRight)
914 std::swap(mfBottomXLeft, mfBottomXRight);
917 // guarantee mfBottomY >= mfTopY
918 if(mfTopY > mfBottomY)
920 std::swap(mfTopY, mfBottomY);
921 std::swap(mfTopXLeft, mfBottomXLeft);
922 std::swap(mfTopXRight, mfBottomXRight);
926 B2DPolygon B2DTrapezoid::getB2DPolygon() const
928 B2DPolygon aRetval;
930 aRetval.append(B2DPoint(getTopXLeft(), getTopY()));
931 aRetval.append(B2DPoint(getTopXRight(), getTopY()));
932 aRetval.append(B2DPoint(getBottomXRight(), getBottomY()));
933 aRetval.append(B2DPoint(getBottomXLeft(), getBottomY()));
934 aRetval.setClosed(true);
936 return aRetval;
938 } // end of namespace basegfx
940 namespace basegfx
942 namespace utils
944 // convert Source utils::PolyPolygon to trapezoids
945 void trapezoidSubdivide(B2DTrapezoidVector& ro_Result, const B2DPolyPolygon& rSourcePolyPolygon)
947 trapezoidhelper::TrapezoidSubdivider aTrapezoidSubdivider(rSourcePolyPolygon);
949 aTrapezoidSubdivider.Subdivide(ro_Result);
952 void createLineTrapezoidFromEdge(
953 B2DTrapezoidVector& ro_Result,
954 const B2DPoint& rPointA,
955 const B2DPoint& rPointB,
956 double fLineWidth)
958 if(fTools::lessOrEqual(fLineWidth, 0.0))
960 // no line width
961 return;
964 if(rPointA.equal(rPointB))
966 // points are equal, no edge
967 return;
970 const double fHalfLineWidth(0.5 * fLineWidth);
972 if(fTools::equal(rPointA.getX(), rPointB.getX()))
974 // vertical line
975 const double fLeftX(rPointA.getX() - fHalfLineWidth);
976 const double fRightX(rPointA.getX() + fHalfLineWidth);
978 ro_Result.emplace_back(
979 fLeftX,
980 fRightX,
981 std::min(rPointA.getY(), rPointB.getY()),
982 fLeftX,
983 fRightX,
984 std::max(rPointA.getY(), rPointB.getY()));
986 else if(fTools::equal(rPointA.getY(), rPointB.getY()))
988 // horizontal line
989 const double fLeftX(std::min(rPointA.getX(), rPointB.getX()));
990 const double fRightX(std::max(rPointA.getX(), rPointB.getX()));
992 ro_Result.emplace_back(
993 fLeftX,
994 fRightX,
995 rPointA.getY() - fHalfLineWidth,
996 fLeftX,
997 fRightX,
998 rPointA.getY() + fHalfLineWidth);
1000 else
1002 // diagonal line
1003 // create perpendicular vector
1004 const B2DVector aDelta(rPointB - rPointA);
1005 B2DVector aPerpendicular(-aDelta.getY(), aDelta.getX());
1006 aPerpendicular.setLength(fHalfLineWidth);
1008 // create StartLow, StartHigh, EndLow and EndHigh
1009 const B2DPoint aStartLow(rPointA + aPerpendicular);
1010 const B2DPoint aStartHigh(rPointA - aPerpendicular);
1011 const B2DPoint aEndHigh(rPointB - aPerpendicular);
1012 const B2DPoint aEndLow(rPointB + aPerpendicular);
1014 // create EdgeEntries
1015 basegfx::trapezoidhelper::TrDeEdgeEntries aTrDeEdgeEntries;
1017 aTrDeEdgeEntries.emplace_back(&aStartLow, &aStartHigh, 0);
1018 aTrDeEdgeEntries.emplace_back(&aStartHigh, &aEndHigh, 0);
1019 aTrDeEdgeEntries.emplace_back(&aEndHigh, &aEndLow, 0);
1020 aTrDeEdgeEntries.emplace_back(&aEndLow, &aStartLow, 0);
1021 aTrDeEdgeEntries.sort();
1023 // here we know we have exactly four edges, and they do not cut, touch or
1024 // intersect. This makes processing much easier. Get the first two as start
1025 // edges for the thought trapezoid
1026 basegfx::trapezoidhelper::TrDeEdgeEntries::iterator aCurrent(aTrDeEdgeEntries.begin());
1027 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft(*aCurrent++);
1028 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight(*aCurrent++);
1029 const bool bEndOnSameLine(fTools::equal(aLeft.getEnd().getY(), aRight.getEnd().getY()));
1031 if(bEndOnSameLine)
1033 // create two triangle trapezoids
1034 ro_Result.emplace_back(
1035 aLeft.getStart().getX(),
1036 aRight.getStart().getX(),
1037 aLeft.getStart().getY(),
1038 aLeft.getEnd().getX(),
1039 aRight.getEnd().getX(),
1040 aLeft.getEnd().getY());
1042 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
1043 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
1045 ro_Result.emplace_back(
1046 aLeft2.getStart().getX(),
1047 aRight2.getStart().getX(),
1048 aLeft2.getStart().getY(),
1049 aLeft2.getEnd().getX(),
1050 aRight2.getEnd().getX(),
1051 aLeft2.getEnd().getY());
1053 else
1055 // create three trapezoids. Check which edge is longer and
1056 // correct accordingly
1057 const bool bLeftIsLonger(fTools::more(aLeft.getEnd().getY(), aRight.getEnd().getY()));
1059 if(bLeftIsLonger)
1061 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
1062 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
1063 const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aRight.getEnd().getY()));
1064 const B2DPoint aSplitRight(aRight2.getCutPointForGivenY(aLeft.getEnd().getY()));
1066 ro_Result.emplace_back(
1067 aLeft.getStart().getX(),
1068 aRight.getStart().getX(),
1069 aLeft.getStart().getY(),
1070 aSplitLeft.getX(),
1071 aRight.getEnd().getX(),
1072 aRight.getEnd().getY());
1074 ro_Result.emplace_back(
1075 aSplitLeft.getX(),
1076 aRight.getEnd().getX(),
1077 aRight.getEnd().getY(),
1078 aLeft2.getStart().getX(),
1079 aSplitRight.getX(),
1080 aLeft2.getStart().getY());
1082 ro_Result.emplace_back(
1083 aLeft2.getStart().getX(),
1084 aSplitRight.getX(),
1085 aLeft2.getStart().getY(),
1086 aLeft2.getEnd().getX(),
1087 aRight2.getEnd().getX(),
1088 aLeft2.getEnd().getY());
1090 else
1092 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
1093 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
1094 const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aLeft.getEnd().getY()));
1095 const B2DPoint aSplitLeft(aLeft2.getCutPointForGivenY(aRight.getEnd().getY()));
1097 ro_Result.emplace_back(
1098 aLeft.getStart().getX(),
1099 aRight.getStart().getX(),
1100 aLeft.getStart().getY(),
1101 aLeft.getEnd().getX(),
1102 aSplitRight.getX(),
1103 aLeft.getEnd().getY());
1105 ro_Result.emplace_back(
1106 aLeft.getEnd().getX(),
1107 aSplitRight.getX(),
1108 aLeft.getEnd().getY(),
1109 aSplitLeft.getX(),
1110 aRight.getEnd().getX(),
1111 aRight2.getStart().getY());
1113 ro_Result.emplace_back(
1114 aSplitLeft.getX(),
1115 aRight.getEnd().getX(),
1116 aRight2.getStart().getY(),
1117 aLeft2.getEnd().getX(),
1118 aRight2.getEnd().getX(),
1119 aLeft2.getEnd().getY());
1125 void createLineTrapezoidFromB2DPolygon(
1126 B2DTrapezoidVector& ro_Result,
1127 const B2DPolygon& rPolygon,
1128 double fLineWidth)
1130 if(fTools::lessOrEqual(fLineWidth, 0.0))
1132 return;
1135 // ensure there are no curves used
1136 B2DPolygon aSource(rPolygon);
1138 if(aSource.areControlPointsUsed())
1140 const double fPrecisionFactor = 0.25;
1141 aSource = adaptiveSubdivideByDistance( aSource, fLineWidth * fPrecisionFactor );
1144 const sal_uInt32 nPointCount(aSource.count());
1146 if(!nPointCount)
1148 return;
1151 const sal_uInt32 nEdgeCount(aSource.isClosed() ? nPointCount : nPointCount - 1);
1152 B2DPoint aCurrent(aSource.getB2DPoint(0));
1154 ro_Result.reserve(ro_Result.size() + (3 * nEdgeCount));
1156 for(sal_uInt32 a(0); a < nEdgeCount; a++)
1158 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
1159 const B2DPoint aNext(aSource.getB2DPoint(nNextIndex));
1161 createLineTrapezoidFromEdge(ro_Result, aCurrent, aNext, fLineWidth);
1162 aCurrent = aNext;
1167 } // end of namespace utils
1168 } // end of namespace basegfx
1170 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */