| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_basegfx.hxx" |
| #include <basegfx/polygon/b2dtrapezoid.hxx> |
| #include <basegfx/range/b1drange.hxx> |
| #include <basegfx/polygon/b2dpolygontools.hxx> |
| #include <list> |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| namespace basegfx |
| { |
| namespace trapezoidhelper |
| { |
| ////////////////////////////////////////////////////////////////////////////// |
| // helper class to hold a simple ege. This is only used for horizontal edges |
| // currently, thus the YPositions will be equal. I did not create a special |
| // class for this since holdingthe pointers is more effective and also can be |
| // used as baseclass for the traversing edges |
| |
| class TrDeSimpleEdge |
| { |
| protected: |
| // pointers to start and end point |
| const B2DPoint* mpStart; |
| const B2DPoint* mpEnd; |
| |
| public: |
| // constructor |
| TrDeSimpleEdge( |
| const B2DPoint* pStart, |
| const B2DPoint* pEnd) |
| : mpStart(pStart), |
| mpEnd(pEnd) |
| { |
| } |
| |
| // data read access |
| const B2DPoint& getStart() const { return *mpStart; } |
| const B2DPoint& getEnd() const { return *mpEnd; } |
| }; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // define vector of simple edges |
| |
| typedef ::std::vector< TrDeSimpleEdge > TrDeSimpleEdges; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // helper class for holding a traversing edge. It will always have some |
| // distance in YPos. The slope (in a numerically useful form, see comments) is |
| // hold and used in SortValue to allow sorting traversing edges by Y, X and slope |
| // (in that order) |
| |
| class TrDeEdgeEntry : public TrDeSimpleEdge |
| { |
| private: |
| // the slope in a numerical useful form for sorting |
| sal_uInt32 mnSortValue; |
| |
| public: |
| // convenience data read access |
| double getDeltaX() const { return mpEnd->getX() - mpStart->getX(); } |
| double getDeltaY() const { return mpEnd->getY() - mpStart->getY(); } |
| |
| // convenience data read access. SortValue is created on demand since |
| // it is not always used |
| sal_uInt32 getSortValue() const |
| { |
| if(0 != mnSortValue) |
| return mnSortValue; |
| |
| // get radiant; has to be in the range ]0.0 .. pi[, thus scale to full |
| // sal_uInt32 range for maximum precision |
| const double fRadiant(atan2(getDeltaY(), getDeltaX()) * (SAL_MAX_UINT32 / F_PI)); |
| |
| // convert to sal_uInt32 value |
| const_cast< TrDeEdgeEntry* >(this)->mnSortValue = sal_uInt32(fRadiant); |
| |
| return mnSortValue; |
| } |
| |
| // constructor. SortValue can be given when known, use zero otherwise |
| TrDeEdgeEntry( |
| const B2DPoint* pStart, |
| const B2DPoint* pEnd, |
| sal_uInt32 nSortValue = 0) |
| : TrDeSimpleEdge(pStart, pEnd), |
| mnSortValue(nSortValue) |
| { |
| // force traversal of deltaY downward |
| if(mpEnd->getY() < mpStart->getY()) |
| { |
| std::swap(mpStart, mpEnd); |
| } |
| |
| // no horizontal edges allowed, all neeed to traverse vertically |
| OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)"); |
| } |
| |
| // data write access to StartPoint |
| void setStart( const B2DPoint* pNewStart) |
| { |
| OSL_ENSURE(0 != pNewStart, "No null pointer allowed here (!)"); |
| |
| if(mpStart != pNewStart) |
| { |
| mpStart = pNewStart; |
| |
| // no horizontal edges allowed, all neeed to traverse vertivally |
| OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)"); |
| } |
| } |
| |
| // data write access to EndPoint |
| void setEnd( const B2DPoint* pNewEnd) |
| { |
| OSL_ENSURE(0 != pNewEnd, "No null pointer allowed here (!)"); |
| |
| if(mpEnd != pNewEnd) |
| { |
| mpEnd = pNewEnd; |
| |
| // no horizontal edges allowed, all neeed to traverse vertivally |
| OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)"); |
| } |
| } |
| |
| // operator for sort support. Sort by Y, X and slope (in that order) |
| bool operator<(const TrDeEdgeEntry& rComp) const |
| { |
| if(fTools::equal(getStart().getY(), rComp.getStart().getY(), fTools::getSmallValue())) |
| { |
| if(fTools::equal(getStart().getX(), rComp.getStart().getX(), fTools::getSmallValue())) |
| { |
| // when start points are equal, use the direction the edge is pointing |
| // to. That value is created on demand and derived from atan2 in the |
| // range ]0.0 .. pi[ (without extremas, we always have a deltaY in this |
| // class) and scaled to sal_uInt32 range for best precision. 0 means no angle, |
| // while SAL_MAX_UINT32 means pi. Thus, the higher the value, the more left |
| // the edge traverses. |
| return (getSortValue() > rComp.getSortValue()); |
| } |
| else |
| { |
| return fTools::less(getStart().getX(), rComp.getStart().getX()); |
| } |
| } |
| else |
| { |
| return fTools::less(getStart().getY(), rComp.getStart().getY()); |
| } |
| } |
| |
| // method for cut support |
| B2DPoint getCutPointForGivenY(double fGivenY) |
| { |
| // Calculate cut point locally (do not use interpolate) since it is numerically |
| // necessary to guarantee the new, equal Y-coordinate |
| const double fFactor((fGivenY - getStart().getY()) / getDeltaY()); |
| const double fDeltaXNew(fFactor * getDeltaX()); |
| |
| return B2DPoint(getStart().getX() + fDeltaXNew, fGivenY); |
| } |
| }; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // define double linked list of edges (for fast random insert) |
| |
| typedef ::std::list< TrDeEdgeEntry > TrDeEdgeEntries; |
| |
| } // end of anonymous namespace |
| } // end of namespace basegfx |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| namespace basegfx |
| { |
| namespace trapezoidhelper |
| { |
| // helper class to handle the complete trapezoid subdivision of a PolyPolygon |
| class TrapezoidSubdivider |
| { |
| private: |
| // local data |
| sal_uInt32 mnInitialEdgeEntryCount; |
| TrDeEdgeEntries maTrDeEdgeEntries; |
| ::std::vector< B2DPoint > maPoints; |
| ::std::vector< B2DPoint* > maNewPoints; |
| |
| void addEdgeSorted( |
| TrDeEdgeEntries::iterator aCurrent, |
| const TrDeEdgeEntry& rNewEdge) |
| { |
| // Loop while new entry is bigger, use operator< |
| while(aCurrent != maTrDeEdgeEntries.end() && (*aCurrent) < rNewEdge) |
| { |
| aCurrent++; |
| } |
| |
| // Insert before first which is smaller or equal or at end |
| maTrDeEdgeEntries.insert(aCurrent, rNewEdge); |
| } |
| |
| bool splitEdgeAtGivenPoint( |
| TrDeEdgeEntries::reference aEdge, |
| const B2DPoint& rCutPoint, |
| TrDeEdgeEntries::iterator aCurrent) |
| { |
| // do not create edges without deltaY: do not split when start is identical |
| if(aEdge.getStart().equal(rCutPoint, fTools::getSmallValue())) |
| { |
| return false; |
| } |
| |
| // do not create edges without deltaY: do not split when end is identical |
| if(aEdge.getEnd().equal(rCutPoint, fTools::getSmallValue())) |
| { |
| return false; |
| } |
| |
| const double fOldDeltaYStart(rCutPoint.getY() - aEdge.getStart().getY()); |
| |
| if(fTools::lessOrEqual(fOldDeltaYStart, 0.0)) |
| { |
| // do not split: the resulting edge would be horizontal |
| // correct it to new start point |
| aEdge.setStart(&rCutPoint); |
| return false; |
| } |
| |
| const double fNewDeltaYStart(aEdge.getEnd().getY() - rCutPoint.getY()); |
| |
| if(fTools::lessOrEqual(fNewDeltaYStart, 0.0)) |
| { |
| // do not split: the resulting edge would be horizontal |
| // correct it to new end point |
| aEdge.setEnd(&rCutPoint); |
| return false; |
| } |
| |
| // Create new entry |
| const TrDeEdgeEntry aNewEdge( |
| &rCutPoint, |
| &aEdge.getEnd(), |
| aEdge.getSortValue()); |
| |
| // Correct old entry |
| aEdge.setEnd(&rCutPoint); |
| |
| // Insert sorted (to avoid new sort) |
| addEdgeSorted(aCurrent, aNewEdge); |
| |
| return true; |
| } |
| |
| bool testAndCorrectEdgeIntersection( |
| TrDeEdgeEntries::reference aEdgeA, |
| TrDeEdgeEntries::reference aEdgeB, |
| TrDeEdgeEntries::iterator aCurrent) |
| { |
| // Exclude simple cases: same start or end point |
| if(aEdgeA.getStart().equal(aEdgeB.getStart(), fTools::getSmallValue())) |
| { |
| return false; |
| } |
| |
| if(aEdgeA.getStart().equal(aEdgeB.getEnd(), fTools::getSmallValue())) |
| { |
| return false; |
| } |
| |
| if(aEdgeA.getEnd().equal(aEdgeB.getStart(), fTools::getSmallValue())) |
| { |
| return false; |
| } |
| |
| if(aEdgeA.getEnd().equal(aEdgeB.getEnd(), fTools::getSmallValue())) |
| { |
| return false; |
| } |
| |
| // Exclude simple cases: one of the edges has no length anymore |
| if(aEdgeA.getStart().equal(aEdgeA.getEnd(), fTools::getSmallValue())) |
| { |
| return false; |
| } |
| |
| if(aEdgeB.getStart().equal(aEdgeB.getEnd(), fTools::getSmallValue())) |
| { |
| return false; |
| } |
| |
| // check if one point is on the other edge (a touch, not a cut) |
| const B2DVector aDeltaB(aEdgeB.getDeltaX(), aEdgeB.getDeltaY()); |
| |
| if(tools::isPointOnEdge(aEdgeA.getStart(), aEdgeB.getStart(), aDeltaB)) |
| { |
| return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getStart(), aCurrent); |
| } |
| |
| if(tools::isPointOnEdge(aEdgeA.getEnd(), aEdgeB.getStart(), aDeltaB)) |
| { |
| return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getEnd(), aCurrent); |
| } |
| |
| const B2DVector aDeltaA(aEdgeA.getDeltaX(), aEdgeA.getDeltaY()); |
| |
| if(tools::isPointOnEdge(aEdgeB.getStart(), aEdgeA.getStart(), aDeltaA)) |
| { |
| return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getStart(), aCurrent); |
| } |
| |
| if(tools::isPointOnEdge(aEdgeB.getEnd(), aEdgeA.getStart(), aDeltaA)) |
| { |
| return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getEnd(), aCurrent); |
| } |
| |
| // check for cut inside edges. Use both t-values to choose the more precise |
| // one later |
| double fCutA(0.0); |
| double fCutB(0.0); |
| |
| if(tools::findCut( |
| aEdgeA.getStart(), aDeltaA, |
| aEdgeB.getStart(), aDeltaB, |
| CUTFLAG_LINE, |
| &fCutA, |
| &fCutB)) |
| { |
| // use a simple metric (length criteria) for choosing the numerically |
| // better cut |
| const double fSimpleLengthA(aDeltaA.getX() + aDeltaA.getY()); |
| const double fSimpleLengthB(aDeltaB.getX() + aDeltaB.getY()); |
| const bool bAIsLonger(fSimpleLengthA > fSimpleLengthB); |
| B2DPoint* pNewPoint = bAIsLonger |
| ? new B2DPoint(aEdgeA.getStart() + (fCutA * aDeltaA)) |
| : new B2DPoint(aEdgeB.getStart() + (fCutB * aDeltaB)); |
| bool bRetval(false); |
| |
| // try to split both edges |
| bRetval = splitEdgeAtGivenPoint(aEdgeA, *pNewPoint, aCurrent); |
| bRetval |= splitEdgeAtGivenPoint(aEdgeB, *pNewPoint, aCurrent); |
| |
| if(bRetval) |
| { |
| maNewPoints.push_back(pNewPoint); |
| } |
| else |
| { |
| delete pNewPoint; |
| } |
| |
| return bRetval; |
| } |
| |
| return false; |
| } |
| |
| void solveHorizontalEdges(TrDeSimpleEdges& rTrDeSimpleEdges) |
| { |
| if(rTrDeSimpleEdges.size() && maTrDeEdgeEntries.size()) |
| { |
| // there were horizontal edges. These can be excluded, but |
| // cuts with other edges need to be solved and added before |
| // ignoring them |
| sal_uInt32 a(0); |
| |
| for(a = 0; a < rTrDeSimpleEdges.size(); a++) |
| { |
| // get horizontal edge as candidate; prepare it's range and fixed Y |
| const TrDeSimpleEdge& rHorEdge = rTrDeSimpleEdges[a]; |
| const B1DRange aRange(rHorEdge.getStart().getX(), rHorEdge.getEnd().getX()); |
| const double fFixedY(rHorEdge.getStart().getY()); |
| |
| // loop over traversing edges |
| TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin()); |
| |
| do |
| { |
| // get compare edge |
| TrDeEdgeEntries::reference aCompare(*aCurrent++); |
| |
| if(fTools::lessOrEqual(aCompare.getEnd().getY(), fFixedY)) |
| { |
| // edge ends above horizontal edge, continue |
| continue; |
| } |
| |
| if(fTools::moreOrEqual(aCompare.getStart().getY(), fFixedY)) |
| { |
| // edge starts below horizontal edge, continue |
| continue; |
| } |
| |
| // vertical overlap, get horizontal range |
| const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX()); |
| |
| if(aRange.overlaps(aCompareRange)) |
| { |
| // possible cut, get cut point |
| const B2DPoint aSplit(aCompare.getCutPointForGivenY(fFixedY)); |
| |
| if(fTools::more(aSplit.getX(), aRange.getMinimum()) |
| && fTools::less(aSplit.getX(), aRange.getMaximum())) |
| { |
| // cut is in XRange of horizontal edge, potenitally needed cut |
| B2DPoint* pNewPoint = new B2DPoint(aSplit); |
| |
| if(splitEdgeAtGivenPoint(aCompare, *pNewPoint, aCurrent)) |
| { |
| maNewPoints.push_back(pNewPoint); |
| } |
| else |
| { |
| delete pNewPoint; |
| } |
| } |
| } |
| } |
| while(aCurrent != maTrDeEdgeEntries.end() |
| && fTools::less(aCurrent->getStart().getY(), fFixedY)); |
| } |
| } |
| } |
| |
| public: |
| TrapezoidSubdivider( |
| const B2DPolyPolygon& rSourcePolyPolygon) |
| : mnInitialEdgeEntryCount(0), |
| maTrDeEdgeEntries(), |
| maPoints(), |
| maNewPoints() |
| { |
| B2DPolyPolygon aSource(rSourcePolyPolygon); |
| const sal_uInt32 nPolygonCount(rSourcePolyPolygon.count()); |
| TrDeSimpleEdges aTrDeSimpleEdges; |
| sal_uInt32 a(0), b(0); |
| sal_uInt32 nAllPointCount(0); |
| |
| // ensure there are no curves used |
| if(aSource.areControlPointsUsed()) |
| { |
| aSource = aSource.getDefaultAdaptiveSubdivision(); |
| } |
| |
| for(a = 0; a < nPolygonCount; a++) |
| { |
| // 1st run: count points |
| const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a)); |
| const sal_uInt32 nCount(aPolygonCandidate.count()); |
| |
| if(nCount > 2) |
| { |
| nAllPointCount += nCount; |
| } |
| } |
| |
| if(nAllPointCount) |
| { |
| // reserve needed points. CAUTION: maPoints size is NOT to be changed anymore |
| // after 2nd loop since pointers to it are used in the edges |
| maPoints.reserve(nAllPointCount); |
| |
| for(a = 0; a < nPolygonCount; a++) |
| { |
| // 2nd run: add points |
| const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a)); |
| const sal_uInt32 nCount(aPolygonCandidate.count()); |
| |
| if(nCount > 2) |
| { |
| for(b = 0; b < nCount; b++) |
| { |
| maPoints.push_back(aPolygonCandidate.getB2DPoint(b)); |
| } |
| } |
| } |
| |
| // Moved the edge construction to a 3rd run: doing it in the 2nd run is |
| // possible(and i used it), but requires a working vector::reserve() |
| // implementation, else the vector will be reallocated and the pointers |
| // in the edges may be wrong. Security first here. |
| sal_uInt32 nStartIndex(0); |
| |
| for(a = 0; a < nPolygonCount; a++) |
| { |
| const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a)); |
| const sal_uInt32 nCount(aPolygonCandidate.count()); |
| |
| if(nCount > 2) |
| { |
| // get the last point of the current polygon |
| B2DPoint* pPrev(&maPoints[nCount + nStartIndex - 1]); |
| |
| for(b = 0; b < nCount; b++) |
| { |
| // get next point |
| B2DPoint* pCurr(&maPoints[nStartIndex++]); |
| |
| if(fTools::equal(pPrev->getY(), pCurr->getY(), fTools::getSmallValue())) |
| { |
| // horizontal edge, check for single point |
| if(!fTools::equal(pPrev->getX(), pCurr->getX(), fTools::getSmallValue())) |
| { |
| // X-order not needed, just add |
| aTrDeSimpleEdges.push_back(TrDeSimpleEdge(pPrev, pCurr)); |
| |
| const double fMiddle((pPrev->getY() + pCurr->getY()) * 0.5); |
| pPrev->setY(fMiddle); |
| pCurr->setY(fMiddle); |
| } |
| } |
| else |
| { |
| // vertical edge. Positive Y-direction is guaranteed by the |
| // TrDeEdgeEntry constructor |
| maTrDeEdgeEntries.push_back(TrDeEdgeEntry(pPrev, pCurr, 0)); |
| mnInitialEdgeEntryCount++; |
| } |
| |
| // prepare next step |
| pPrev = pCurr; |
| } |
| } |
| } |
| } |
| |
| if(maTrDeEdgeEntries.size()) |
| { |
| // single and initial sort of traversing edges |
| maTrDeEdgeEntries.sort(); |
| |
| // solve horizontal edges if there are any detected |
| solveHorizontalEdges(aTrDeSimpleEdges); |
| } |
| } |
| |
| ~TrapezoidSubdivider() |
| { |
| // delete the extra points created for cuts |
| const sal_uInt32 nCount(maNewPoints.size()); |
| |
| for(sal_uInt32 a(0); a < nCount; a++) |
| { |
| delete maNewPoints[a]; |
| } |
| } |
| |
| void Subdivide(B2DTrapezoidVector& ro_Result) |
| { |
| // This is the central subdivider. The strategy is to use the first two entries |
| // from the traversing edges as a potential trapezoid and do the needed corrections |
| // and adaptions on the way. |
| // |
| // There always must be two edges with the same YStart value: When adding the polygons |
| // in the constructor, there is always a topmost point from which two edges start; when |
| // the topmost is an edge, there is a start and end of this edge from which two edges |
| // start. All cases have two edges with same StartY (QED). |
| // |
| // Based on this these edges get corrected when: |
| // - one is longer than the other |
| // - they intersect |
| // - they intersect with other edges |
| // - another edge starts inside the thought trapezoid |
| // |
| // All this cases again produce a valid state so that the first two edges have a common |
| // Ystart again. Some cases lead to a restart of the process, some allow consuming the |
| // edges and create the intended trapezoid. |
| // |
| // Be careful when doing chages here: It is essential to keep all possible paths |
| // in valid states and to be numerically correct. This is especially needed e.g. |
| // by using fTools::equal(..) in the more robust small-value incarnation. |
| B1DRange aLeftRange; |
| B1DRange aRightRange; |
| |
| if(!maTrDeEdgeEntries.empty()) |
| { |
| // measuring shows that the relation between edges and created trapezoids is |
| // mostly in the 1:1 range, thus reserve as much trapezoids as edges exist. Do |
| // not use maTrDeEdgeEntries.size() since that may be a non-constant time |
| // operation for Lists. Instead, use mnInitialEdgeEntryCount which will contain |
| // the roughly counted adds to the List |
| ro_Result.reserve(ro_Result.size() + mnInitialEdgeEntryCount); |
| } |
| |
| while(!maTrDeEdgeEntries.empty()) |
| { |
| // Prepare current operator and get first edge |
| TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin()); |
| TrDeEdgeEntries::reference aLeft(*aCurrent++); |
| |
| if(aCurrent == maTrDeEdgeEntries.end()) |
| { |
| // Should not happen: No 2nd edge; consume the single edge |
| // to not have an endless loop and start next. During development |
| // i constantly had breakpoints here, so i am sure enough to add an |
| // assertion here |
| OSL_ENSURE(false, "Trapeziod decomposer in illegal state (!)"); |
| maTrDeEdgeEntries.pop_front(); |
| continue; |
| } |
| |
| // get second edge |
| TrDeEdgeEntries::reference aRight(*aCurrent++); |
| |
| if(!fTools::equal(aLeft.getStart().getY(), aRight.getStart().getY(), fTools::getSmallValue())) |
| { |
| // Should not happen: We have a 2nd edge, but YStart is on another |
| // line; consume the single edge to not have an endless loop and start |
| // next. During development i constantly had breakpoints here, so i am |
| // sure enough to add an assertion here |
| OSL_ENSURE(false, "Trapeziod decomposer in illegal state (!)"); |
| maTrDeEdgeEntries.pop_front(); |
| continue; |
| } |
| |
| // aLeft and aRight build a thought trapezoid now. They have a common |
| // start line (same Y for start points). Potentially, one of the edges |
| // is longer than the other. It is only needed to look at the shorter |
| // length which build the potential trapezoid. To do so, get the end points |
| // locally and adapt the evtl. longer one. Use only aLeftEnd and aRightEnd |
| // from here on, not the aLeft.getEnd() or aRight.getEnd() accesses. |
| B2DPoint aLeftEnd(aLeft.getEnd()); |
| B2DPoint aRightEnd(aRight.getEnd()); |
| |
| // check if end points are on the same line. If yes, no adaption |
| // needs to be prepared. Also remember which one actually is longer. |
| const bool bEndOnSameLine(fTools::equal(aLeftEnd.getY(), aRightEnd.getY(), fTools::getSmallValue())); |
| bool bLeftIsLonger(false); |
| |
| if(!bEndOnSameLine) |
| { |
| // check which edge is longer and correct accordingly |
| bLeftIsLonger = fTools::more(aLeftEnd.getY(), aRightEnd.getY()); |
| |
| if(bLeftIsLonger) |
| { |
| aLeftEnd = aLeft.getCutPointForGivenY(aRightEnd.getY()); |
| } |
| else |
| { |
| aRightEnd = aRight.getCutPointForGivenY(aLeftEnd.getY()); |
| } |
| } |
| |
| // check for same start and end points |
| const bool bSameStartPoint(aLeft.getStart().equal(aRight.getStart(), fTools::getSmallValue())); |
| const bool bSameEndPoint(aLeftEnd.equal(aRightEnd, fTools::getSmallValue())); |
| |
| // check the simple case that the edges form a 'blind' edge (deadend) |
| if(bSameStartPoint && bSameEndPoint) |
| { |
| // correct the longer edge if prepared |
| if(!bEndOnSameLine) |
| { |
| if(bLeftIsLonger) |
| { |
| B2DPoint* pNewPoint = new B2DPoint(aLeftEnd); |
| |
| if(splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent)) |
| { |
| maNewPoints.push_back(pNewPoint); |
| } |
| else |
| { |
| delete pNewPoint; |
| } |
| } |
| else |
| { |
| B2DPoint* pNewPoint = new B2DPoint(aRightEnd); |
| |
| if(splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent)) |
| { |
| maNewPoints.push_back(pNewPoint); |
| } |
| else |
| { |
| delete pNewPoint; |
| } |
| } |
| } |
| |
| // consume both edges and start next run |
| maTrDeEdgeEntries.pop_front(); |
| maTrDeEdgeEntries.pop_front(); |
| |
| continue; |
| } |
| |
| // check if the edges self-intersect. This can only happen when |
| // start and end point are different |
| bool bRangesSet(false); |
| |
| if(!(bSameStartPoint || bSameEndPoint)) |
| { |
| // get XRanges of edges |
| aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX()); |
| aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX()); |
| bRangesSet = true; |
| |
| // use fast range test first |
| if(aLeftRange.overlaps(aRightRange)) |
| { |
| // real cut test and correction. If correction was needed, |
| // start new run |
| if(testAndCorrectEdgeIntersection(aLeft, aRight, aCurrent)) |
| { |
| continue; |
| } |
| } |
| } |
| |
| // now we need to check if there are intersections with other edges |
| // or if other edges start inside the candidate trapezoid |
| if(aCurrent != maTrDeEdgeEntries.end() |
| && fTools::less(aCurrent->getStart().getY(), aLeftEnd.getY())) |
| { |
| // get XRanges of edges |
| if(!bRangesSet) |
| { |
| aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX()); |
| aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX()); |
| } |
| |
| // build full XRange for fast check |
| B1DRange aAllRange(aLeftRange); |
| aAllRange.expand(aRightRange); |
| |
| // prepare loop iterator; aCurrent needs to stay unchanged for |
| // eventual sorted insertions of new EdgeNodes. Also prepare stop flag |
| TrDeEdgeEntries::iterator aLoop(aCurrent); |
| bool bDone(false); |
| |
| do |
| { |
| // get compare edge and it's XRange |
| TrDeEdgeEntries::reference aCompare(*aLoop++); |
| |
| // avoid edges using the same start point as one of |
| // the edges. These can neither have their start point |
| // in the thought trapezoid nor cut with one of the edges |
| if(aCompare.getStart().equal(aRight.getStart(), fTools::getSmallValue())) |
| { |
| continue; |
| } |
| |
| // get compare XRange |
| const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX()); |
| |
| // use fast range test first |
| if(aAllRange.overlaps(aCompareRange)) |
| { |
| // check for start point inside thought trapezoid |
| if(fTools::more(aCompare.getStart().getY(), aLeft.getStart().getY())) |
| { |
| // calculate the two possible split points at compare's Y |
| const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aCompare.getStart().getY())); |
| const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aCompare.getStart().getY())); |
| |
| // check for start point of aCompare being inside thought |
| // trapezoid |
| if(aCompare.getStart().getX() >= aSplitLeft.getX() && |
| aCompare.getStart().getX() <= aSplitRight.getX()) |
| { |
| // is inside, correct and restart loop |
| B2DPoint* pNewLeft = new B2DPoint(aSplitLeft); |
| |
| if(splitEdgeAtGivenPoint(aLeft, *pNewLeft, aCurrent)) |
| { |
| maNewPoints.push_back(pNewLeft); |
| bDone = true; |
| } |
| else |
| { |
| delete pNewLeft; |
| } |
| |
| B2DPoint* pNewRight = new B2DPoint(aSplitRight); |
| |
| if(splitEdgeAtGivenPoint(aRight, *pNewRight, aCurrent)) |
| { |
| maNewPoints.push_back(pNewRight); |
| bDone = true; |
| } |
| else |
| { |
| delete pNewRight; |
| } |
| } |
| } |
| |
| if(!bDone && aLeftRange.overlaps(aCompareRange)) |
| { |
| // test for concrete cut of compare edge with left edge |
| bDone = testAndCorrectEdgeIntersection(aLeft, aCompare, aCurrent); |
| } |
| |
| if(!bDone && aRightRange.overlaps(aCompareRange)) |
| { |
| // test for concrete cut of compare edge with Right edge |
| bDone = testAndCorrectEdgeIntersection(aRight, aCompare, aCurrent); |
| } |
| } |
| } |
| while(!bDone |
| && aLoop != maTrDeEdgeEntries.end() |
| && fTools::less(aLoop->getStart().getY(), aLeftEnd.getY())); |
| |
| if(bDone) |
| { |
| // something needed to be changed; start next loop |
| continue; |
| } |
| } |
| |
| // when we get here, the intended trapezoid can be used. It needs to |
| // be corrected, eventually (if prepared); but this is no reason not to |
| // use it in the same loop iteration |
| if(!bEndOnSameLine) |
| { |
| if(bLeftIsLonger) |
| { |
| B2DPoint* pNewPoint = new B2DPoint(aLeftEnd); |
| |
| if(splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent)) |
| { |
| maNewPoints.push_back(pNewPoint); |
| } |
| else |
| { |
| delete pNewPoint; |
| } |
| } |
| else |
| { |
| B2DPoint* pNewPoint = new B2DPoint(aRightEnd); |
| |
| if(splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent)) |
| { |
| maNewPoints.push_back(pNewPoint); |
| } |
| else |
| { |
| delete pNewPoint; |
| } |
| } |
| } |
| |
| // the two edges start at the same Y, they use the same DeltaY, they |
| // do not cut themselves and not any other edge in range. Create a |
| // B2DTrapezoid and consume both edges |
| ro_Result.push_back( |
| B2DTrapezoid( |
| aLeft.getStart().getX(), |
| aRight.getStart().getX(), |
| aLeft.getStart().getY(), |
| aLeftEnd.getX(), |
| aRightEnd.getX(), |
| aLeftEnd.getY())); |
| |
| maTrDeEdgeEntries.pop_front(); |
| maTrDeEdgeEntries.pop_front(); |
| } |
| } |
| }; |
| } // end of anonymous namespace |
| } // end of namespace basegfx |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| namespace basegfx |
| { |
| B2DTrapezoid::B2DTrapezoid( |
| const double& rfTopXLeft, |
| const double& rfTopXRight, |
| const double& rfTopY, |
| const double& rfBottomXLeft, |
| const double& rfBottomXRight, |
| const double& rfBottomY) |
| : mfTopXLeft(rfTopXLeft), |
| mfTopXRight(rfTopXRight), |
| mfTopY(rfTopY), |
| mfBottomXLeft(rfBottomXLeft), |
| mfBottomXRight(rfBottomXRight), |
| mfBottomY(rfBottomY) |
| { |
| // guarantee mfTopXRight >= mfTopXLeft |
| if(mfTopXLeft > mfTopXRight) |
| { |
| std::swap(mfTopXLeft, mfTopXRight); |
| } |
| |
| // guarantee mfBottomXRight >= mfBottomXLeft |
| if(mfBottomXLeft > mfBottomXRight) |
| { |
| std::swap(mfBottomXLeft, mfBottomXRight); |
| } |
| |
| // guarantee mfBottomY >= mfTopY |
| if(mfTopY > mfBottomY) |
| { |
| std::swap(mfTopY, mfBottomY); |
| std::swap(mfTopXLeft, mfBottomXLeft); |
| std::swap(mfTopXRight, mfBottomXRight); |
| } |
| } |
| |
| B2DPolygon B2DTrapezoid::getB2DPolygon() const |
| { |
| B2DPolygon aRetval; |
| |
| aRetval.append(B2DPoint(getTopXLeft(), getTopY())); |
| aRetval.append(B2DPoint(getTopXRight(), getTopY())); |
| aRetval.append(B2DPoint(getBottomXRight(), getBottomY())); |
| aRetval.append(B2DPoint(getBottomXLeft(), getBottomY())); |
| aRetval.setClosed(true); |
| |
| return aRetval; |
| } |
| } // end of namespace basegfx |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| namespace basegfx |
| { |
| namespace tools |
| { |
| // convert Source PolyPolygon to trapezoids |
| void trapezoidSubdivide(B2DTrapezoidVector& ro_Result, const B2DPolyPolygon& rSourcePolyPolygon) |
| { |
| trapezoidhelper::TrapezoidSubdivider aTrapezoidSubdivider(rSourcePolyPolygon); |
| |
| aTrapezoidSubdivider.Subdivide(ro_Result); |
| } |
| |
| void createLineTrapezoidFromEdge( |
| B2DTrapezoidVector& ro_Result, |
| const B2DPoint& rPointA, |
| const B2DPoint& rPointB, |
| double fLineWidth) |
| { |
| if(fTools::lessOrEqual(fLineWidth, 0.0)) |
| { |
| // no line witdh |
| return; |
| } |
| |
| if(rPointA.equal(rPointB, fTools::getSmallValue())) |
| { |
| // points are equal, no edge |
| return; |
| } |
| |
| const double fHalfLineWidth(0.5 * fLineWidth); |
| |
| if(fTools::equal(rPointA.getX(), rPointB.getX(), fTools::getSmallValue())) |
| { |
| // vertical line |
| const double fLeftX(rPointA.getX() - fHalfLineWidth); |
| const double fRightX(rPointA.getX() + fHalfLineWidth); |
| |
| ro_Result.push_back( |
| B2DTrapezoid( |
| fLeftX, |
| fRightX, |
| std::min(rPointA.getY(), rPointB.getY()), |
| fLeftX, |
| fRightX, |
| std::max(rPointA.getY(), rPointB.getY()))); |
| } |
| else if(fTools::equal(rPointA.getY(), rPointB.getY(), fTools::getSmallValue())) |
| { |
| // horizontal line |
| const double fLeftX(std::min(rPointA.getX(), rPointB.getX())); |
| const double fRightX(std::max(rPointA.getX(), rPointB.getX())); |
| |
| ro_Result.push_back( |
| B2DTrapezoid( |
| fLeftX, |
| fRightX, |
| rPointA.getY() - fHalfLineWidth, |
| fLeftX, |
| fRightX, |
| rPointA.getY() + fHalfLineWidth)); |
| } |
| else |
| { |
| // diagonal line |
| // create perpendicular vector |
| const B2DVector aDelta(rPointB - rPointA); |
| B2DVector aPerpendicular(-aDelta.getY(), aDelta.getX()); |
| aPerpendicular.setLength(fHalfLineWidth); |
| |
| // create StartLow, StartHigh, EndLow and EndHigh |
| const B2DPoint aStartLow(rPointA + aPerpendicular); |
| const B2DPoint aStartHigh(rPointA - aPerpendicular); |
| const B2DPoint aEndHigh(rPointB - aPerpendicular); |
| const B2DPoint aEndLow(rPointB + aPerpendicular); |
| |
| // create EdgeEntries |
| basegfx::trapezoidhelper::TrDeEdgeEntries aTrDeEdgeEntries; |
| |
| aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aStartLow, &aStartHigh, 0)); |
| aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aStartHigh, &aEndHigh, 0)); |
| aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aEndHigh, &aEndLow, 0)); |
| aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aEndLow, &aStartLow, 0)); |
| aTrDeEdgeEntries.sort(); |
| |
| // here we know we have exactly four edges, and they do not cut, touch or |
| // intersect. This makes processing much easier. Get the first two as start |
| // edges for the thought trapezoid |
| basegfx::trapezoidhelper::TrDeEdgeEntries::iterator aCurrent(aTrDeEdgeEntries.begin()); |
| basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft(*aCurrent++); |
| basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight(*aCurrent++); |
| const bool bEndOnSameLine(fTools::equal(aLeft.getEnd().getY(), aRight.getEnd().getY(), fTools::getSmallValue())); |
| |
| if(bEndOnSameLine) |
| { |
| // create two triangle trapezoids |
| ro_Result.push_back( |
| B2DTrapezoid( |
| aLeft.getStart().getX(), |
| aRight.getStart().getX(), |
| aLeft.getStart().getY(), |
| aLeft.getEnd().getX(), |
| aRight.getEnd().getX(), |
| aLeft.getEnd().getY())); |
| |
| basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++); |
| basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++); |
| |
| ro_Result.push_back( |
| B2DTrapezoid( |
| aLeft2.getStart().getX(), |
| aRight2.getStart().getX(), |
| aLeft2.getStart().getY(), |
| aLeft2.getEnd().getX(), |
| aRight2.getEnd().getX(), |
| aLeft2.getEnd().getY())); |
| } |
| else |
| { |
| // create three trapezoids. Check which edge is longer and |
| // correct accordingly |
| const bool bLeftIsLonger(fTools::more(aLeft.getEnd().getY(), aRight.getEnd().getY())); |
| |
| if(bLeftIsLonger) |
| { |
| basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++); |
| basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++); |
| const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aRight.getEnd().getY())); |
| const B2DPoint aSplitRight(aRight2.getCutPointForGivenY(aLeft.getEnd().getY())); |
| |
| ro_Result.push_back( |
| B2DTrapezoid( |
| aLeft.getStart().getX(), |
| aRight.getStart().getX(), |
| aLeft.getStart().getY(), |
| aSplitLeft.getX(), |
| aRight.getEnd().getX(), |
| aRight.getEnd().getY())); |
| |
| ro_Result.push_back( |
| B2DTrapezoid( |
| aSplitLeft.getX(), |
| aRight.getEnd().getX(), |
| aRight.getEnd().getY(), |
| aLeft2.getStart().getX(), |
| aSplitRight.getX(), |
| aLeft2.getStart().getY())); |
| |
| ro_Result.push_back( |
| B2DTrapezoid( |
| aLeft2.getStart().getX(), |
| aSplitRight.getX(), |
| aLeft2.getStart().getY(), |
| aLeft2.getEnd().getX(), |
| aRight2.getEnd().getX(), |
| aLeft2.getEnd().getY())); |
| } |
| else |
| { |
| basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++); |
| basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++); |
| const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aLeft.getEnd().getY())); |
| const B2DPoint aSplitLeft(aLeft2.getCutPointForGivenY(aRight.getEnd().getY())); |
| |
| ro_Result.push_back( |
| B2DTrapezoid( |
| aLeft.getStart().getX(), |
| aRight.getStart().getX(), |
| aLeft.getStart().getY(), |
| aLeft.getEnd().getX(), |
| aSplitRight.getX(), |
| aLeft.getEnd().getY())); |
| |
| ro_Result.push_back( |
| B2DTrapezoid( |
| aLeft.getEnd().getX(), |
| aSplitRight.getX(), |
| aLeft.getEnd().getY(), |
| aSplitLeft.getX(), |
| aRight.getEnd().getX(), |
| aRight2.getStart().getY())); |
| |
| ro_Result.push_back( |
| B2DTrapezoid( |
| aSplitLeft.getX(), |
| aRight.getEnd().getX(), |
| aRight2.getStart().getY(), |
| aLeft2.getEnd().getX(), |
| aRight2.getEnd().getX(), |
| aLeft2.getEnd().getY())); |
| } |
| } |
| } |
| } |
| |
| void createLineTrapezoidFromB2DPolygon( |
| B2DTrapezoidVector& ro_Result, |
| const B2DPolygon& rPolygon, |
| double fLineWidth) |
| { |
| if(fTools::lessOrEqual(fLineWidth, 0.0)) |
| { |
| return; |
| } |
| |
| // ensure there are no curves used |
| B2DPolygon aSource(rPolygon); |
| |
| if(aSource.areControlPointsUsed()) |
| { |
| const double fPrecisionFactor = 0.25; |
| aSource = adaptiveSubdivideByDistance( aSource, fLineWidth * fPrecisionFactor ); |
| } |
| |
| const sal_uInt32 nPointCount(aSource.count()); |
| |
| if(!nPointCount) |
| { |
| return; |
| } |
| |
| const sal_uInt32 nEdgeCount(aSource.isClosed() ? nPointCount : nPointCount - 1); |
| B2DPoint aCurrent(aSource.getB2DPoint(0)); |
| |
| ro_Result.reserve(ro_Result.size() + (3 * nEdgeCount)); |
| |
| for(sal_uInt32 a(0); a < nEdgeCount; a++) |
| { |
| const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
| const B2DPoint aNext(aSource.getB2DPoint(nNextIndex)); |
| |
| createLineTrapezoidFromEdge(ro_Result, aCurrent, aNext, fLineWidth); |
| aCurrent = aNext; |
| } |
| } |
| |
| void createLineTrapezoidFromB2DPolyPolygon( |
| B2DTrapezoidVector& ro_Result, |
| const B2DPolyPolygon& rPolyPolygon, |
| double fLineWidth) |
| { |
| if(fTools::lessOrEqual(fLineWidth, 0.0)) |
| { |
| return; |
| } |
| |
| // ensure there are no curves used |
| B2DPolyPolygon aSource(rPolyPolygon); |
| |
| if(aSource.areControlPointsUsed()) |
| { |
| aSource = aSource.getDefaultAdaptiveSubdivision(); |
| } |
| |
| const sal_uInt32 nCount(aSource.count()); |
| |
| if(!nCount) |
| { |
| return; |
| } |
| |
| for(sal_uInt32 a(0); a < nCount; a++) |
| { |
| createLineTrapezoidFromB2DPolygon( |
| ro_Result, |
| aSource.getB2DPolygon(a), |
| fLineWidth); |
| } |
| } |
| |
| } // end of namespace tools |
| } // end of namespace basegfx |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // eof |