bump product version to 5.0.4.1
[LibreOffice.git] / basegfx / source / polygon / b2dtrapezoid.cxx
blobb813e03a9b85780b320e9a407aa3f63ef8a4e8d6
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 ege. 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 holdingthe 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(0 != mnSortValue)
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 = 0)
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 neeed 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(0 != pNewStart, "No null pointer allowed here (!)");
120 if(mpStart != pNewStart)
122 mpStart = pNewStart;
124 // no horizontal edges allowed, all neeed to traverse vertivally
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(0 != pNewEnd, "No null pointer allowed here (!)");
134 if(mpEnd != pNewEnd)
136 mpEnd = pNewEnd;
138 // no horizontal edges allowed, all neeed to traverse vertivally
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(), fTools::getSmallValue()))
148 if(fTools::equal(getStart().getX(), rComp.getStart().getX(), fTools::getSmallValue()))
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)
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 *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 TrDeEdgeEntries::iterator aCurrent)
276 // do not create edges without deltaY: do not split when start is identical
277 if(aEdge.getStart().equal(rCutPoint, fTools::getSmallValue()))
279 return false;
282 // do not create edges without deltaY: do not split when end is identical
283 if(aEdge.getEnd().equal(rCutPoint, fTools::getSmallValue()))
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 TrDeEdgeEntries::iterator aCurrent)
328 // Exclude simple cases: same start or end point
329 if(aEdgeA.getStart().equal(aEdgeB.getStart(), fTools::getSmallValue()))
331 return false;
334 if(aEdgeA.getStart().equal(aEdgeB.getEnd(), fTools::getSmallValue()))
336 return false;
339 if(aEdgeA.getEnd().equal(aEdgeB.getStart(), fTools::getSmallValue()))
341 return false;
344 if(aEdgeA.getEnd().equal(aEdgeB.getEnd(), fTools::getSmallValue()))
346 return false;
349 // Exclude simple cases: one of the edges has no length anymore
350 if(aEdgeA.getStart().equal(aEdgeA.getEnd(), fTools::getSmallValue()))
352 return false;
355 if(aEdgeB.getStart().equal(aEdgeB.getEnd(), fTools::getSmallValue()))
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(tools::isPointOnEdge(aEdgeA.getStart(), aEdgeB.getStart(), aDeltaB))
365 return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getStart(), aCurrent);
368 if(tools::isPointOnEdge(aEdgeA.getEnd(), aEdgeB.getStart(), aDeltaB))
370 return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getEnd(), aCurrent);
373 const B2DVector aDeltaA(aEdgeA.getDeltaX(), aEdgeA.getDeltaY());
375 if(tools::isPointOnEdge(aEdgeB.getStart(), aEdgeA.getStart(), aDeltaA))
377 return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getStart(), aCurrent);
380 if(tools::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(tools::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(sal_uInt32 a = 0; a < rTrDeSimpleEdges.size(); a++)
428 // get horizontal edge as candidate; prepare its range and fixed Y
429 const TrDeSimpleEdge& rHorEdge = rTrDeSimpleEdges[a];
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 const sal_uInt32 nPolygonCount(rSourcePolyPolygon.count());
490 TrDeSimpleEdges aTrDeSimpleEdges;
491 sal_uInt32 a(0), b(0);
492 sal_uInt32 nAllPointCount(0);
494 // ensure there are no curves used
495 if(aSource.areControlPointsUsed())
497 aSource = aSource.getDefaultAdaptiveSubdivision();
500 for(a = 0; a < nPolygonCount; a++)
502 // 1st run: count points
503 const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a));
504 const sal_uInt32 nCount(aPolygonCandidate.count());
506 if(nCount > 2)
508 nAllPointCount += nCount;
512 if(nAllPointCount)
514 // reserve needed points. CAUTION: maPoints size is NOT to be changed anymore
515 // after 2nd loop since pointers to it are used in the edges
516 maPoints.reserve(nAllPointCount);
518 for(a = 0; a < nPolygonCount; a++)
520 // 2nd run: add points
521 const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a));
522 const sal_uInt32 nCount(aPolygonCandidate.count());
524 if(nCount > 2)
526 for(b = 0; b < nCount; b++)
528 maPoints.push_back(aPolygonCandidate.getB2DPoint(b));
533 // Moved the edge construction to a 3rd run: doing it in the 2nd run is
534 // possible(and i used it), but requires a working vector::reserve()
535 // implementation, else the vector will be reallocated and the pointers
536 // in the edges may be wrong. Security first here.
537 sal_uInt32 nStartIndex(0);
539 for(a = 0; a < nPolygonCount; a++)
541 const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a));
542 const sal_uInt32 nCount(aPolygonCandidate.count());
544 if(nCount > 2)
546 // get the last point of the current polygon
547 B2DPoint* pPrev(&maPoints[nCount + nStartIndex - 1]);
549 for(b = 0; b < nCount; b++)
551 // get next point
552 B2DPoint* pCurr(&maPoints[nStartIndex++]);
554 if(fTools::equal(pPrev->getY(), pCurr->getY(), fTools::getSmallValue()))
556 // horizontal edge, check for single point
557 if(!fTools::equal(pPrev->getX(), pCurr->getX(), fTools::getSmallValue()))
559 // X-order not needed, just add
560 aTrDeSimpleEdges.push_back(TrDeSimpleEdge(pPrev, pCurr));
562 const double fMiddle((pPrev->getY() + pCurr->getY()) * 0.5);
563 pPrev->setY(fMiddle);
564 pCurr->setY(fMiddle);
567 else
569 // vertical edge. Positive Y-direction is guaranteed by the
570 // TrDeEdgeEntry constructor
571 maTrDeEdgeEntries.push_back(TrDeEdgeEntry(pPrev, pCurr, 0));
572 mnInitialEdgeEntryCount++;
575 // prepare next step
576 pPrev = pCurr;
582 if(!maTrDeEdgeEntries.empty())
584 // single and initial sort of traversing edges
585 maTrDeEdgeEntries.sort();
587 // solve horizontal edges if there are any detected
588 solveHorizontalEdges(aTrDeSimpleEdges);
592 void Subdivide(B2DTrapezoidVector& ro_Result)
594 // This is the central subdivider. The strategy is to use the first two entries
595 // from the traversing edges as a potential trapezoid and do the needed corrections
596 // and adaptions on the way.
598 // There always must be two edges with the same YStart value: When adding the polygons
599 // in the constructor, there is always a topmost point from which two edges start; when
600 // the topmost is an edge, there is a start and end of this edge from which two edges
601 // start. All cases have two edges with same StartY (QED).
603 // Based on this these edges get corrected when:
604 // - one is longer than the other
605 // - they intersect
606 // - they intersect with other edges
607 // - another edge starts inside the thought trapezoid
609 // All this cases again produce a valid state so that the first two edges have a common
610 // Ystart again. Some cases lead to a restart of the process, some allow consuming the
611 // edges and create the intended trapezoid.
613 // Be careful when doing chages here: It is essential to keep all possible paths
614 // in valid states and to be numerically correct. This is especially needed e.g.
615 // by using fTools::equal(..) in the more robust small-value incarnation.
616 B1DRange aLeftRange;
617 B1DRange aRightRange;
619 if(!maTrDeEdgeEntries.empty())
621 // measuring shows that the relation between edges and created trapezoids is
622 // mostly in the 1:1 range, thus reserve as much trapezoids as edges exist. Do
623 // not use maTrDeEdgeEntries.size() since that may be a non-constant time
624 // operation for Lists. Instead, use mnInitialEdgeEntryCount which will contain
625 // the roughly counted adds to the List
626 ro_Result.reserve(ro_Result.size() + mnInitialEdgeEntryCount);
629 while(!maTrDeEdgeEntries.empty())
631 // Prepare current operator and get first edge
632 TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin());
633 TrDeEdgeEntries::reference aLeft(*aCurrent++);
635 if(aCurrent == maTrDeEdgeEntries.end())
637 // Should not happen: No 2nd edge; consume the single edge
638 // to not have an endless loop and start next. During development
639 // i constantly had breakpoints here, so i am sure enough to add an
640 // assertion here
641 OSL_FAIL("Trapeziod decomposer in illegal state (!)");
642 maTrDeEdgeEntries.pop_front();
643 continue;
646 // get second edge
647 TrDeEdgeEntries::reference aRight(*aCurrent++);
649 if(!fTools::equal(aLeft.getStart().getY(), aRight.getStart().getY(), fTools::getSmallValue()))
651 // Should not happen: We have a 2nd edge, but YStart is on another
652 // line; consume the single edge to not have an endless loop and start
653 // next. During development i constantly had breakpoints here, so i am
654 // sure enough to add an assertion here
655 OSL_FAIL("Trapeziod decomposer in illegal state (!)");
656 maTrDeEdgeEntries.pop_front();
657 continue;
660 // aLeft and aRight build a thought trapezoid now. They have a common
661 // start line (same Y for start points). Potentially, one of the edges
662 // is longer than the other. It is only needed to look at the shorter
663 // length which build the potential trapezoid. To do so, get the end points
664 // locally and adapt the evtl. longer one. Use only aLeftEnd and aRightEnd
665 // from here on, not the aLeft.getEnd() or aRight.getEnd() accesses.
666 B2DPoint aLeftEnd(aLeft.getEnd());
667 B2DPoint aRightEnd(aRight.getEnd());
669 // check if end points are on the same line. If yes, no adaption
670 // needs to be prepared. Also remember which one actually is longer.
671 const bool bEndOnSameLine(fTools::equal(aLeftEnd.getY(), aRightEnd.getY(), fTools::getSmallValue()));
672 bool bLeftIsLonger(false);
674 if(!bEndOnSameLine)
676 // check which edge is longer and correct accordingly
677 bLeftIsLonger = fTools::more(aLeftEnd.getY(), aRightEnd.getY());
679 if(bLeftIsLonger)
681 aLeftEnd = aLeft.getCutPointForGivenY(aRightEnd.getY());
683 else
685 aRightEnd = aRight.getCutPointForGivenY(aLeftEnd.getY());
689 // check for same start and end points
690 const bool bSameStartPoint(aLeft.getStart().equal(aRight.getStart(), fTools::getSmallValue()));
691 const bool bSameEndPoint(aLeftEnd.equal(aRightEnd, fTools::getSmallValue()));
693 // check the simple case that the edges form a 'blind' edge (deadend)
694 if(bSameStartPoint && bSameEndPoint)
696 // correct the longer edge if prepared
697 if(!bEndOnSameLine)
699 if(bLeftIsLonger)
701 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aLeftEnd);
703 if(!splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent))
705 maNewPoints.freeIfLast(pNewPoint);
708 else
710 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aRightEnd);
712 if(!splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent))
714 maNewPoints.freeIfLast(pNewPoint);
719 // consume both edges and start next run
720 maTrDeEdgeEntries.pop_front();
721 maTrDeEdgeEntries.pop_front();
723 continue;
726 // check if the edges self-intersect. This can only happen when
727 // start and end point are different
728 bool bRangesSet(false);
730 if(!(bSameStartPoint || bSameEndPoint))
732 // get XRanges of edges
733 aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX());
734 aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX());
735 bRangesSet = true;
737 // use fast range test first
738 if(aLeftRange.overlaps(aRightRange))
740 // real cut test and correction. If correction was needed,
741 // start new run
742 if(testAndCorrectEdgeIntersection(aLeft, aRight, aCurrent))
744 continue;
749 // now we need to check if there are intersections with other edges
750 // or if other edges start inside the candidate trapezoid
751 if(aCurrent != maTrDeEdgeEntries.end()
752 && fTools::less(aCurrent->getStart().getY(), aLeftEnd.getY()))
754 // get XRanges of edges
755 if(!bRangesSet)
757 aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX());
758 aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX());
761 // build full XRange for fast check
762 B1DRange aAllRange(aLeftRange);
763 aAllRange.expand(aRightRange);
765 // prepare loop iterator; aCurrent needs to stay unchanged for
766 // eventual sorted insertions of new EdgeNodes. Also prepare stop flag
767 TrDeEdgeEntries::iterator aLoop(aCurrent);
768 bool bDone(false);
772 // get compare edge and its XRange
773 TrDeEdgeEntries::reference aCompare(*aLoop++);
775 // avoid edges using the same start point as one of
776 // the edges. These can neither have their start point
777 // in the thought trapezoid nor cut with one of the edges
778 if(aCompare.getStart().equal(aRight.getStart(), fTools::getSmallValue()))
780 continue;
783 // get compare XRange
784 const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX());
786 // use fast range test first
787 if(aAllRange.overlaps(aCompareRange))
789 // check for start point inside thought trapezoid
790 if(fTools::more(aCompare.getStart().getY(), aLeft.getStart().getY()))
792 // calculate the two possible split points at compare's Y
793 const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aCompare.getStart().getY()));
794 const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aCompare.getStart().getY()));
796 // check for start point of aCompare being inside thought
797 // trapezoid
798 if(aCompare.getStart().getX() >= aSplitLeft.getX() &&
799 aCompare.getStart().getX() <= aSplitRight.getX())
801 // is inside, correct and restart loop
802 B2DPoint* pNewLeft = maNewPoints.allocatePoint(aSplitLeft);
804 if(splitEdgeAtGivenPoint(aLeft, *pNewLeft, aCurrent))
806 bDone = true;
808 else
810 maNewPoints.freeIfLast(pNewLeft);
813 B2DPoint* pNewRight = maNewPoints.allocatePoint(aSplitRight);
815 if(splitEdgeAtGivenPoint(aRight, *pNewRight, aCurrent))
817 bDone = true;
819 else
821 maNewPoints.freeIfLast(pNewRight);
826 if(!bDone && aLeftRange.overlaps(aCompareRange))
828 // test for concrete cut of compare edge with left edge
829 bDone = testAndCorrectEdgeIntersection(aLeft, aCompare, aCurrent);
832 if(!bDone && aRightRange.overlaps(aCompareRange))
834 // test for concrete cut of compare edge with Right edge
835 bDone = testAndCorrectEdgeIntersection(aRight, aCompare, aCurrent);
839 while(!bDone
840 && aLoop != maTrDeEdgeEntries.end()
841 && fTools::less(aLoop->getStart().getY(), aLeftEnd.getY()));
843 if(bDone)
845 // something needed to be changed; start next loop
846 continue;
850 // when we get here, the intended trapezoid can be used. It needs to
851 // be corrected, eventually (if prepared); but this is no reason not to
852 // use it in the same loop iteration
853 if(!bEndOnSameLine)
855 if(bLeftIsLonger)
857 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aLeftEnd);
859 if(!splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent))
861 maNewPoints.freeIfLast(pNewPoint);
864 else
866 B2DPoint* pNewPoint = maNewPoints.allocatePoint(aRightEnd);
868 if(!splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent))
870 maNewPoints.freeIfLast(pNewPoint);
875 // the two edges start at the same Y, they use the same DeltaY, they
876 // do not cut themselves and not any other edge in range. Create a
877 // B2DTrapezoid and consume both edges
878 ro_Result.push_back(
879 B2DTrapezoid(
880 aLeft.getStart().getX(),
881 aRight.getStart().getX(),
882 aLeft.getStart().getY(),
883 aLeftEnd.getX(),
884 aRightEnd.getX(),
885 aLeftEnd.getY()));
887 maTrDeEdgeEntries.pop_front();
888 maTrDeEdgeEntries.pop_front();
892 } // end of anonymous namespace
893 } // end of namespace basegfx
895 namespace basegfx
897 B2DTrapezoid::B2DTrapezoid(
898 const double& rfTopXLeft,
899 const double& rfTopXRight,
900 const double& rfTopY,
901 const double& rfBottomXLeft,
902 const double& rfBottomXRight,
903 const double& rfBottomY)
904 : mfTopXLeft(rfTopXLeft),
905 mfTopXRight(rfTopXRight),
906 mfTopY(rfTopY),
907 mfBottomXLeft(rfBottomXLeft),
908 mfBottomXRight(rfBottomXRight),
909 mfBottomY(rfBottomY)
911 // guarantee mfTopXRight >= mfTopXLeft
912 if(mfTopXLeft > mfTopXRight)
914 std::swap(mfTopXLeft, mfTopXRight);
917 // guarantee mfBottomXRight >= mfBottomXLeft
918 if(mfBottomXLeft > mfBottomXRight)
920 std::swap(mfBottomXLeft, mfBottomXRight);
923 // guarantee mfBottomY >= mfTopY
924 if(mfTopY > mfBottomY)
926 std::swap(mfTopY, mfBottomY);
927 std::swap(mfTopXLeft, mfBottomXLeft);
928 std::swap(mfTopXRight, mfBottomXRight);
932 B2DPolygon B2DTrapezoid::getB2DPolygon() const
934 B2DPolygon aRetval;
936 aRetval.append(B2DPoint(getTopXLeft(), getTopY()));
937 aRetval.append(B2DPoint(getTopXRight(), getTopY()));
938 aRetval.append(B2DPoint(getBottomXRight(), getBottomY()));
939 aRetval.append(B2DPoint(getBottomXLeft(), getBottomY()));
940 aRetval.setClosed(true);
942 return aRetval;
944 } // end of namespace basegfx
946 namespace basegfx
948 namespace tools
950 // convert Source tools::PolyPolygon to trapezoids
951 void trapezoidSubdivide(B2DTrapezoidVector& ro_Result, const B2DPolyPolygon& rSourcePolyPolygon)
953 trapezoidhelper::TrapezoidSubdivider aTrapezoidSubdivider(rSourcePolyPolygon);
955 aTrapezoidSubdivider.Subdivide(ro_Result);
958 void createLineTrapezoidFromEdge(
959 B2DTrapezoidVector& ro_Result,
960 const B2DPoint& rPointA,
961 const B2DPoint& rPointB,
962 double fLineWidth)
964 if(fTools::lessOrEqual(fLineWidth, 0.0))
966 // no line witdh
967 return;
970 if(rPointA.equal(rPointB, fTools::getSmallValue()))
972 // points are equal, no edge
973 return;
976 const double fHalfLineWidth(0.5 * fLineWidth);
978 if(fTools::equal(rPointA.getX(), rPointB.getX(), fTools::getSmallValue()))
980 // vertical line
981 const double fLeftX(rPointA.getX() - fHalfLineWidth);
982 const double fRightX(rPointA.getX() + fHalfLineWidth);
984 ro_Result.push_back(
985 B2DTrapezoid(
986 fLeftX,
987 fRightX,
988 std::min(rPointA.getY(), rPointB.getY()),
989 fLeftX,
990 fRightX,
991 std::max(rPointA.getY(), rPointB.getY())));
993 else if(fTools::equal(rPointA.getY(), rPointB.getY(), fTools::getSmallValue()))
995 // horizontal line
996 const double fLeftX(std::min(rPointA.getX(), rPointB.getX()));
997 const double fRightX(std::max(rPointA.getX(), rPointB.getX()));
999 ro_Result.push_back(
1000 B2DTrapezoid(
1001 fLeftX,
1002 fRightX,
1003 rPointA.getY() - fHalfLineWidth,
1004 fLeftX,
1005 fRightX,
1006 rPointA.getY() + fHalfLineWidth));
1008 else
1010 // diagonal line
1011 // create perpendicular vector
1012 const B2DVector aDelta(rPointB - rPointA);
1013 B2DVector aPerpendicular(-aDelta.getY(), aDelta.getX());
1014 aPerpendicular.setLength(fHalfLineWidth);
1016 // create StartLow, StartHigh, EndLow and EndHigh
1017 const B2DPoint aStartLow(rPointA + aPerpendicular);
1018 const B2DPoint aStartHigh(rPointA - aPerpendicular);
1019 const B2DPoint aEndHigh(rPointB - aPerpendicular);
1020 const B2DPoint aEndLow(rPointB + aPerpendicular);
1022 // create EdgeEntries
1023 basegfx::trapezoidhelper::TrDeEdgeEntries aTrDeEdgeEntries;
1025 aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aStartLow, &aStartHigh, 0));
1026 aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aStartHigh, &aEndHigh, 0));
1027 aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aEndHigh, &aEndLow, 0));
1028 aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aEndLow, &aStartLow, 0));
1029 aTrDeEdgeEntries.sort();
1031 // here we know we have exactly four edges, and they do not cut, touch or
1032 // intersect. This makes processing much easier. Get the first two as start
1033 // edges for the thought trapezoid
1034 basegfx::trapezoidhelper::TrDeEdgeEntries::iterator aCurrent(aTrDeEdgeEntries.begin());
1035 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft(*aCurrent++);
1036 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight(*aCurrent++);
1037 const bool bEndOnSameLine(fTools::equal(aLeft.getEnd().getY(), aRight.getEnd().getY(), fTools::getSmallValue()));
1039 if(bEndOnSameLine)
1041 // create two triangle trapezoids
1042 ro_Result.push_back(
1043 B2DTrapezoid(
1044 aLeft.getStart().getX(),
1045 aRight.getStart().getX(),
1046 aLeft.getStart().getY(),
1047 aLeft.getEnd().getX(),
1048 aRight.getEnd().getX(),
1049 aLeft.getEnd().getY()));
1051 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
1052 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
1054 ro_Result.push_back(
1055 B2DTrapezoid(
1056 aLeft2.getStart().getX(),
1057 aRight2.getStart().getX(),
1058 aLeft2.getStart().getY(),
1059 aLeft2.getEnd().getX(),
1060 aRight2.getEnd().getX(),
1061 aLeft2.getEnd().getY()));
1063 else
1065 // create three trapezoids. Check which edge is longer and
1066 // correct accordingly
1067 const bool bLeftIsLonger(fTools::more(aLeft.getEnd().getY(), aRight.getEnd().getY()));
1069 if(bLeftIsLonger)
1071 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
1072 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
1073 const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aRight.getEnd().getY()));
1074 const B2DPoint aSplitRight(aRight2.getCutPointForGivenY(aLeft.getEnd().getY()));
1076 ro_Result.push_back(
1077 B2DTrapezoid(
1078 aLeft.getStart().getX(),
1079 aRight.getStart().getX(),
1080 aLeft.getStart().getY(),
1081 aSplitLeft.getX(),
1082 aRight.getEnd().getX(),
1083 aRight.getEnd().getY()));
1085 ro_Result.push_back(
1086 B2DTrapezoid(
1087 aSplitLeft.getX(),
1088 aRight.getEnd().getX(),
1089 aRight.getEnd().getY(),
1090 aLeft2.getStart().getX(),
1091 aSplitRight.getX(),
1092 aLeft2.getStart().getY()));
1094 ro_Result.push_back(
1095 B2DTrapezoid(
1096 aLeft2.getStart().getX(),
1097 aSplitRight.getX(),
1098 aLeft2.getStart().getY(),
1099 aLeft2.getEnd().getX(),
1100 aRight2.getEnd().getX(),
1101 aLeft2.getEnd().getY()));
1103 else
1105 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
1106 basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
1107 const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aLeft.getEnd().getY()));
1108 const B2DPoint aSplitLeft(aLeft2.getCutPointForGivenY(aRight.getEnd().getY()));
1110 ro_Result.push_back(
1111 B2DTrapezoid(
1112 aLeft.getStart().getX(),
1113 aRight.getStart().getX(),
1114 aLeft.getStart().getY(),
1115 aLeft.getEnd().getX(),
1116 aSplitRight.getX(),
1117 aLeft.getEnd().getY()));
1119 ro_Result.push_back(
1120 B2DTrapezoid(
1121 aLeft.getEnd().getX(),
1122 aSplitRight.getX(),
1123 aLeft.getEnd().getY(),
1124 aSplitLeft.getX(),
1125 aRight.getEnd().getX(),
1126 aRight2.getStart().getY()));
1128 ro_Result.push_back(
1129 B2DTrapezoid(
1130 aSplitLeft.getX(),
1131 aRight.getEnd().getX(),
1132 aRight2.getStart().getY(),
1133 aLeft2.getEnd().getX(),
1134 aRight2.getEnd().getX(),
1135 aLeft2.getEnd().getY()));
1141 void createLineTrapezoidFromB2DPolygon(
1142 B2DTrapezoidVector& ro_Result,
1143 const B2DPolygon& rPolygon,
1144 double fLineWidth)
1146 if(fTools::lessOrEqual(fLineWidth, 0.0))
1148 return;
1151 // ensure there are no curves used
1152 B2DPolygon aSource(rPolygon);
1154 if(aSource.areControlPointsUsed())
1156 const double fPrecisionFactor = 0.25;
1157 aSource = adaptiveSubdivideByDistance( aSource, fLineWidth * fPrecisionFactor );
1160 const sal_uInt32 nPointCount(aSource.count());
1162 if(!nPointCount)
1164 return;
1167 const sal_uInt32 nEdgeCount(aSource.isClosed() ? nPointCount : nPointCount - 1);
1168 B2DPoint aCurrent(aSource.getB2DPoint(0));
1170 ro_Result.reserve(ro_Result.size() + (3 * nEdgeCount));
1172 for(sal_uInt32 a(0); a < nEdgeCount; a++)
1174 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
1175 const B2DPoint aNext(aSource.getB2DPoint(nNextIndex));
1177 createLineTrapezoidFromEdge(ro_Result, aCurrent, aNext, fLineWidth);
1178 aCurrent = aNext;
1182 void createLineTrapezoidFromB2DPolyPolygon(
1183 B2DTrapezoidVector& ro_Result,
1184 const B2DPolyPolygon& rPolyPolygon,
1185 double fLineWidth)
1187 if(fTools::lessOrEqual(fLineWidth, 0.0))
1189 return;
1192 // ensure there are no curves used
1193 B2DPolyPolygon aSource(rPolyPolygon);
1195 if(aSource.areControlPointsUsed())
1197 aSource = aSource.getDefaultAdaptiveSubdivision();
1200 const sal_uInt32 nCount(aSource.count());
1202 if(!nCount)
1204 return;
1207 for(sal_uInt32 a(0); a < nCount; a++)
1209 createLineTrapezoidFromB2DPolygon(
1210 ro_Result,
1211 aSource.getB2DPolygon(a),
1212 fLineWidth);
1216 } // end of namespace tools
1217 } // end of namespace basegfx
1219 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */