// helper class to hold a simple edge. This is only used for horizontal edges // currently, thus the YPositions will be equal. I did not create a special // class for this since holding the pointers is more effective and also can be // used as baseclass for the traversing edges
namespace {
class TrDeSimpleEdge
{ protected: // pointers to start and end point const B2DPoint* mpStart; const B2DPoint* mpEnd;
// 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)
namespace {
class TrDeEdgeEntry : public TrDeSimpleEdge
{ private: // the slope in a numerical useful form for sorting
sal_uInt32 mnSortValue;
// convenience data read access. SortValue is created on demand since // it is not always used
sal_uInt32 getSortValue() const
{ if(mnSortValue != 0) return mnSortValue;
// get radiant; has to be in the range ]0.0 .. pi[, thus scale to full // sal_uInt32 range for maximum precision constdouble fRadiant(atan2(getDeltaY(), getDeltaX()) * (SAL_MAX_UINT32 / M_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)
: TrDeSimpleEdge(pStart, pEnd),
mnSortValue(nSortValue)
{ // force traversal of deltaY downward if(mpEnd->getY() < mpStart->getY())
{
std::swap(mpStart, mpEnd);
}
// no horizontal edges allowed, all need to traverse vertically
OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
}
// data write access to StartPoint void setStart( const B2DPoint* pNewStart)
{
OSL_ENSURE(pNewStart != nullptr, "No null pointer allowed here (!)");
if(mpStart != pNewStart)
{
mpStart = pNewStart;
// no horizontal edges allowed, all need to traverse vertically
OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
}
}
// data write access to EndPoint void setEnd( const B2DPoint* pNewEnd)
{
OSL_ENSURE(pNewEnd != nullptr, "No null pointer allowed here (!)");
if(mpEnd != pNewEnd)
{
mpEnd = pNewEnd;
// no horizontal edges allowed, all need to traverse vertically
OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
}
}
// operator for sort support. Sort by Y, X and slope (in that order) booloperator<(const TrDeEdgeEntry& rComp) const
{ if(fTools::equal(getStart().getY(), rComp.getStart().getY()))
{ if(fTools::equal(getStart().getX(), rComp.getStart().getX()))
{ // 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) const
{ // avoid div/0 if (getDeltaY() == 0) return B2DPoint(getStart().getX(), fGivenY); // Calculate cut point locally (do not use interpolate) since it is numerically // necessary to guarantee the new, equal Y-coordinate constdouble fFactor((fGivenY - getStart().getY()) / getDeltaY()); constdouble fDeltaXNew(fFactor * getDeltaX());
// FIXME: templatize this and use it for TrDeEdgeEntries too ...
namespace {
/// Class to allow efficient allocation and release of B2DPoints class PointBlockAllocator
{ staticconst size_t nBlockSize = 32;
size_t nCurPoint;
B2DPoint *mpPointBase; /// Special case the first allocation to avoid it.
B2DPoint maFirstStackBlock[nBlockSize];
std::vector< B2DPoint * > maBlocks; public:
PointBlockAllocator() :
nCurPoint( nBlockSize ),
mpPointBase( maFirstStackBlock )
{
}
/// This is a very uncommon case but why not ... void freeIfLast(B2DPoint const *pPoint)
{ // just re-use the last point if we can. if ( nCurPoint > 0 && pPoint == mpPointBase + nCurPoint - 1 )
nCurPoint--;
}
};
// helper class to handle the complete trapezoid subdivision of a PolyPolygon class TrapezoidSubdivider
{ private: // local data
TrDeEdgeEntries maTrDeEdgeEntries;
std::vector< B2DPoint > maPoints; /// new points allocated for cuts
PointBlockAllocator 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, const TrDeEdgeEntries::iterator& aCurrent)
{ // do not create edges without deltaY: do not split when start is identical if(aEdge.getStart().equal(rCutPoint))
{ returnfalse;
}
// do not create edges without deltaY: do not split when end is identical if(aEdge.getEnd().equal(rCutPoint))
{ returnfalse;
}
if(fOldDeltaYStart <= 0.0)
{ // do not split: the resulting edge would be horizontal // correct it to new start point
aEdge.setStart(&rCutPoint); returnfalse;
}
if(fNewDeltaYStart <= 0.0)
{ // do not split: the resulting edge would be horizontal // correct it to new end point
aEdge.setEnd(&rCutPoint); returnfalse;
}
// 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);
returntrue;
}
bool testAndCorrectEdgeIntersection(
TrDeEdgeEntries::reference aEdgeA,
TrDeEdgeEntries::reference aEdgeB, const TrDeEdgeEntries::iterator& aCurrent)
{ // Exclude simple cases: same start or end point if(aEdgeA.getStart().equal(aEdgeB.getStart()))
{ returnfalse;
}
// there were horizontal edges. These can be excluded, but // cuts with other edges need to be solved and added before // ignoring them for(const TrDeSimpleEdge & rHorEdge : rTrDeSimpleEdges)
{ // get horizontal edge as candidate; prepare its range and fixed Y const B1DRange aRange(rHorEdge.getStart().getX(), rHorEdge.getEnd().getX()); constdouble fFixedY(rHorEdge.getStart().getY());
// loop over traversing edges
TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin());
do
{ // get compare edge
TrDeEdgeEntries::reference aCompare(*aCurrent++);
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);
if(nCount > 2)
{ for(sal_uInt32 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);
if(nCount > 2)
{ // get the last point of the current polygon
B2DPoint* pPrev(&maPoints[nCount + nStartIndex - 1]);
for(sal_uInt32 b = 0; b < nCount; b++)
{ // get next point
B2DPoint* pCurr(&maPoints[nStartIndex++]);
if(fTools::equal(pPrev->getY(), pCurr->getY()))
{ // horizontal edge, check for single point if(!fTools::equal(pPrev->getX(), pCurr->getX()))
{ // X-order not needed, just add
aTrDeSimpleEdges.emplace_back(pPrev, pCurr);
constdouble 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.emplace_back(pPrev, pCurr, 0);
}
// prepare next step
pPrev = pCurr;
}
}
}
}
if(!maTrDeEdgeEntries.empty())
{ // single and initial sort of traversing edges
maTrDeEdgeEntries.sort();
// solve horizontal edges if there are any detected
solveHorizontalEdges(aTrDeSimpleEdges);
}
}
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 adaptations 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 changes 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.
ro_Result.reserve(ro_Result.size() + maTrDeEdgeEntries.size());
}
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_FAIL("Trapezoid decomposer in illegal state (!)");
maTrDeEdgeEntries.pop_front(); continue;
}
// get second edge
TrDeEdgeEntries::reference aRight(*aCurrent++);
if(!fTools::equal(aLeft.getStart().getY(), aRight.getStart().getY()))
{ // 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_FAIL("Trapezoid 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 adaptation // needs to be prepared. Also remember which one actually is longer. constbool bEndOnSameLine(fTools::equal(aLeftEnd.getY(), aRightEnd.getY())); bool bLeftIsLonger(false);
if(!bEndOnSameLine)
{ // check which edge is longer and correct accordingly
bLeftIsLonger = fTools::more(aLeftEnd.getY(), aRightEnd.getY());
// check for same start and end points constbool bSameStartPoint(aLeft.getStart().equal(aRight.getStart())); constbool bSameEndPoint(aLeftEnd.equal(aRightEnd));
// 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 = maNewPoints.allocatePoint(aLeftEnd);
// 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 // possibly sorted insertions of new EdgeNodes. Also prepare stop flag
TrDeEdgeEntries::iterator aLoop(aCurrent); bool bDone(false);
do
{ // get compare edge and its 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()))
{ 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 = maNewPoints.allocatePoint(aSplitLeft);
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 possibly (if prepared); but this is no reason not to // use it in the same loop iteration if(!bEndOnSameLine)
{ if(bLeftIsLonger)
{
B2DPoint* pNewPoint = maNewPoints.allocatePoint(aLeftEnd);
// 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.emplace_back(
aLeft.getStart().getX(),
aRight.getStart().getX(),
aLeft.getStart().getY(),
aLeftEnd.getX(),
aRightEnd.getX(),
aLeftEnd.getY());
// 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++); constbool bEndOnSameLine(fTools::equal(aLeft.getEnd().getY(), aRight.getEnd().getY()));