Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / basegfx / source / polygon / b2dtrapezoid.cxx
blob0916099a8c4c44f5d0f27da27071bb8b3f274fd8
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>
24 #include <osl/diagnose.h>
26 #include <list>
28 namespace basegfx
30 namespace trapezoidhelper
33 // helper class to hold a simple edge. This is only used for horizontal edges
34 // currently, thus the YPositions will be equal. I did not create a special
35 // class for this since holding the pointers is more effective and also can be
36 // used as baseclass for the traversing edges
38 class TrDeSimpleEdge
40 protected:
41 // pointers to start and end point
42 const B2DPoint* mpStart;
43 const B2DPoint* mpEnd;
45 public:
46 // constructor
47 TrDeSimpleEdge(
48 const B2DPoint* pStart,
49 const B2DPoint* pEnd)
50 : mpStart(pStart),
51 mpEnd(pEnd)
55 // data read access
56 const B2DPoint& getStart() const { return *mpStart; }
57 const B2DPoint& getEnd() const { return *mpEnd; }
60 // define vector of simple edges
62 typedef std::vector< TrDeSimpleEdge > TrDeSimpleEdges;
64 // helper class for holding a traversing edge. It will always have some
65 // distance in YPos. The slope (in a numerically useful form, see comments) is
66 // hold and used in SortValue to allow sorting traversing edges by Y, X and slope
67 // (in that order)
69 class TrDeEdgeEntry : public TrDeSimpleEdge
71 private:
72 // the slope in a numerical useful form for sorting
73 sal_uInt32 mnSortValue;
75 public:
76 // convenience data read access
77 double getDeltaX() const { return mpEnd->getX() - mpStart->getX(); }
78 double getDeltaY() const { return mpEnd->getY() - mpStart->getY(); }
80 // convenience data read access. SortValue is created on demand since
81 // it is not always used
82 sal_uInt32 getSortValue() const
84 if(mnSortValue != 0)
85 return mnSortValue;
87 // get radiant; has to be in the range ]0.0 .. pi[, thus scale to full
88 // sal_uInt32 range for maximum precision
89 const double fRadiant(atan2(getDeltaY(), getDeltaX()) * (SAL_MAX_UINT32 / F_PI));
91 // convert to sal_uInt32 value
92 const_cast< TrDeEdgeEntry* >(this)->mnSortValue = sal_uInt32(fRadiant);
94 return mnSortValue;
97 // constructor. SortValue can be given when known, use zero otherwise
98 TrDeEdgeEntry(
99 const B2DPoint* pStart,
100 const B2DPoint* pEnd,
101 sal_uInt32 nSortValue)
102 : TrDeSimpleEdge(pStart, pEnd),
103 mnSortValue(nSortValue)
105 // force traversal of deltaY downward
106 if(mpEnd->getY() < mpStart->getY())
108 std::swap(mpStart, mpEnd);
111 // no horizontal edges allowed, all need to traverse vertically
112 OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
115 // data write access to StartPoint
116 void setStart( const B2DPoint* pNewStart)
118 OSL_ENSURE(pNewStart != nullptr, "No null pointer allowed here (!)");
120 if(mpStart != pNewStart)
122 mpStart = pNewStart;
124 // no horizontal edges allowed, all need to traverse vertically
125 OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
129 // data write access to EndPoint
130 void setEnd( const B2DPoint* pNewEnd)
132 OSL_ENSURE(pNewEnd != nullptr, "No null pointer allowed here (!)");
134 if(mpEnd != pNewEnd)
136 mpEnd = pNewEnd;
138 // no horizontal edges allowed, all need to traverse vertically
139 OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
143 // operator for sort support. Sort by Y, X and slope (in that order)
144 bool operator<(const TrDeEdgeEntry& rComp) const
146 if(fTools::equal(getStart().getY(), rComp.getStart().getY()))
148 if(fTools::equal(getStart().getX(), rComp.getStart().getX()))
150 // when start points are equal, use the direction the edge is pointing
151 // to. That value is created on demand and derived from atan2 in the
152 // range ]0.0 .. pi[ (without extremas, we always have a deltaY in this
153 // class) and scaled to sal_uInt32 range for best precision. 0 means no angle,
154 // while SAL_MAX_UINT32 means pi. Thus, the higher the value, the more left
155 // the edge traverses.
156 return (getSortValue() > rComp.getSortValue());
158 else
160 return fTools::less(getStart().getX(), rComp.getStart().getX());
163 else
165 return fTools::less(getStart().getY(), rComp.getStart().getY());
169 // method for cut support
170 B2DPoint getCutPointForGivenY(double fGivenY) const
172 // Calculate cut point locally (do not use interpolate) since it is numerically
173 // necessary to guarantee the new, equal Y-coordinate
174 const double fFactor((fGivenY - getStart().getY()) / getDeltaY());
175 const double fDeltaXNew(fFactor * getDeltaX());
177 return B2DPoint(getStart().getX() + fDeltaXNew, fGivenY);
181 // define double linked list of edges (for fast random insert)
183 typedef std::list< TrDeEdgeEntry > TrDeEdgeEntries;
185 } // end of anonymous namespace
186 } // end of namespace basegfx
188 namespace basegfx
190 namespace trapezoidhelper
192 // FIXME: templatize this and use it for TrDeEdgeEntries too ...
194 /// Class to allow efficient allocation and release of B2DPoints
195 class PointBlockAllocator
197 static const size_t nBlockSize = 32;
198 size_t nCurPoint;
199 B2DPoint *mpPointBase;
200 /// Special case the first allocation to avoid it.
201 B2DPoint maFirstStackBlock[nBlockSize];
202 std::vector< B2DPoint * > maBlocks;
203 public:
204 PointBlockAllocator() :
205 nCurPoint( nBlockSize ),
206 mpPointBase( maFirstStackBlock )
210 ~PointBlockAllocator()
212 while(maBlocks.size() > 0)
214 delete [] maBlocks.back();
215 maBlocks.pop_back();
219 B2DPoint *allocatePoint()
221 if(nCurPoint >= nBlockSize)
223 mpPointBase = new B2DPoint[nBlockSize];
224 maBlocks.push_back(mpPointBase);
225 nCurPoint = 0;
227 return mpPointBase + nCurPoint++;
230 B2DPoint *allocatePoint(const B2DTuple &rPoint)
232 B2DPoint *pPoint = allocatePoint();
233 *pPoint = rPoint;
234 return pPoint;
237 /// This is a very uncommon case but why not ...
238 void freeIfLast(B2DPoint const *pPoint)
240 // just re-use the last point if we can.
241 if ( nCurPoint > 0 && pPoint == mpPointBase + nCurPoint - 1 )
242 nCurPoint--;
246 // helper class to handle the complete trapezoid subdivision of a PolyPolygon
247 class TrapezoidSubdivider
249 private:
250 // local data
251 sal_uInt32 mnInitialEdgeEntryCount;
252 TrDeEdgeEntries maTrDeEdgeEntries;
253 std::vector< B2DPoint > maPoints;
254 /// new points allocated for cuts
255 PointBlockAllocator maNewPoints;
257 void addEdgeSorted(
258 TrDeEdgeEntries::iterator aCurrent,
259 const TrDeEdgeEntry& rNewEdge)
261 // Loop while new entry is bigger, use operator<
262 while(aCurrent != maTrDeEdgeEntries.end() && (*aCurrent) < rNewEdge)
264 ++aCurrent;
267 // Insert before first which is smaller or equal or at end
268 maTrDeEdgeEntries.insert(aCurrent, rNewEdge);
271 bool splitEdgeAtGivenPoint(
272 TrDeEdgeEntries::reference aEdge,
273 const B2DPoint& rCutPoint,
274 const TrDeEdgeEntries::iterator& aCurrent)
276 // do not create edges without deltaY: do not split when start is identical
277 if(aEdge.getStart().equal(rCutPoint))
279 return false;
282 // do not create edges without deltaY: do not split when end is identical
283 if(aEdge.getEnd().equal(rCutPoint))
285 return false;
288 const double fOldDeltaYStart(rCutPoint.getY() - aEdge.getStart().getY());
290 if(fTools::lessOrEqual(fOldDeltaYStart, 0.0))
292 // do not split: the resulting edge would be horizontal
293 // correct it to new start point
294 aEdge.setStart(&rCutPoint);
295 return false;
298 const double fNewDeltaYStart(aEdge.getEnd().getY() - rCutPoint.getY());
300 if(fTools::lessOrEqual(fNewDeltaYStart, 0.0))
302 // do not split: the resulting edge would be horizontal
303 // correct it to new end point
304 aEdge.setEnd(&rCutPoint);
305 return false;
308 // Create new entry
309 const TrDeEdgeEntry aNewEdge(
310 &rCutPoint,
311 &aEdge.getEnd(),
312 aEdge.getSortValue());
314 // Correct old entry
315 aEdge.setEnd(&rCutPoint);
317 // Insert sorted (to avoid new sort)
318 addEdgeSorted(aCurrent, aNewEdge);
320 return true;
323 bool testAndCorrectEdgeIntersection(
324 TrDeEdgeEntries::reference aEdgeA,
325 TrDeEdgeEntries::reference aEdgeB,
326 const TrDeEdgeEntries::iterator& aCurrent)
328 // Exclude simple cases: same start or end point
329 if(aEdgeA.getStart().equal(aEdgeB.getStart()))
331 return false;
334 if(aEdgeA.getStart().equal(aEdgeB.getEnd()))
336 return false;
339 if(aEdgeA.getEnd().equal(aEdgeB.getStart()))
341 return false;
344 if(aEdgeA.getEnd().equal(aEdgeB.getEnd()))
346 return false;
349 // Exclude simple cases: one of the edges has no length anymore
350 if(aEdgeA.getStart().equal(aEdgeA.getEnd()))
352 return false;
355 if(aEdgeB.getStart().equal(aEdgeB.getEnd()))
357 return false;
360 // check if one point is on the other edge (a touch, not a cut)
361 const B2DVector aDeltaB(aEdgeB.getDeltaX(), aEdgeB.getDeltaY());
363 if(utils::isPointOnEdge(aEdgeA.getStart(), aEdgeB.getStart(), aDeltaB))
365 return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getStart(), aCurrent);
368 if(utils::isPointOnEdge(aEdgeA.getEnd(), aEdgeB.getStart(), aDeltaB))
370 return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getEnd(), aCurrent);
373 const B2DVector aDeltaA(aEdgeA.getDeltaX(), aEdgeA.getDeltaY());
375 if(utils::isPointOnEdge(aEdgeB.getStart(), aEdgeA.getStart(), aDeltaA))
377 return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getStart(), aCurrent);
380 if(utils::isPointOnEdge(aEdgeB.getEnd(), aEdgeA.getStart(), aDeltaA))
382 return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getEnd(), aCurrent);
385 // check for cut inside edges. Use both t-values to choose the more precise
386 // one later
387 double fCutA(0.0);
388 double fCutB(0.0);
390 if(utils::findCut(
391 aEdgeA.getStart(), aDeltaA,
392 aEdgeB.getStart(), aDeltaB,
393 CutFlagValue::LINE,
394 &fCutA,
395 &fCutB) != CutFlagValue::NONE)
397 // use a simple metric (length criteria) for choosing the numerically
398 // better cut
399 const double fSimpleLengthA(aDeltaA.getX() + aDeltaA.getY());
400 const double fSimpleLengthB(aDeltaB.getX() + aDeltaB.getY());
401 const bool bAIsLonger(fSimpleLengthA > fSimpleLengthB);
402 B2DPoint* pNewPoint = bAIsLonger
403 ? maNewPoints.allocatePoint(aEdgeA.getStart() + (fCutA * aDeltaA))
404 : maNewPoints.allocatePoint(aEdgeB.getStart() + (fCutB * aDeltaB));
406 // try to split both edges
407 bool bRetval = splitEdgeAtGivenPoint(aEdgeA, *pNewPoint, aCurrent);
408 bRetval |= splitEdgeAtGivenPoint(aEdgeB, *pNewPoint, aCurrent);
410 if(!bRetval)
411 maNewPoints.freeIfLast(pNewPoint);
413 return bRetval;
416 return false;
419 void solveHorizontalEdges(TrDeSimpleEdges& rTrDeSimpleEdges)
421 if(!rTrDeSimpleEdges.empty() && !maTrDeEdgeEntries.empty())
423 // there were horizontal edges. These can be excluded, but
424 // cuts with other edges need to be solved and added before
425 // ignoring them
426 for(TrDeSimpleEdge & rHorEdge : rTrDeSimpleEdges)
428 // get horizontal edge as candidate; prepare its range and fixed Y
429 const B1DRange aRange(rHorEdge.getStart().getX(), rHorEdge.getEnd().getX());
430 const double fFixedY(rHorEdge.getStart().getY());
432 // loop over traversing edges
433 TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin());
437 // get compare edge
438 TrDeEdgeEntries::reference aCompare(*aCurrent++);
440 if(fTools::lessOrEqual(aCompare.getEnd().getY(), fFixedY))
442 // edge ends above horizontal edge, continue
443 continue;
446 if(fTools::moreOrEqual(aCompare.getStart().getY(), fFixedY))
448 // edge starts below horizontal edge, continue
449 continue;
452 // vertical overlap, get horizontal range
453 const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX());
455 if(aRange.overlaps(aCompareRange))
457 // possible cut, get cut point
458 const B2DPoint aSplit(aCompare.getCutPointForGivenY(fFixedY));
460 if(fTools::more(aSplit.getX(), aRange.getMinimum())
461 && fTools::less(aSplit.getX(), aRange.getMaximum()))
463 // cut is in XRange of horizontal edge, potentially needed cut
464 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aSplit);
466 if(!splitEdgeAtGivenPoint(aCompare, *pNewPoint, aCurrent))
468 maNewPoints.freeIfLast(pNewPoint);
473 while(aCurrent != maTrDeEdgeEntries.end()
474 && fTools::less(aCurrent->getStart().getY(), fFixedY));
479 public:
480 explicit TrapezoidSubdivider(
481 const B2DPolyPolygon& rSourcePolyPolygon)
482 : mnInitialEdgeEntryCount(0),
483 maTrDeEdgeEntries(),
484 maPoints(),
485 maNewPoints()
487 B2DPolyPolygon aSource(rSourcePolyPolygon);
488 const sal_uInt32 nPolygonCount(rSourcePolyPolygon.count());
489 TrDeSimpleEdges aTrDeSimpleEdges;
490 sal_uInt32 a(0), b(0);
491 sal_uInt32 nAllPointCount(0);
493 // ensure there are no curves used
494 if(aSource.areControlPointsUsed())
496 aSource = aSource.getDefaultAdaptiveSubdivision();
499 for(a = 0; a < nPolygonCount; a++)
501 // 1st run: count points
502 const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a));
503 const sal_uInt32 nCount(aPolygonCandidate.count());
505 if(nCount > 2)
507 nAllPointCount += nCount;
511 if(nAllPointCount)
513 // reserve needed points. CAUTION: maPoints size is NOT to be changed anymore
514 // after 2nd loop since pointers to it are used in the edges
515 maPoints.reserve(nAllPointCount);
517 for(a = 0; a < nPolygonCount; a++)
519 // 2nd run: add points
520 const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a));
521 const sal_uInt32 nCount(aPolygonCandidate.count());
523 if(nCount > 2)
525 for(b = 0; b < nCount; b++)
527 maPoints.push_back(aPolygonCandidate.getB2DPoint(b));
532 // Moved the edge construction to a 3rd run: doing it in the 2nd run is
533 // possible(and i used it), but requires a working vector::reserve()
534 // implementation, else the vector will be reallocated and the pointers
535 // in the edges may be wrong. Security first here.
536 sal_uInt32 nStartIndex(0);
538 for(a = 0; a < nPolygonCount; a++)
540 const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a));
541 const sal_uInt32 nCount(aPolygonCandidate.count());
543 if(nCount > 2)
545 // get the last point of the current polygon
546 B2DPoint* pPrev(&maPoints[nCount + nStartIndex - 1]);
548 for(b = 0; b < nCount; b++)
550 // get next point
551 B2DPoint* pCurr(&maPoints[nStartIndex++]);
553 if(fTools::equal(pPrev->getY(), pCurr->getY()))
555 // horizontal edge, check for single point
556 if(!fTools::equal(pPrev->getX(), pCurr->getX()))
558 // X-order not needed, just add
559 aTrDeSimpleEdges.emplace_back(pPrev, pCurr);
561 const double fMiddle((pPrev->getY() + pCurr->getY()) * 0.5);
562 pPrev->setY(fMiddle);
563 pCurr->setY(fMiddle);
566 else
568 // vertical edge. Positive Y-direction is guaranteed by the
569 // TrDeEdgeEntry constructor
570 maTrDeEdgeEntries.emplace_back(pPrev, pCurr, 0);
571 mnInitialEdgeEntryCount++;
574 // prepare next step
575 pPrev = pCurr;
581 if(!maTrDeEdgeEntries.empty())
583 // single and initial sort of traversing edges
584 maTrDeEdgeEntries.sort();
586 // solve horizontal edges if there are any detected
587 solveHorizontalEdges(aTrDeSimpleEdges);
591 void Subdivide(B2DTrapezoidVector& ro_Result)
593 // This is the central subdivider. The strategy is to use the first two entries
594 // from the traversing edges as a potential trapezoid and do the needed corrections
595 // and adaptions on the way.
597 // There always must be two edges with the same YStart value: When adding the polygons
598 // in the constructor, there is always a topmost point from which two edges start; when
599 // the topmost is an edge, there is a start and end of this edge from which two edges
600 // start. All cases have two edges with same StartY (QED).
602 // Based on this these edges get corrected when:
603 // - one is longer than the other
604 // - they intersect
605 // - they intersect with other edges
606 // - another edge starts inside the thought trapezoid
608 // All this cases again produce a valid state so that the first two edges have a common
609 // Ystart again. Some cases lead to a restart of the process, some allow consuming the
610 // edges and create the intended trapezoid.
612 // Be careful when doing changes here: it is essential to keep all possible paths
613 // in valid states and to be numerically correct. This is especially needed e.g.
614 // by using fTools::equal(..) in the more robust small-value incarnation.
615 B1DRange aLeftRange;
616 B1DRange aRightRange;
618 if(!maTrDeEdgeEntries.empty())
620 // measuring shows that the relation between edges and created trapezoids is
621 // mostly in the 1:1 range, thus reserve as much trapezoids as edges exist. Do
622 // not use maTrDeEdgeEntries.size() since that may be a non-constant time
623 // operation for Lists. Instead, use mnInitialEdgeEntryCount which will contain
624 // the roughly counted adds to the List
625 ro_Result.reserve(ro_Result.size() + mnInitialEdgeEntryCount);
628 while(!maTrDeEdgeEntries.empty())
630 // Prepare current operator and get first edge
631 TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin());
632 TrDeEdgeEntries::reference aLeft(*aCurrent++);
634 if(aCurrent == maTrDeEdgeEntries.end())
636 // Should not happen: No 2nd edge; consume the single edge
637 // to not have an endless loop and start next. During development
638 // i constantly had breakpoints here, so i am sure enough to add an
639 // assertion here
640 OSL_FAIL("Trapezoid decomposer in illegal state (!)");
641 maTrDeEdgeEntries.pop_front();
642 continue;
645 // get second edge
646 TrDeEdgeEntries::reference aRight(*aCurrent++);
648 if(!fTools::equal(aLeft.getStart().getY(), aRight.getStart().getY()))
650 // Should not happen: We have a 2nd edge, but YStart is on another
651 // line; consume the single edge to not have an endless loop and start
652 // next. During development i constantly had breakpoints here, so i am
653 // sure enough to add an assertion here
654 OSL_FAIL("Trapezoid decomposer in illegal state (!)");
655 maTrDeEdgeEntries.pop_front();
656 continue;
659 // aLeft and aRight build a thought trapezoid now. They have a common
660 // start line (same Y for start points). Potentially, one of the edges
661 // is longer than the other. It is only needed to look at the shorter
662 // length which build the potential trapezoid. To do so, get the end points
663 // locally and adapt the evtl. longer one. Use only aLeftEnd and aRightEnd
664 // from here on, not the aLeft.getEnd() or aRight.getEnd() accesses.
665 B2DPoint aLeftEnd(aLeft.getEnd());
666 B2DPoint aRightEnd(aRight.getEnd());
668 // check if end points are on the same line. If yes, no adaption
669 // needs to be prepared. Also remember which one actually is longer.
670 const bool bEndOnSameLine(fTools::equal(aLeftEnd.getY(), aRightEnd.getY()));
671 bool bLeftIsLonger(false);
673 if(!bEndOnSameLine)
675 // check which edge is longer and correct accordingly
676 bLeftIsLonger = fTools::more(aLeftEnd.getY(), aRightEnd.getY());
678 if(bLeftIsLonger)
680 aLeftEnd = aLeft.getCutPointForGivenY(aRightEnd.getY());
682 else
684 aRightEnd = aRight.getCutPointForGivenY(aLeftEnd.getY());
688 // check for same start and end points
689 const bool bSameStartPoint(aLeft.getStart().equal(aRight.getStart()));
690 const bool bSameEndPoint(aLeftEnd.equal(aRightEnd));
692 // check the simple case that the edges form a 'blind' edge (deadend)
693 if(bSameStartPoint && bSameEndPoint)
695 // correct the longer edge if prepared
696 if(!bEndOnSameLine)
698 if(bLeftIsLonger)
700 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aLeftEnd);
702 if(!splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent))
704 maNewPoints.freeIfLast(pNewPoint);
707 else
709 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aRightEnd);
711 if(!splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent))
713 maNewPoints.freeIfLast(pNewPoint);
718 // consume both edges and start next run
719 maTrDeEdgeEntries.pop_front();
720 maTrDeEdgeEntries.pop_front();
722 continue;
725 // check if the edges self-intersect. This can only happen when
726 // start and end point are different
727 bool bRangesSet(false);
729 if(!(bSameStartPoint || bSameEndPoint))
731 // get XRanges of edges
732 aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX());
733 aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX());
734 bRangesSet = true;
736 // use fast range test first
737 if(aLeftRange.overlaps(aRightRange))
739 // real cut test and correction. If correction was needed,
740 // start new run
741 if(testAndCorrectEdgeIntersection(aLeft, aRight, aCurrent))
743 continue;
748 // now we need to check if there are intersections with other edges
749 // or if other edges start inside the candidate trapezoid
750 if(aCurrent != maTrDeEdgeEntries.end()
751 && fTools::less(aCurrent->getStart().getY(), aLeftEnd.getY()))
753 // get XRanges of edges
754 if(!bRangesSet)
756 aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX());
757 aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX());
760 // build full XRange for fast check
761 B1DRange aAllRange(aLeftRange);
762 aAllRange.expand(aRightRange);
764 // prepare loop iterator; aCurrent needs to stay unchanged for
765 // possibly sorted insertions of new EdgeNodes. Also prepare stop flag
766 TrDeEdgeEntries::iterator aLoop(aCurrent);
767 bool bDone(false);
771 // get compare edge and its XRange
772 TrDeEdgeEntries::reference aCompare(*aLoop++);
774 // avoid edges using the same start point as one of
775 // the edges. These can neither have their start point
776 // in the thought trapezoid nor cut with one of the edges
777 if(aCompare.getStart().equal(aRight.getStart()))
779 continue;
782 // get compare XRange
783 const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX());
785 // use fast range test first
786 if(aAllRange.overlaps(aCompareRange))
788 // check for start point inside thought trapezoid
789 if(fTools::more(aCompare.getStart().getY(), aLeft.getStart().getY()))
791 // calculate the two possible split points at compare's Y
792 const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aCompare.getStart().getY()));
793 const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aCompare.getStart().getY()));
795 // check for start point of aCompare being inside thought
796 // trapezoid
797 if(aCompare.getStart().getX() >= aSplitLeft.getX() &&
798 aCompare.getStart().getX() <= aSplitRight.getX())
800 // is inside, correct and restart loop
801 B2DPoint* pNewLeft = maNewPoints.allocatePoint(aSplitLeft);
803 if(splitEdgeAtGivenPoint(aLeft, *pNewLeft, aCurrent))
805 bDone = true;
807 else
809 maNewPoints.freeIfLast(pNewLeft);
812 B2DPoint* pNewRight = maNewPoints.allocatePoint(aSplitRight);
814 if(splitEdgeAtGivenPoint(aRight, *pNewRight, aCurrent))
816 bDone = true;
818 else
820 maNewPoints.freeIfLast(pNewRight);
825 if(!bDone && aLeftRange.overlaps(aCompareRange))
827 // test for concrete cut of compare edge with left edge
828 bDone = testAndCorrectEdgeIntersection(aLeft, aCompare, aCurrent);
831 if(!bDone && aRightRange.overlaps(aCompareRange))
833 // test for concrete cut of compare edge with Right edge
834 bDone = testAndCorrectEdgeIntersection(aRight, aCompare, aCurrent);
838 while(!bDone
839 && aLoop != maTrDeEdgeEntries.end()
840 && fTools::less(aLoop->getStart().getY(), aLeftEnd.getY()));
842 if(bDone)
844 // something needed to be changed; start next loop
845 continue;
849 // when we get here, the intended trapezoid can be used. It needs to
850 // be corrected possibly (if prepared); but this is no reason not to
851 // use it in the same loop iteration
852 if(!bEndOnSameLine)
854 if(bLeftIsLonger)
856 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aLeftEnd);
858 if(!splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent))
860 maNewPoints.freeIfLast(pNewPoint);
863 else
865 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aRightEnd);
867 if(!splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent))
869 maNewPoints.freeIfLast(pNewPoint);
874 // the two edges start at the same Y, they use the same DeltaY, they
875 // do not cut themselves and not any other edge in range. Create a
876 // B2DTrapezoid and consume both edges
877 ro_Result.emplace_back(
878 aLeft.getStart().getX(),
879 aRight.getStart().getX(),
880 aLeft.getStart().getY(),
881 aLeftEnd.getX(),
882 aRightEnd.getX(),
883 aLeftEnd.getY());
885 maTrDeEdgeEntries.pop_front();
886 maTrDeEdgeEntries.pop_front();
890 } // end of anonymous namespace
891 } // end of namespace basegfx
893 namespace basegfx
895 B2DTrapezoid::B2DTrapezoid(
896 const double& rfTopXLeft,
897 const double& rfTopXRight,
898 const double& rfTopY,
899 const double& rfBottomXLeft,
900 const double& rfBottomXRight,
901 const double& rfBottomY)
902 : mfTopXLeft(rfTopXLeft),
903 mfTopXRight(rfTopXRight),
904 mfTopY(rfTopY),
905 mfBottomXLeft(rfBottomXLeft),
906 mfBottomXRight(rfBottomXRight),
907 mfBottomY(rfBottomY)
909 // guarantee mfTopXRight >= mfTopXLeft
910 if(mfTopXLeft > mfTopXRight)
912 std::swap(mfTopXLeft, mfTopXRight);
915 // guarantee mfBottomXRight >= mfBottomXLeft
916 if(mfBottomXLeft > mfBottomXRight)
918 std::swap(mfBottomXLeft, mfBottomXRight);
921 // guarantee mfBottomY >= mfTopY
922 if(mfTopY > mfBottomY)
924 std::swap(mfTopY, mfBottomY);
925 std::swap(mfTopXLeft, mfBottomXLeft);
926 std::swap(mfTopXRight, mfBottomXRight);
930 B2DPolygon B2DTrapezoid::getB2DPolygon() const
932 B2DPolygon aRetval;
934 aRetval.append(B2DPoint(getTopXLeft(), getTopY()));
935 aRetval.append(B2DPoint(getTopXRight(), getTopY()));
936 aRetval.append(B2DPoint(getBottomXRight(), getBottomY()));
937 aRetval.append(B2DPoint(getBottomXLeft(), getBottomY()));
938 aRetval.setClosed(true);
940 return aRetval;
942 } // end of namespace basegfx
944 namespace basegfx
946 namespace utils
948 // convert Source utils::PolyPolygon to trapezoids
949 void trapezoidSubdivide(B2DTrapezoidVector& ro_Result, const B2DPolyPolygon& rSourcePolyPolygon)
951 trapezoidhelper::TrapezoidSubdivider aTrapezoidSubdivider(rSourcePolyPolygon);
953 aTrapezoidSubdivider.Subdivide(ro_Result);
956 void createLineTrapezoidFromEdge(
957 B2DTrapezoidVector& ro_Result,
958 const B2DPoint& rPointA,
959 const B2DPoint& rPointB,
960 double fLineWidth)
962 if(fTools::lessOrEqual(fLineWidth, 0.0))
964 // no line witdh
965 return;
968 if(rPointA.equal(rPointB))
970 // points are equal, no edge
971 return;
974 const double fHalfLineWidth(0.5 * fLineWidth);
976 if(fTools::equal(rPointA.getX(), rPointB.getX()))
978 // vertical line
979 const double fLeftX(rPointA.getX() - fHalfLineWidth);
980 const double fRightX(rPointA.getX() + fHalfLineWidth);
982 ro_Result.emplace_back(
983 fLeftX,
984 fRightX,
985 std::min(rPointA.getY(), rPointB.getY()),
986 fLeftX,
987 fRightX,
988 std::max(rPointA.getY(), rPointB.getY()));
990 else if(fTools::equal(rPointA.getY(), rPointB.getY()))
992 // horizontal line
993 const double fLeftX(std::min(rPointA.getX(), rPointB.getX()));
994 const double fRightX(std::max(rPointA.getX(), rPointB.getX()));
996 ro_Result.emplace_back(
997 fLeftX,
998 fRightX,
999 rPointA.getY() - fHalfLineWidth,
1000 fLeftX,
1001 fRightX,
1002 rPointA.getY() + fHalfLineWidth);
1004 else
1006 // diagonal line
1007 // create perpendicular vector
1008 const B2DVector aDelta(rPointB - rPointA);
1009 B2DVector aPerpendicular(-aDelta.getY(), aDelta.getX());
1010 aPerpendicular.setLength(fHalfLineWidth);
1012 // create StartLow, StartHigh, EndLow and EndHigh
1013 const B2DPoint aStartLow(rPointA + aPerpendicular);
1014 const B2DPoint aStartHigh(rPointA - aPerpendicular);
1015 const B2DPoint aEndHigh(rPointB - aPerpendicular);
1016 const B2DPoint aEndLow(rPointB + aPerpendicular);
1018 // create EdgeEntries
1019 basegfx::trapezoidhelper::TrDeEdgeEntries aTrDeEdgeEntries;
1021 aTrDeEdgeEntries.emplace_back(&aStartLow, &aStartHigh, 0);
1022 aTrDeEdgeEntries.emplace_back(&aStartHigh, &aEndHigh, 0);
1023 aTrDeEdgeEntries.emplace_back(&aEndHigh, &aEndLow, 0);
1024 aTrDeEdgeEntries.emplace_back(&aEndLow, &aStartLow, 0);
1025 aTrDeEdgeEntries.sort();
1027 // here we know we have exactly four edges, and they do not cut, touch or
1028 // intersect. This makes processing much easier. Get the first two as start
1029 // edges for the thought trapezoid
1030 basegfx::trapezoidhelper::TrDeEdgeEntries::iterator aCurrent(aTrDeEdgeEntries.begin());
1031 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft(*aCurrent++);
1032 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight(*aCurrent++);
1033 const bool bEndOnSameLine(fTools::equal(aLeft.getEnd().getY(), aRight.getEnd().getY()));
1035 if(bEndOnSameLine)
1037 // create two triangle trapezoids
1038 ro_Result.emplace_back(
1039 aLeft.getStart().getX(),
1040 aRight.getStart().getX(),
1041 aLeft.getStart().getY(),
1042 aLeft.getEnd().getX(),
1043 aRight.getEnd().getX(),
1044 aLeft.getEnd().getY());
1046 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
1047 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
1049 ro_Result.emplace_back(
1050 aLeft2.getStart().getX(),
1051 aRight2.getStart().getX(),
1052 aLeft2.getStart().getY(),
1053 aLeft2.getEnd().getX(),
1054 aRight2.getEnd().getX(),
1055 aLeft2.getEnd().getY());
1057 else
1059 // create three trapezoids. Check which edge is longer and
1060 // correct accordingly
1061 const bool bLeftIsLonger(fTools::more(aLeft.getEnd().getY(), aRight.getEnd().getY()));
1063 if(bLeftIsLonger)
1065 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
1066 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
1067 const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aRight.getEnd().getY()));
1068 const B2DPoint aSplitRight(aRight2.getCutPointForGivenY(aLeft.getEnd().getY()));
1070 ro_Result.emplace_back(
1071 aLeft.getStart().getX(),
1072 aRight.getStart().getX(),
1073 aLeft.getStart().getY(),
1074 aSplitLeft.getX(),
1075 aRight.getEnd().getX(),
1076 aRight.getEnd().getY());
1078 ro_Result.emplace_back(
1079 aSplitLeft.getX(),
1080 aRight.getEnd().getX(),
1081 aRight.getEnd().getY(),
1082 aLeft2.getStart().getX(),
1083 aSplitRight.getX(),
1084 aLeft2.getStart().getY());
1086 ro_Result.emplace_back(
1087 aLeft2.getStart().getX(),
1088 aSplitRight.getX(),
1089 aLeft2.getStart().getY(),
1090 aLeft2.getEnd().getX(),
1091 aRight2.getEnd().getX(),
1092 aLeft2.getEnd().getY());
1094 else
1096 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
1097 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
1098 const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aLeft.getEnd().getY()));
1099 const B2DPoint aSplitLeft(aLeft2.getCutPointForGivenY(aRight.getEnd().getY()));
1101 ro_Result.emplace_back(
1102 aLeft.getStart().getX(),
1103 aRight.getStart().getX(),
1104 aLeft.getStart().getY(),
1105 aLeft.getEnd().getX(),
1106 aSplitRight.getX(),
1107 aLeft.getEnd().getY());
1109 ro_Result.emplace_back(
1110 aLeft.getEnd().getX(),
1111 aSplitRight.getX(),
1112 aLeft.getEnd().getY(),
1113 aSplitLeft.getX(),
1114 aRight.getEnd().getX(),
1115 aRight2.getStart().getY());
1117 ro_Result.emplace_back(
1118 aSplitLeft.getX(),
1119 aRight.getEnd().getX(),
1120 aRight2.getStart().getY(),
1121 aLeft2.getEnd().getX(),
1122 aRight2.getEnd().getX(),
1123 aLeft2.getEnd().getY());
1129 void createLineTrapezoidFromB2DPolygon(
1130 B2DTrapezoidVector& ro_Result,
1131 const B2DPolygon& rPolygon,
1132 double fLineWidth)
1134 if(fTools::lessOrEqual(fLineWidth, 0.0))
1136 return;
1139 // ensure there are no curves used
1140 B2DPolygon aSource(rPolygon);
1142 if(aSource.areControlPointsUsed())
1144 const double fPrecisionFactor = 0.25;
1145 aSource = adaptiveSubdivideByDistance( aSource, fLineWidth * fPrecisionFactor );
1148 const sal_uInt32 nPointCount(aSource.count());
1150 if(!nPointCount)
1152 return;
1155 const sal_uInt32 nEdgeCount(aSource.isClosed() ? nPointCount : nPointCount - 1);
1156 B2DPoint aCurrent(aSource.getB2DPoint(0));
1158 ro_Result.reserve(ro_Result.size() + (3 * nEdgeCount));
1160 for(sal_uInt32 a(0); a < nEdgeCount; a++)
1162 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
1163 const B2DPoint aNext(aSource.getB2DPoint(nNextIndex));
1165 createLineTrapezoidFromEdge(ro_Result, aCurrent, aNext, fLineWidth);
1166 aCurrent = aNext;
1171 } // end of namespace utils
1172 } // end of namespace basegfx
1174 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */