diff options
author | Vladimir Glazounov <vg@openoffice.org> | 2008-08-19 23:02:34 +0000 |
---|---|---|
committer | Vladimir Glazounov <vg@openoffice.org> | 2008-08-19 23:02:34 +0000 |
commit | 2bd76c3928d6250c287fb8910e8843d035100e5e (patch) | |
tree | 984ad51ea3f30b0d54f8802fb9c0a714b1375812 /basegfx | |
parent | c0bf981dce151b8967148cee49dd3161791966b9 (diff) |
INTEGRATION: CWS aw033 (1.20.2); FILE MERGED
2008/06/24 15:26:47 aw 1.20.2.24: #i39532# corrections
2008/05/27 14:08:45 aw 1.20.2.23: #i39532# changes DEV300 m12 resync corrections
2008/05/14 14:40:21 aw 1.20.2.22: RESYNC: (1.26-1.28); FILE MERGED
2007/12/18 15:07:16 aw 1.20.2.21: #i39532# cleanup
2007/12/17 09:52:16 aw 1.20.2.20: #i39532# minor primitive corrections
2007/12/12 13:13:33 aw 1.20.2.19: #i39532# clipping changes
2007/11/27 11:57:02 aw 1.20.2.18: #i39532# added GetRange() to B2DPolygon
2007/11/26 11:21:59 aw 1.20.2.17: #i39532# reworked B2DPolygon default decomposition
2007/11/22 14:56:58 aw 1.20.2.16: #i39532# polygon bezier changes
2007/11/20 11:26:30 aw 1.20.2.15: #i39532# warning adaptions for unxlngi6
2007/11/19 10:17:02 aw 1.20.2.14: #i39532# Lot of changes to make polygon stuff bezier-able
2007/11/07 17:38:48 aw 1.20.2.13: #i39532# prep version for HDU
2007/11/07 14:24:29 aw 1.20.2.12: #i39532# committing to have a base for HDU
2007/10/22 10:41:56 aw 1.20.2.11: #i39523# changes for SD primitive usage
2007/10/17 10:41:29 aw 1.20.2.10: #i39532# support for reSegmentPolygonEdges
2007/09/20 09:48:21 aw 1.20.2.9: #i39532# added Waveline support and tiled polygon
2007/08/13 15:28:43 aw 1.20.2.8: #i39532# changes after resync
2007/08/09 22:04:12 aw 1.20.2.7: RESYNC: (1.24-1.26); FILE MERGED
2007/04/23 07:09:52 hdu 1.20.2.6: add basegfx::tools::createSimplifiedPolygon() method
2006/11/28 16:13:13 aw 1.20.2.5: RESYNC: (1.23-1.24); FILE MERGED
2006/09/26 14:49:47 aw 1.20.2.4: RESYNC: (1.21-1.23); FILE MERGED
2006/05/12 14:35:17 aw 1.20.2.3: RESYNC: (1.20-1.21); FILE MERGED
2006/05/12 11:36:06 aw 1.20.2.2: code changes for primitive support
2005/10/28 11:24:14 aw 1.20.2.1: #i39532#
Diffstat (limited to 'basegfx')
-rw-r--r-- | basegfx/source/polygon/b2dpolygontools.cxx | 1493 |
1 files changed, 1127 insertions, 366 deletions
diff --git a/basegfx/source/polygon/b2dpolygontools.cxx b/basegfx/source/polygon/b2dpolygontools.cxx index ea19a5abe44c..d001bfb5a4cb 100644 --- a/basegfx/source/polygon/b2dpolygontools.cxx +++ b/basegfx/source/polygon/b2dpolygontools.cxx @@ -7,7 +7,7 @@ * OpenOffice.org - a multi-platform office productivity suite * * $RCSfile: b2dpolygontools.cxx,v $ - * $Revision: 1.28 $ + * $Revision: 1.29 $ * * This file is part of OpenOffice.org. * @@ -43,6 +43,7 @@ #include <basegfx/point/b3dpoint.hxx> #include <basegfx/matrix/b3dhommatrix.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/curve/b2dbeziertools.hxx> #include <numeric> #include <limits> @@ -155,6 +156,10 @@ namespace basegfx { const double fSignedArea(getSignedArea(rCandidate)); + if(fTools::equalZero(fSignedArea)) + { + // ORIENTATION_NEUTRAL, already set + } if(fSignedArea > 0.0) { eRetval = ORIENTATION_POSITIVE; @@ -173,45 +178,6 @@ namespace basegfx return rCandidate.getContinuityInPoint(nIndex); } - B2DPolyPolygon removeIntersections(const B2DPolygon& rCandidate, bool bKeepOrientations) - { - B2DPolyPolygon aRetval; - - if(rCandidate.count() > 2L) - { - aRetval = SolveCrossovers(rCandidate); - - if(bKeepOrientations && aRetval.count() > 1L) - { - // there were cuts done, correct orientations - const B2VectorOrientation aOriginalOrientation(getOrientation(rCandidate)); - B2DPolyPolygon aNewRetval; - - for(sal_uInt32 a(0L); a < aRetval.count(); a++) - { - B2DPolygon aCandidate = aRetval.getB2DPolygon(a); - const B2VectorOrientation aOrientation(getOrientation(aCandidate)); - - if(aOriginalOrientation != aOrientation - && ORIENTATION_NEUTRAL != aOrientation) - { - aCandidate.flip(); - } - - aNewRetval.append(aCandidate); - } - - aRetval = aNewRetval; - } - } - else - { - aRetval.append(rCandidate); - } - - return aRetval; - } - B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound) { if(rCandidate.areControlPointsUsed()) @@ -426,7 +392,7 @@ namespace basegfx bool isInside(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder) { - const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? adaptiveSubdivideByCount(rCandidate, 6L) : rCandidate); + const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); if(bWithBorder && isPointOnPolygon(aCandidate, rPoint, true)) { @@ -485,8 +451,8 @@ namespace basegfx bool isInside(const B2DPolygon& rCandidate, const B2DPolygon& rPolygon, bool bWithBorder) { - const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? adaptiveSubdivideByCount(rCandidate, 6L) : rCandidate); - const B2DPolygon aPolygon(rPolygon.areControlPointsUsed() ? adaptiveSubdivideByCount(rPolygon, 6L) : rPolygon); + const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); + const B2DPolygon aPolygon(rPolygon.areControlPointsUsed() ? rPolygon.getDefaultAdaptiveSubdivision() : rPolygon); const sal_uInt32 nPointCount(aPolygon.count()); for(sal_uInt32 a(0L); a < nPointCount; a++) @@ -502,53 +468,39 @@ namespace basegfx return true; } - B2DRange getRange(const B2DPolygon& rCandidate) + B2DRange getRangeWithControlPoints(const B2DPolygon& rCandidate) { const sal_uInt32 nPointCount(rCandidate.count()); B2DRange aRetval; - if(rCandidate.areControlPointsUsed()) + if(nPointCount) { - if(nPointCount) + const bool bControlPointsUsed(rCandidate.areControlPointsUsed()); + + for(sal_uInt32 a(0); a < nPointCount; a++) { - B2DPoint aCurrent(rCandidate.getB2DPoint(0)); + aRetval.expand(rCandidate.getB2DPoint(a)); - for(sal_uInt32 a(0L); a < nPointCount; a++) + if(bControlPointsUsed) { - const sal_uInt32 nNextIndex((a + 1) % nPointCount); - const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex)); - - aRetval.expand(aCurrent); - - const B2DPoint aControlPointNext(rCandidate.getNextControlPoint(a)); - const B2DPoint aControlPointPrev(rCandidate.getPrevControlPoint(nNextIndex)); - - if(!aControlPointNext.equal(aCurrent) || !aControlPointPrev.equal(aNext)) - { - aRetval.expand(aControlPointNext); - aRetval.expand(aControlPointPrev); - } - - // prepare next step - aCurrent = aNext; + aRetval.expand(rCandidate.getNextControlPoint(a)); + aRetval.expand(rCandidate.getPrevControlPoint(a)); } } } - else - { - for(sal_uInt32 a(0L); a < nPointCount; a++) - { - const B2DPoint aTestPoint(rCandidate.getB2DPoint(a)); - aRetval.expand(aTestPoint); - } - } return aRetval; } + B2DRange getRange(const B2DPolygon& rCandidate) + { + // changed to use internally buffered version at B2DPolygon + return rCandidate.getB2DRange(); + } + double getSignedArea(const B2DPolygon& rCandidate) { - const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? adaptiveSubdivideByCount(rCandidate, 6L) : rCandidate); + const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); double fRetval(0.0); const sal_uInt32 nPointCount(aCandidate.count()); @@ -564,6 +516,14 @@ namespace basegfx } fRetval /= 2.0; + + // correct to zero if small enough. Also test the quadratic + // of the result since the precision is near quadratic due to + // the algorithm + if(fTools::equalZero(fRetval) || fTools::equalZero(fRetval * fRetval)) + { + fRetval = 0.0; + } } return fRetval; @@ -589,20 +549,31 @@ namespace basegfx double getEdgeLength(const B2DPolygon& rCandidate, sal_uInt32 nIndex) { - OSL_ENSURE(!rCandidate.areControlPointsUsed(), "getEdgeLength: ATM works not for curves (!)"); - OSL_ENSURE(nIndex < rCandidate.count(), "getEdgeLength: Access to polygon out of range (!)"); - double fRetval(0.0); const sal_uInt32 nPointCount(rCandidate.count()); + OSL_ENSURE(nIndex < nPointCount, "getEdgeLength: Access to polygon out of range (!)"); + double fRetval(0.0); - if(nIndex < nPointCount) + if(nPointCount) { - if(rCandidate.isClosed() || ((nIndex + 1L) != nPointCount)) + const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); + + if(rCandidate.areControlPointsUsed()) { - const sal_uInt32 nNextIndex(getIndexOfSuccessor(nIndex, rCandidate)); - const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(nIndex)); - const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex)); - const B2DVector aVector(aNextPoint - aCurrentPoint); - fRetval = aVector.getLength(); + B2DCubicBezier aEdge; + + aEdge.setStartPoint(rCandidate.getB2DPoint(nIndex)); + aEdge.setControlPointA(rCandidate.getNextControlPoint(nIndex)); + aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); + aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); + + fRetval = aEdge.getLength(); + } + else + { + const B2DPoint aCurrent(rCandidate.getB2DPoint(nIndex)); + const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex)); + + fRetval = B2DVector(aNext - aCurrent).getLength(); } } @@ -611,21 +582,41 @@ namespace basegfx double getLength(const B2DPolygon& rCandidate) { - OSL_ENSURE(!rCandidate.areControlPointsUsed(), "getLength: ATM works not for curves (!)"); double fRetval(0.0); const sal_uInt32 nPointCount(rCandidate.count()); - if(nPointCount > 1L) + if(nPointCount) { - const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); - for(sal_uInt32 a(0L); a < nLoopCount; a++) + if(rCandidate.areControlPointsUsed()) { - const sal_uInt32 nNextIndex(getIndexOfSuccessor(a, rCandidate)); - const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(a)); - const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex)); - const B2DVector aVector(aNextPoint - aCurrentPoint); - fRetval += aVector.getLength(); + B2DCubicBezier aEdge; + aEdge.setStartPoint(rCandidate.getB2DPoint(0)); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + aEdge.setControlPointA(rCandidate.getNextControlPoint(a)); + aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); + aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); + + fRetval += aEdge.getLength(); + aEdge.setStartPoint(aEdge.getEndPoint()); + } + } + else + { + B2DPoint aCurrent(rCandidate.getB2DPoint(0)); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex)); + + fRetval += B2DVector(aNext - aCurrent).getLength(); + aCurrent = aNext; + } } } @@ -636,7 +627,6 @@ namespace basegfx { B2DPoint aRetval; const sal_uInt32 nPointCount(rCandidate.count()); - const sal_uInt32 nPointCountMinusOne(nPointCount - 1L); if( 1L == nPointCount ) { @@ -645,10 +635,9 @@ namespace basegfx } else if(nPointCount > 1L) { + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); sal_uInt32 nIndex(0L); bool bIndexDone(false); - const double fZero(0.0); - double fEdgeLength(fZero); // get length if not given if(fTools::equalZero(fLength)) @@ -656,9 +645,9 @@ namespace basegfx fLength = getLength(rCandidate); } - // handle fDistance < 0.0 - if(fTools::less(fDistance, fZero)) + if(fTools::less(fDistance, 0.0)) { + // handle fDistance < 0.0 if(rCandidate.isClosed()) { // if fDistance < 0.0 increment with multiple of fLength @@ -668,14 +657,13 @@ namespace basegfx else { // crop to polygon start - fDistance = fZero; + fDistance = 0.0; bIndexDone = true; } } - - // handle fDistance >= fLength - if(fTools::moreOrEqual(fDistance, fLength)) + else if(fTools::moreOrEqual(fDistance, fLength)) { + // handle fDistance >= fLength if(rCandidate.isClosed()) { // if fDistance >= fLength decrement with multiple of fLength @@ -685,40 +673,35 @@ namespace basegfx else { // crop to polygon end - fDistance = fZero; - nIndex = nPointCountMinusOne; + fDistance = 0.0; + nIndex = nEdgeCount; bIndexDone = true; } } // look for correct index. fDistance is now [0.0 .. fLength[ - if(!bIndexDone) - { - do - { - // get length of next edge - fEdgeLength = getEdgeLength(rCandidate, nIndex); - - // edge found must be on the half-open range - // [0,fEdgeLength). - // Note that in theory, we cannot move beyond - // the last polygon point, since fDistance>=fLength - // is checked above. Unfortunately, with floating- - // point calculations, this case might happen. - // Handled by nIndex check below - if( nIndex < nPointCountMinusOne && - fDistance >= fEdgeLength ) - { - // go to next edge - fDistance -= fEdgeLength; - nIndex++; - } - else - { - // it's on this edge, stop - bIndexDone = true; - } - } while (!bIndexDone); + double fEdgeLength(getEdgeLength(rCandidate, nIndex)); + + while(!bIndexDone) + { + // edge found must be on the half-open range + // [0,fEdgeLength). + // Note that in theory, we cannot move beyond + // the last polygon point, since fDistance>=fLength + // is checked above. Unfortunately, with floating- + // point calculations, this case might happen. + // Handled by nIndex check below + if(nIndex < nEdgeCount && fTools::moreOrEqual(fDistance, fEdgeLength)) + { + // go to next edge + fDistance -= fEdgeLength; + fEdgeLength = getEdgeLength(rCandidate, ++nIndex); + } + else + { + // it's on this edge, stop + bIndexDone = true; + } } // get the point using nIndex @@ -728,21 +711,50 @@ namespace basegfx // length is in fEdgeLength. if(!fTools::equalZero(fDistance)) { - sal_uInt32 nNextIndex(getIndexOfSuccessor(nIndex, rCandidate)); - const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex)); - double fRelative(fZero); - - if(!fTools::equalZero(fEdgeLength)) + if(fTools::moreOrEqual(fDistance, fEdgeLength)) { - // clamp fRelative to [0,1] range. Borderline cases - // can happen, since this is floating point arithmetic. - fRelative = ::std::max(0.0, - ::std::min(1.0, - fDistance / fEdgeLength) ); + // end point of choosen edge + const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); + aRetval = rCandidate.getB2DPoint(nNextIndex); } + else if(fTools::equalZero(fDistance)) + { + // start point of choosen edge + aRetval = aRetval; + } + else + { + // inside edge + const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); + const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex)); + bool bDone(false); - // add calculated average value to the return value - aRetval = interpolate(aRetval, aNextPoint, fRelative); + // add calculated average value to the return value + if(rCandidate.areControlPointsUsed()) + { + // get as bezier segment + const B2DCubicBezier aBezierSegment( + aRetval, rCandidate.getNextControlPoint(nIndex), + rCandidate.getPrevControlPoint(nNextIndex), aNextPoint); + + if(aBezierSegment.isBezier()) + { + // use B2DCubicBezierHelper to bridge the non-linear gap between + // length and bezier distances + const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment); + const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fDistance)); + + aRetval = aBezierSegment.interpolatePoint(fBezierDistance); + bDone = true; + } + } + + if(!bDone) + { + const double fRelativeInEdge(fDistance / fEdgeLength); + aRetval = interpolate(aRetval, aNextPoint, fRelativeInEdge); + } + } } } @@ -764,112 +776,198 @@ namespace basegfx B2DPolygon getSnippetAbsolute(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength) { - OSL_ENSURE(!rCandidate.areControlPointsUsed(), "getSnippetAbsolute: ATM works not for curves (!)"); - B2DPolygon aRetval; const sal_uInt32 nPointCount(rCandidate.count()); - // get length if not given - if(fTools::equalZero(fLength)) - { - fLength = getLength(rCandidate); - } - - // test and correct fFrom - if(fFrom < 0.0) + if(nPointCount) { - fFrom = 0.0; - } + // get length if not given + if(fTools::equalZero(fLength)) + { + fLength = getLength(rCandidate); + } - // test and correct fTo - if(fTo > fLength) - { - fTo = fLength; - } + // test and correct fFrom + if(fTools::less(fFrom, 0.0)) + { + fFrom = 0.0; + } - // test and correct relationship of fFrom, fTo - if(fFrom > fTo) - { - fFrom = fTo = (fFrom + fTo) / 2.0; - } + // test and correct fTo + if(fTools::more(fTo, fLength)) + { + fTo = fLength; + } - if(0.0 == fFrom && fTo == fLength) - { - // result is the whole polygon - aRetval = rCandidate; - } - else - { - double fPositionOfStart(0.0); - bool bStartDone(false); - bool bEndDone(false); + // test and correct relationship of fFrom, fTo + if(fTools::more(fFrom, fTo)) + { + fFrom = fTo = (fFrom + fTo) / 2.0; + } - for(sal_uInt32 a(0L); !(bStartDone && bEndDone) && a < nPointCount; a++) + if(fTools::equalZero(fFrom) && fTools::equal(fTo, fLength)) + { + // no change, result is the whole polygon + return rCandidate; + } + else { - const sal_uInt32 nNextIndex(getIndexOfSuccessor(a, rCandidate)); - const B2DPoint aStart(rCandidate.getB2DPoint(a)); - const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex)); - const B2DVector aEdgeVector(aEnd - aStart); - const double fEdgeLength(aEdgeVector.getLength()); + B2DPolygon aRetval; + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); + double fPositionOfStart(0.0); + bool bStartDone(false); + bool bEndDone(false); - if(!bStartDone) + for(sal_uInt32 a(0L); !(bStartDone && bEndDone) && a < nEdgeCount; a++) { - if(0.0 == fFrom) + const double fEdgeLength(getEdgeLength(rCandidate, a)); + + if(!bStartDone) { - aRetval.append(aStart); - bStartDone = true; + if(fTools::equalZero(fFrom)) + { + aRetval.append(rCandidate.getB2DPoint(a)); + + if(rCandidate.areControlPointsUsed()) + { + aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a)); + } + + bStartDone = true; + } + else if(fTools::moreOrEqual(fFrom, fPositionOfStart) && fTools::less(fFrom, fPositionOfStart + fEdgeLength)) + { + // calculate and add start point + if(fTools::equalZero(fEdgeLength)) + { + aRetval.append(rCandidate.getB2DPoint(a)); + + if(rCandidate.areControlPointsUsed()) + { + aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a)); + } + } + else + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B2DPoint aStart(rCandidate.getB2DPoint(a)); + const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex)); + bool bDone(false); + + if(rCandidate.areControlPointsUsed()) + { + const B2DCubicBezier aBezierSegment( + aStart, rCandidate.getNextControlPoint(a), + rCandidate.getPrevControlPoint(nNextIndex), aEnd); + + if(aBezierSegment.isBezier()) + { + // use B2DCubicBezierHelper to bridge the non-linear gap between + // length and bezier distances + const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment); + const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fFrom - fPositionOfStart)); + B2DCubicBezier aRight; + + aBezierSegment.split(fBezierDistance, 0, &aRight); + aRetval.append(aRight.getStartPoint()); + aRetval.setNextControlPoint(aRetval.count() - 1, aRight.getControlPointA()); + bDone = true; + } + } + + if(!bDone) + { + const double fRelValue((fFrom - fPositionOfStart) / fEdgeLength); + aRetval.append(interpolate(aStart, aEnd, fRelValue)); + } + } + + bStartDone = true; + + // if same point, end is done, too. + if(fFrom == fTo) + { + bEndDone = true; + } + } } - else if(fFrom >= fPositionOfStart && fFrom < fPositionOfStart + fEdgeLength) + + if(!bEndDone && fTools::moreOrEqual(fTo, fPositionOfStart) && fTools::less(fTo, fPositionOfStart + fEdgeLength)) { - // calculate and add start point - if(0.0 != fEdgeLength) + // calculate and add end point + if(fTools::equalZero(fEdgeLength)) { - aRetval.append(interpolate(aStart, aEnd, (fFrom - fPositionOfStart) / fEdgeLength)); + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + aRetval.append(rCandidate.getB2DPoint(nNextIndex)); + + if(rCandidate.areControlPointsUsed()) + { + aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex)); + } } else { - aRetval.append(aStart); - } + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B2DPoint aStart(rCandidate.getB2DPoint(a)); + const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex)); + bool bDone(false); - bStartDone = true; + if(rCandidate.areControlPointsUsed()) + { + const B2DCubicBezier aBezierSegment( + aStart, rCandidate.getNextControlPoint(a), + rCandidate.getPrevControlPoint(nNextIndex), aEnd); + + if(aBezierSegment.isBezier()) + { + // use B2DCubicBezierHelper to bridge the non-linear gap between + // length and bezier distances + const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment); + const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fTo - fPositionOfStart)); + B2DCubicBezier aLeft; + + aBezierSegment.split(fBezierDistance, &aLeft, 0); + aRetval.append(aLeft.getEndPoint()); + aRetval.setPrevControlPoint(aRetval.count() - 1, aLeft.getControlPointB()); + bDone = true; + } + } - // if same point, end is done, too. - if(fFrom == fTo) - { - bEndDone = true; + if(!bDone) + { + const double fRelValue((fTo - fPositionOfStart) / fEdgeLength); + aRetval.append(interpolate(aStart, aEnd, fRelValue)); + } } - } - } - if(!bEndDone && fTo >= fPositionOfStart && fTo < fPositionOfStart + fEdgeLength) - { - // calculate and add end point - if(0.0 != fEdgeLength) - { - aRetval.append(interpolate(aStart, aEnd, (fTo - fPositionOfStart) / fEdgeLength)); + bEndDone = true; } - else + + if(!bEndDone) { - aRetval.append(aEnd); - } + if(bStartDone) + { + // add segments end point + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + aRetval.append(rCandidate.getB2DPoint(nNextIndex)); - bEndDone = true; - } + if(rCandidate.areControlPointsUsed()) + { + aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex)); + aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(nNextIndex)); + } + } - if(!bEndDone) - { - if(bStartDone) - { - // add segments end point - aRetval.append(aEnd); + // increment fPositionOfStart + fPositionOfStart += fEdgeLength; } - - // increment fPositionOfStart - fPositionOfStart += fEdgeLength; } + return aRetval; } } - - return aRetval; + else + { + return rCandidate; + } } B2DPolygon getSnippetRelative(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength) @@ -1156,8 +1254,7 @@ namespace basegfx { double fValue = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX(); - if(fTools::more(fValue, fZero) - && fTools::less(fValue, fOne)) + if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne)) { if(pCut) { @@ -1196,103 +1293,255 @@ namespace basegfx return false; } - B2DPolyPolygon applyLineDashing(const B2DPolygon& rCandidate, const ::std::vector<double>& raDashDotArray, double fFullDashDotLen) + void applyLineDashing(const B2DPolygon& rCandidate, const ::std::vector<double>& rDotDashArray, B2DPolyPolygon* pLineTarget, B2DPolyPolygon* pGapTarget, double fDotDashLength) { - OSL_ENSURE(!rCandidate.areControlPointsUsed(), "applyLineDashing: ATM works not for curves (!)"); - B2DPolyPolygon aRetval; + const sal_uInt32 nPointCount(rCandidate.count()); + const sal_uInt32 nDotDashCount(rDotDashArray.size()); - if(0.0 == fFullDashDotLen && rCandidate.count() && raDashDotArray.size()) + if(fTools::lessOrEqual(fDotDashLength, 0.0)) { - // calculate fFullDashDotLen from raDashDotArray - fFullDashDotLen = ::std::accumulate(raDashDotArray.begin(), raDashDotArray.end(), 0.0); + fDotDashLength = ::std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0); } - if(rCandidate.count() && fFullDashDotLen > 0.0) + if(fTools::more(fDotDashLength, 0.0) && (pLineTarget || pGapTarget) && nPointCount) { - // prepare candidate, evtl. remove curves - B2DPolygon aCandidate(rCandidate); + // clear targets + if(pLineTarget) + { + pLineTarget->clear(); + } - if(aCandidate.areControlPointsUsed()) + if(pGapTarget) { - aCandidate = adaptiveSubdivideByAngle(aCandidate); + pGapTarget->clear(); } - const sal_uInt32 nCount(aCandidate.isClosed() ? aCandidate.count() : aCandidate.count() - 1L); - sal_uInt32 nDashDotIndex(0L); - double fDashDotLength(raDashDotArray[nDashDotIndex]); + // prepare current edge's start + B2DCubicBezier aCurrentEdge; + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); + aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0)); + + // prepare DotDashArray iteration and the line/gap switching bool + sal_uInt32 nDotDashIndex(0); + bool bIsLine(true); + double fDotDashMovingLength(rDotDashArray[0]); + B2DPolygon aSnippet; - for(sal_uInt32 a(0L); a < nCount; a++) + // iterate over all edges + for(sal_uInt32 a(0); a < nEdgeCount; a++) { - const sal_uInt32 nNextIndex(getIndexOfSuccessor(a, aCandidate)); - const B2DPoint aStart(aCandidate.getB2DPoint(a)); - const B2DPoint aEnd(aCandidate.getB2DPoint(nNextIndex)); - B2DVector aVector(aEnd - aStart); - double fLength(aVector.getLength()); - double fPosOnVector(0.0); + // update current edge (fill in C1, C2 and end point) + double fLastDotDashMovingLength(0.0); + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a)); + aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); + aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); - // normalize vector for further usage as multiplication base - aVector.normalize(); + // check if we have a trivial bezier segment -> possible fallback to edge + aCurrentEdge.testAndSolveTrivialBezier(); - while(fLength >= fDashDotLength) + if(aCurrentEdge.isBezier()) { - // handle [fPosOnVector .. fPosOnVector+fDashDotLength] - if(!(nDashDotIndex % 2L)) + // bezier segment + const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge); + const double fEdgeLength(aCubicBezierHelper.getLength()); + + if(!fTools::equalZero(fEdgeLength)) { - B2DPolygon aResult; + while(fTools::less(fDotDashMovingLength, fEdgeLength)) + { + // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength] + const bool bHandleLine(bIsLine && pLineTarget); + const bool bHandleGap(!bIsLine && pGapTarget); + + if(bHandleLine || bHandleGap) + { + const double fBezierSplitStart(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength)); + const double fBezierSplitEnd(aCubicBezierHelper.distanceToRelative(fDotDashMovingLength)); + B2DCubicBezier aBezierSnippet(aCurrentEdge.snippet(fBezierSplitStart, fBezierSplitEnd)); + + if(!aSnippet.count()) + { + aSnippet.append(aBezierSnippet.getStartPoint()); + } + + aSnippet.appendBezierSegment(aBezierSnippet.getControlPointA(), aBezierSnippet.getControlPointB(), aBezierSnippet.getEndPoint()); + + if(bHandleLine) + { + pLineTarget->append(aSnippet); + } + else + { + pGapTarget->append(aSnippet); + } + + aSnippet.clear(); + } + + // prepare next DotDashArray step and flip line/gap flag + fLastDotDashMovingLength = fDotDashMovingLength; + fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount]; + bIsLine = !bIsLine; + } + + // append closing snippet [fLastDotDashMovingLength, fEdgeLength] + const bool bHandleLine(bIsLine && pLineTarget); + const bool bHandleGap(!bIsLine && pGapTarget); - // add start point - if(0.0 == fPosOnVector) + if(bHandleLine || bHandleGap) { - aResult.append(aStart); + B2DCubicBezier aRight; + const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength)); + + aCurrentEdge.split(fBezierSplit, 0, &aRight); + + if(!aSnippet.count()) + { + aSnippet.append(aRight.getStartPoint()); + } + + aSnippet.appendBezierSegment(aRight.getControlPointA(), aRight.getControlPointB(), aRight.getEndPoint()); } - else + + // prepare move to next edge + fDotDashMovingLength -= fEdgeLength; + } + } + else + { + // simple edge + const double fEdgeLength(aCurrentEdge.getEdgeLength()); + + if(!fTools::equalZero(fEdgeLength)) + { + while(fTools::less(fDotDashMovingLength, fEdgeLength)) { - aResult.append( B2DPoint(aStart + (aVector * fPosOnVector)) ); + // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength] + const bool bHandleLine(bIsLine && pLineTarget); + const bool bHandleGap(!bIsLine && pGapTarget); + + if(bHandleLine || bHandleGap) + { + if(!aSnippet.count()) + { + aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength)); + } + + aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength)); + + if(bHandleLine) + { + pLineTarget->append(aSnippet); + } + else + { + pGapTarget->append(aSnippet); + } + + aSnippet.clear(); + } + + // prepare next DotDashArray step and flip line/gap flag + fLastDotDashMovingLength = fDotDashMovingLength; + fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount]; + bIsLine = !bIsLine; } - // add end point - aResult.append( B2DPoint(aStart + (aVector * (fPosOnVector + fDashDotLength))) ); + // append snippet [fLastDotDashMovingLength, fEdgeLength] + const bool bHandleLine(bIsLine && pLineTarget); + const bool bHandleGap(!bIsLine && pGapTarget); - // add line to PolyPolygon - aRetval.append(aResult); + if(bHandleLine || bHandleGap) + { + if(!aSnippet.count()) + { + aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength)); + } + + aSnippet.append(aCurrentEdge.getEndPoint()); + } + + // prepare move to next edge + fDotDashMovingLength -= fEdgeLength; } + } + + // prepare next edge step (end point gets new start point) + aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint()); + } - // consume from fDashDotLength - fPosOnVector += fDashDotLength; - fLength -= fDashDotLength; - nDashDotIndex = (nDashDotIndex + 1L) % raDashDotArray.size(); - fDashDotLength = raDashDotArray[nDashDotIndex]; + // append last intermediate results (if exists) + if(aSnippet.count()) + { + if(bIsLine && pLineTarget) + { + pLineTarget->append(aSnippet); } + else if(!bIsLine && pGapTarget) + { + pGapTarget->append(aSnippet); + } + } - // handle [fPosOnVector .. fPosOnVector+fLength (bzw. end)] - if((fLength > 0.0) && (!(nDashDotIndex % 2L))) + // check if start and end polygon may be merged + if(pLineTarget) + { + const sal_uInt32 nCount(pLineTarget->count()); + + if(nCount > 1) { - B2DPolygon aResult; + // these polygons were created above, there exists none with less than two points, + // thus dircet point access below is allowed + const B2DPolygon aFirst(pLineTarget->getB2DPolygon(0)); + B2DPolygon aLast(pLineTarget->getB2DPolygon(nCount - 1)); - // add start point - if(0.0 == fPosOnVector) - { - aResult.append(aStart); - } - else + if(aFirst.getB2DPoint(0).equal(aLast.getB2DPoint(aLast.count() - 1))) { - // add modified start - aResult.append( B2DPoint(aStart + (aVector * fPosOnVector)) ); + // start of first and end of last are the same -> merge them + aLast.append(aFirst); + aLast.removeDoublePoints(); + pLineTarget->setB2DPolygon(0, aLast); + pLineTarget->remove(nCount - 1); } + } + } - // add end point - aResult.append(aEnd); + if(pGapTarget) + { + const sal_uInt32 nCount(pGapTarget->count()); - // add line to PolyPolygon - aRetval.append(aResult); - } + if(nCount > 1) + { + // these polygons were created above, there exists none with less than two points, + // thus dircet point access below is allowed + const B2DPolygon aFirst(pGapTarget->getB2DPolygon(0)); + B2DPolygon aLast(pGapTarget->getB2DPolygon(nCount - 1)); - // consume from fDashDotLength - fDashDotLength -= fLength; + if(aFirst.getB2DPoint(0).equal(aLast.getB2DPoint(aLast.count() - 1))) + { + // start of first and end of last are the same -> merge them + aLast.append(aFirst); + aLast.removeDoublePoints(); + pGapTarget->setB2DPolygon(0, aLast); + pGapTarget->remove(nCount - 1); + } + } } } + else + { + // parameters make no sense, just add source to targets + if(pLineTarget) + { + pLineTarget->append(rCandidate); + } - return aRetval; + if(pGapTarget) + { + pGapTarget->append(rCandidate); + } + } } // test if point is inside epsilon-range around an edge defined @@ -1385,33 +1634,44 @@ namespace basegfx // with distance fDistance and rounded edges (start and end point). bool isInEpsilonRange(const B2DPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance) { - if(rCandidate.areControlPointsUsed()) - { - // call myself recursively with subdivided input - const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate)); - return isInEpsilonRange(aCandidate, rTestPosition, fDistance); - } - else + // force to non-bezier polygon + const B2DPolygon aCandidate(rCandidate.getDefaultAdaptiveSubdivision()); + const sal_uInt32 nPointCount(aCandidate.count()); + + if(nPointCount) { - if(rCandidate.count()) - { - const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? rCandidate.count() : rCandidate.count() - 1L); + const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L); + B2DPoint aCurrent(aCandidate.getB2DPoint(0)); - for(sal_uInt32 a(0L); a < nEdgeCount; a++) + if(nEdgeCount) + { + // edges + for(sal_uInt32 a(0); a < nEdgeCount; a++) { - B2DPoint aStart(rCandidate.getB2DPoint(a)); - const sal_uInt32 nNextIndex(getIndexOfSuccessor(a, rCandidate)); - B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex)); + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex)); - if(isInEpsilonRange(aStart, aEnd, rTestPosition, fDistance)) + if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance)) { return true; } + + // prepare next step + aCurrent = aNext; + } + } + else + { + // no edges, but points -> not closed. Check single point. Just + // use isInEpsilonRange with twice the same point, it handles those well + if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance)) + { + return true; } } - - return false; } + + return false; } B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadius ) @@ -1466,8 +1726,25 @@ namespace basegfx if(fZero == fRadiusX || fZero == fRadiusY) { - // at least in one direction no radius, use rectangle - return createPolygonFromRect( rRect ); + B2DPolygon aRetval; + + // at least in one direction no radius, use rectangle. + // Do not use createPolygonFromRect() here since original + // creator (historical reasons) still creates a start point at the + // bottom center, so do the same here to get the same line patterns. + // Due to this the order of points is different, too. + const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY()); + aRetval.append(aBottomCenter); + + aRetval.append( B2DPoint( rRect.getMinX(), rRect.getMaxY() ) ); + aRetval.append( B2DPoint( rRect.getMinX(), rRect.getMinY() ) ); + aRetval.append( B2DPoint( rRect.getMaxX(), rRect.getMinY() ) ); + aRetval.append( B2DPoint( rRect.getMaxX(), rRect.getMaxY() ) ); + + // close + aRetval.setClosed( true ); + + return aRetval; } else if(fOne == fRadiusX && fOne == fRadiusY) { @@ -1485,46 +1762,53 @@ namespace basegfx const double fBowY((rRect.getHeight() / 2.0) * fRadiusY); const double fKappa((M_SQRT2 - 1.0) * 4.0 / 3.0); + // create start point at bottom center + if(fOne != fRadiusX) + { + const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY()); + aRetval.append(aBottomCenter); + } + // create first bow - const B2DPoint aBottomRight(rRect.getMaxX(), rRect.getMaxY()); { - const B2DPoint aStart(aBottomRight + B2DPoint(0.0, -fBowY)); - const B2DPoint aStop(aBottomRight + B2DPoint(-fBowX, 0.0)); + const B2DPoint aBottomRight(rRect.getMaxX(), rRect.getMaxY()); + const B2DPoint aStart(aBottomRight + B2DPoint(-fBowX, 0.0)); + const B2DPoint aStop(aBottomRight + B2DPoint(0.0, -fBowY)); aRetval.append(aStart); aRetval.appendBezierSegment(interpolate(aStart, aBottomRight, fKappa), interpolate(aStop, aBottomRight, fKappa), aStop); } // create second bow - const B2DPoint aBottomLeft(rRect.getMinX(), rRect.getMaxY()); { - const B2DPoint aStart(aBottomLeft + B2DPoint(fBowX, 0.0)); - const B2DPoint aStop(aBottomLeft + B2DPoint(0.0, -fBowY)); + const B2DPoint aTopRight(rRect.getMaxX(), rRect.getMinY()); + const B2DPoint aStart(aTopRight + B2DPoint(0.0, fBowY)); + const B2DPoint aStop(aTopRight + B2DPoint(-fBowX, 0.0)); aRetval.append(aStart); - aRetval.appendBezierSegment(interpolate(aStart, aBottomLeft, fKappa), interpolate(aStop, aBottomLeft, fKappa), aStop); + aRetval.appendBezierSegment(interpolate(aStart, aTopRight, fKappa), interpolate(aStop, aTopRight, fKappa), aStop); } // create third bow - const B2DPoint aTopLeft(rRect.getMinX(), rRect.getMinY()); { - const B2DPoint aStart(aTopLeft + B2DPoint(0.0, fBowY)); - const B2DPoint aStop(aTopLeft + B2DPoint(fBowX, 0.0)); + const B2DPoint aTopLeft(rRect.getMinX(), rRect.getMinY()); + const B2DPoint aStart(aTopLeft + B2DPoint(fBowX, 0.0)); + const B2DPoint aStop(aTopLeft + B2DPoint(0.0, fBowY)); aRetval.append(aStart); aRetval.appendBezierSegment(interpolate(aStart, aTopLeft, fKappa), interpolate(aStop, aTopLeft, fKappa), aStop); } // create forth bow - const B2DPoint aTopRight(rRect.getMaxX(), rRect.getMinY()); { - const B2DPoint aStart(aTopRight + B2DPoint(-fBowX, 0.0)); - const B2DPoint aStop(aTopRight + B2DPoint(0.0, fBowY)); + const B2DPoint aBottomLeft(rRect.getMinX(), rRect.getMaxY()); + const B2DPoint aStart(aBottomLeft + B2DPoint(0.0, -fBowY)); + const B2DPoint aStop(aBottomLeft + B2DPoint(fBowX, 0.0)); aRetval.append(aStart); - aRetval.appendBezierSegment(interpolate(aStart, aTopRight, fKappa), interpolate(aStop, aTopRight, fKappa), aStop); + aRetval.appendBezierSegment(interpolate(aStart, aBottomLeft, fKappa), interpolate(aStop, aBottomLeft, fKappa), aStop); } // close aRetval.setClosed( true ); - // remove double created points if there is extreme radius + // remove double created points if there are extreme radii envolved if(fOne == fRadiusX || fOne == fRadiusY) { aRetval.removeDoublePoints(); @@ -1590,15 +1874,15 @@ namespace basegfx } } - B2DPolygon createPolygonFromUnitCircle() + B2DPolygon createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant) { B2DPolygon aRetval; // create unit-circle with all 4 segments, close it - appendUnitCircleQuadrant(aRetval, 0); - appendUnitCircleQuadrant(aRetval, 1); - appendUnitCircleQuadrant(aRetval, 2); - appendUnitCircleQuadrant(aRetval, 3); + appendUnitCircleQuadrant(aRetval, nStartQuadrant % 4); nStartQuadrant++; + appendUnitCircleQuadrant(aRetval, nStartQuadrant % 4); nStartQuadrant++; + appendUnitCircleQuadrant(aRetval, nStartQuadrant % 4); nStartQuadrant++; + appendUnitCircleQuadrant(aRetval, nStartQuadrant % 4); nStartQuadrant++; aRetval.setClosed(true); // remove double points between segments created by segmented creation @@ -1675,9 +1959,8 @@ namespace basegfx B2DCubicBezier aCubicBezier( aQuadrant.getB2DPoint(0L), aQuadrant.getNextControlPoint(0L), aQuadrant.getPrevControlPoint(1L), aQuadrant.getB2DPoint(1L)); - B2DCubicBezier aCubicBezierWaste; - aCubicBezier.split(fStart, aCubicBezier, aCubicBezierWaste); + aCubicBezier.split(fStart, &aCubicBezier, 0); rPolygon.append(aCubicBezier.getEndPoint()); } } @@ -1686,23 +1969,8 @@ namespace basegfx B2DCubicBezier aCubicBezier( aQuadrant.getB2DPoint(0L), aQuadrant.getNextControlPoint(0L), aQuadrant.getPrevControlPoint(1L), aQuadrant.getB2DPoint(1L)); - B2DCubicBezier aCubicBezierWaste; - - if(!bEndIsOne) - { - aCubicBezier.split(fEnd, aCubicBezier, aCubicBezierWaste); - - if(!bStartIsZero) - { - fStart /= fEnd; - } - } - - if(!bStartIsZero) - { - aCubicBezier.split(fStart, aCubicBezierWaste, aCubicBezier); - } + aCubicBezier = aCubicBezier.snippet(fStart, fEnd); rPolygon.append(aCubicBezier.getStartPoint()); rPolygon.appendBezierSegment(aCubicBezier.getControlPointA(), aCubicBezier.getControlPointB(), aCubicBezier.getEndPoint()); } @@ -1984,7 +2252,7 @@ namespace basegfx bool isPointOnPolygon(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithPoints) { - const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? adaptiveSubdivideByCount(rCandidate, 6L) : rCandidate); + const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); const sal_uInt32 nPointCount(aCandidate.count()); if(nPointCount > 1L) @@ -2232,10 +2500,35 @@ namespace basegfx return aRetval; } + double getDistancePointToEndlessRay(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut) + { + if(rPointA.equal(rPointB)) + { + rCut = 0.0; + const B2DVector aVector(rTestPoint - rPointA); + return aVector.getLength(); + } + else + { + // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint + const B2DVector aVector1(rPointB - rPointA); + const B2DVector aVector2(rTestPoint - rPointA); + const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY())); + const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY())); + + rCut = fDividend / fDivisor; + + const B2DPoint aCutPoint(rPointA + rCut * aVector1); + const B2DVector aVector(rTestPoint - aCutPoint); + return aVector.getLength(); + } + } + double getSmallestDistancePointToEdge(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut) { if(rPointA.equal(rPointB)) { + rCut = 0.0; const B2DVector aVector(rTestPoint - rPointA); return aVector.getLength(); } @@ -2447,17 +2740,31 @@ namespace basegfx // predecessor if(!rCandidate.isPrevControlPointUsed(nIndex)) { - const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount); - rCandidate.setPrevControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0)); - bRetval = true; + if(!rCandidate.isClosed() && 0 == nIndex) + { + // do not create previous vector for start point of open polygon + } + else + { + const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount); + rCandidate.setPrevControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0)); + bRetval = true; + } } // successor if(!rCandidate.isNextControlPointUsed(nIndex)) { - const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); - rCandidate.setNextControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0)); - bRetval = true; + if(!rCandidate.isClosed() && nIndex + 1 == nPointCount) + { + // do not create next vector for end point of open polygon + } + else + { + const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); + rCandidate.setNextControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0)); + bRetval = true; + } } } @@ -2492,15 +2799,33 @@ namespace basegfx { if(rCandidate.isPrevControlPointUsed(nIndex)) { - const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount); - rCandidate.setPrevControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0)); + if(!rCandidate.isClosed() && 0 == nIndex) + { + // remove existing previous vector for start point of open polygon + rCandidate.resetPrevControlPoint(nIndex); + } + else + { + const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount); + rCandidate.setPrevControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0)); + } + bRetval = true; } if(rCandidate.isNextControlPointUsed(nIndex)) { - const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); - rCandidate.setNextControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0)); + if(!rCandidate.isClosed() && nIndex == nPointCount + 1) + { + // remove next vector for end point of open polygon + rCandidate.resetNextControlPoint(nIndex); + } + else + { + const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); + rCandidate.setNextControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0)); + } + bRetval = true; } @@ -2510,6 +2835,7 @@ namespace basegfx { if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex)) { + // lengths both exist since both are used B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint); B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint); const double fLenPrev(aVectorPrev.getLength()); @@ -2518,9 +2844,9 @@ namespace basegfx aVectorNext.normalize(); const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext)); - if(ORIENTATION_NEUTRAL == aOrientation) + if(ORIENTATION_NEUTRAL == aOrientation && aVectorPrev.scalar(aVectorNext) < 0.0) { - // already parallel, check length + // parallel and opposite direction; check length if(fTools::equal(fLenPrev, fLenNext)) { // this would be even C2, but we want C1. Use the lengths of the corresponding edges. @@ -2537,7 +2863,7 @@ namespace basegfx } else { - // not parallel, set vectors and length + // not parallel or same direction, set vectors and length const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext)); if(ORIENTATION_POSITIVE == aOrientation) @@ -2562,6 +2888,7 @@ namespace basegfx { if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex)) { + // lengths both exist since both are used B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint); B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint); const double fCommonLength((aVectorPrev.getLength() + aVectorNext.getLength()) / 2.0); @@ -2569,9 +2896,9 @@ namespace basegfx aVectorNext.normalize(); const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext)); - if(ORIENTATION_NEUTRAL == aOrientation) + if(ORIENTATION_NEUTRAL == aOrientation && aVectorPrev.scalar(aVectorNext) < 0.0) { - // already parallel, set length. Use one direction for better numerical correctness + // parallel and opposite direction; set length. Use one direction for better numerical correctness const B2DVector aScaledDirection(aVectorPrev * fCommonLength); rCandidate.setControlPoints(nIndex, @@ -2580,7 +2907,7 @@ namespace basegfx } else { - // not parallel, set vectors and length + // not parallel or same direction, set vectors and length const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext)); const B2DVector aPerpendicular(aNormalizedPerpendicular * fCommonLength); @@ -2608,34 +2935,226 @@ namespace basegfx return bRetval; } - bool isPolyPolygonEqualRectangle( const ::basegfx::B2DPolyPolygon& rPolyPoly, - const ::basegfx::B2DRange& rRect ) + B2DPolygon growInNormalDirection(const B2DPolygon& rCandidate, double fValue) + { + if(0.0 != fValue) + { + if(rCandidate.areControlPointsUsed()) + { + // call myself recursively with subdivided input + const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate)); + return growInNormalDirection(aCandidate, fValue); + } + else + { + B2DPolygon aRetval; + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount) + { + B2DPoint aPrev(rCandidate.getB2DPoint(nPointCount - 1L)); + B2DPoint aCurrent(rCandidate.getB2DPoint(0L)); + + for(sal_uInt32 a(0L); a < nPointCount; a++) + { + const B2DPoint aNext(rCandidate.getB2DPoint(a + 1L == nPointCount ? 0L : a + 1L)); + const B2DVector aBack(aPrev - aCurrent); + const B2DVector aForw(aNext - aCurrent); + const B2DVector aPerpBack(getNormalizedPerpendicular(aBack)); + const B2DVector aPerpForw(getNormalizedPerpendicular(aForw)); + B2DVector aDirection(aPerpBack - aPerpForw); + aDirection.normalize(); + aDirection *= fValue; + aRetval.append(aCurrent + aDirection); + + // prepare next step + aPrev = aCurrent; + aCurrent = aNext; + } + } + + // copy closed state + aRetval.setClosed(rCandidate.isClosed()); + + return aRetval; + } + } + else + { + return rCandidate; + } + } + + B2DPolygon reSegmentPolygon(const B2DPolygon& rCandidate, sal_uInt32 nSegments) + { + B2DPolygon aRetval; + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount && nSegments) + { + // get current segment count + const sal_uInt32 nSegmentCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); + + if(nSegmentCount == nSegments) + { + aRetval = rCandidate; + } + else + { + const double fLength(getLength(rCandidate)); + const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nSegments : nSegments + 1L); + + for(sal_uInt32 a(0L); a < nLoopCount; a++) + { + const double fRelativePos((double)a / (double)nSegments); // 0.0 .. 1.0 + const B2DPoint aNewPoint(getPositionRelative(rCandidate, fRelativePos, fLength)); + aRetval.append(aNewPoint); + } + + // copy closed flag + aRetval.setClosed(rCandidate.isClosed()); + } + } + + return aRetval; + } + + B2DPolygon reSegmentPolygonEdges(const B2DPolygon& rCandidate, sal_uInt32 nSubEdges, bool bHandleCurvedEdges, bool bHandleStraightEdges) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount < 2 || nSubEdges < 2 || (!bHandleCurvedEdges && !bHandleStraightEdges)) + { + // nothing to do: + // - less than two points -> no edge at all + // - less than two nSubEdges -> no resegment necessary + // - neither bHandleCurvedEdges nor bHandleStraightEdges -> nothing to do + return rCandidate; + } + else + { + B2DPolygon aRetval; + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); + B2DCubicBezier aCurrentEdge; + + // prepare first edge and add start point to target + aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0)); + aRetval.append(aCurrentEdge.getStartPoint()); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + // fill edge + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a)); + aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); + aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); + + if(aCurrentEdge.isBezier()) + { + if(bHandleCurvedEdges) + { + for(sal_uInt32 b(nSubEdges); b > 1; b--) + { + const double fSplitPoint(1.0 / b); + B2DCubicBezier aLeftPart; + + aCurrentEdge.split(fSplitPoint, &aLeftPart, &aCurrentEdge); + aRetval.appendBezierSegment(aLeftPart.getControlPointA(), aLeftPart.getControlPointB(), aLeftPart.getEndPoint()); + } + } + + // copy remaining segment to target + aRetval.appendBezierSegment(aCurrentEdge.getControlPointA(), aCurrentEdge.getControlPointB(), aCurrentEdge.getEndPoint()); + } + else + { + if(bHandleStraightEdges) + { + for(sal_uInt32 b(nSubEdges); b > 1; b--) + { + const double fSplitPoint(1.0 / b); + const B2DPoint aSplitPoint(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fSplitPoint)); + + aRetval.append(aSplitPoint); + aCurrentEdge.setStartPoint(aSplitPoint); + } + } + + // copy remaining segment to target + aRetval.append(aCurrentEdge.getEndPoint()); + } + + // prepare next step + aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint()); + } + + // copy closed flag and return + aRetval.setClosed(rCandidate.isClosed()); + return aRetval; + } + } + + B2DPolygon interpolate(const B2DPolygon& rOld1, const B2DPolygon& rOld2, double t) + { + OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolygon interpolate: Different geometry (!)"); + + if(fTools::lessOrEqual(t, 0.0) || rOld1 == rOld2) + { + return rOld1; + } + else if(fTools::moreOrEqual(t, 1.0)) + { + return rOld2; + } + else + { + B2DPolygon aRetval; + const bool bInterpolateVectors(rOld1.areControlPointsUsed() || rOld2.areControlPointsUsed()); + aRetval.setClosed(rOld1.isClosed() && rOld2.isClosed()); + + for(sal_uInt32 a(0L); a < rOld1.count(); a++) + { + aRetval.append(interpolate(rOld1.getB2DPoint(a), rOld2.getB2DPoint(a), t)); + + if(bInterpolateVectors) + { + aRetval.setPrevControlPoint(a, interpolate(rOld1.getPrevControlPoint(a), rOld2.getPrevControlPoint(a), t)); + aRetval.setNextControlPoint(a, interpolate(rOld1.getNextControlPoint(a), rOld2.getNextControlPoint(a), t)); + } + } + + return aRetval; + } + } + + bool isPolyPolygonEqualRectangle( const B2DPolyPolygon& rPolyPoly, + const B2DRange& rRect ) { // exclude some cheap cases first if( rPolyPoly.count() != 1 ) return false; // fill array with rectangle vertices - const ::basegfx::B2DPoint aPoints[] = + const B2DPoint aPoints[] = { - ::basegfx::B2DPoint(rRect.getMinX(),rRect.getMinY()), - ::basegfx::B2DPoint(rRect.getMaxX(),rRect.getMinY()), - ::basegfx::B2DPoint(rRect.getMaxX(),rRect.getMaxY()), - ::basegfx::B2DPoint(rRect.getMinX(),rRect.getMaxY()) + B2DPoint(rRect.getMinX(),rRect.getMinY()), + B2DPoint(rRect.getMaxX(),rRect.getMinY()), + B2DPoint(rRect.getMaxX(),rRect.getMaxY()), + B2DPoint(rRect.getMinX(),rRect.getMaxY()) }; - const ::basegfx::B2DPolygon& rPoly( rPolyPoly.getB2DPolygon(0) ); + const B2DPolygon& rPoly( rPolyPoly.getB2DPolygon(0) ); const sal_uInt32 nCount( rPoly.count() ); const double epsilon = ::std::numeric_limits<double>::epsilon(); for(unsigned int j=0; j<4; ++j) { - const ::basegfx::B2DPoint &p1 = aPoints[j]; - const ::basegfx::B2DPoint &p2 = aPoints[(j+1)%4]; + const B2DPoint &p1 = aPoints[j]; + const B2DPoint &p2 = aPoints[(j+1)%4]; bool bPointOnBoundary = false; for( sal_uInt32 i=0; i<nCount; ++i ) { - const ::basegfx::B2DPoint p(rPoly.getB2DPoint(i)); + const B2DPoint p(rPoly.getB2DPoint(i)); // 1 | x0 y0 1 | // A = - | x1 y1 1 | @@ -2660,6 +3179,87 @@ namespace basegfx return true; } + + // create simplified version of the original polygon by + // replacing segments with spikes/loops and self intersections + // by several trivial sub-segments + B2DPolygon createSimplifiedPolygon( const B2DPolygon& rCandidate ) + { + const sal_uInt32 nCount(rCandidate.count()); + + if(nCount && rCandidate.areControlPointsUsed()) + { + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nCount : nCount - 1); + B2DPolygon aRetval; + B2DCubicBezier aSegment; + + aSegment.setStartPoint(rCandidate.getB2DPoint(0)); + aRetval.append(aSegment.getStartPoint()); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + // fill edge + const sal_uInt32 nNextIndex((a + 1) % nCount); + aSegment.setControlPointA(rCandidate.getNextControlPoint(a)); + aSegment.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); + aSegment.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); + + if(aSegment.isBezier()) + { + double fExtremumPos(0.0); + sal_uInt32 nExtremumCounter(4); + + while(nExtremumCounter-- && aSegment.isBezier() && aSegment.getMinimumExtremumPosition(fExtremumPos)) + { + // split off left, now extremum-free part and append + B2DCubicBezier aLeft; + + aSegment.split(fExtremumPos, &aLeft, &aSegment); + aLeft.testAndSolveTrivialBezier(); + aSegment.testAndSolveTrivialBezier(); + + if(aLeft.isBezier()) + { + aRetval.appendBezierSegment(aLeft.getControlPointA(), aLeft.getControlPointB(), aLeft.getEndPoint()); + } + else + { + aRetval.append(aLeft.getEndPoint()); + } + } + + // append (evtl. reduced) rest of Segment + if(aSegment.isBezier()) + { + aRetval.appendBezierSegment(aSegment.getControlPointA(), aSegment.getControlPointB(), aSegment.getEndPoint()); + } + else + { + aRetval.append(aSegment.getEndPoint()); + } + } + else + { + // simple edge, append end point + aRetval.append(aSegment.getEndPoint()); + } + + // prepare next edge + aSegment.setStartPoint(aSegment.getEndPoint()); + } + + // copy closed flag and check for double points + aRetval.setClosed(rCandidate.isClosed()); + aRetval.removeDoublePoints(); + + return aRetval; + } + else + { + return rCandidate; + } + } + // #i76891# B2DPolygon simplifyCurveSegments(const B2DPolygon& rCandidate) { @@ -2745,6 +3345,167 @@ namespace basegfx return rCandidate; } + B2DPolygon createEdgesOfGivenLength(const B2DPolygon& rCandidate, double fLength, double fStart, double fEnd) + { + B2DPolygon aRetval; + + if(fLength < 0.0) + { + fLength = 0.0; + } + + if(!fTools::equalZero(fLength)) + { + if(fStart < 0.0) + { + fStart = 0.0; + } + + if(fEnd < 0.0) + { + fEnd = 0.0; + } + + if(fEnd < fStart) + { + fEnd = fStart; + } + + // iterate and consume pieces with fLength. First subdivide to reduce input to line segments + const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); + const sal_uInt32 nPointCount(aCandidate.count()); + + if(nPointCount > 1) + { + const bool bEndActive(!fTools::equalZero(fEnd)); + const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1); + B2DPoint aCurrent(aCandidate.getB2DPoint(0)); + double fPositionInEdge(fStart); + double fAbsolutePosition(fStart); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex)); + const B2DVector aEdge(aNext - aCurrent); + double fEdgeLength(aEdge.getLength()); + + if(!fTools::equalZero(fEdgeLength)) + { + while(fTools::less(fPositionInEdge, fEdgeLength)) + { + // move position on edge forward as long as on edge + const double fScalar(fPositionInEdge / fEdgeLength); + aRetval.append(aCurrent + (aEdge * fScalar)); + fPositionInEdge += fLength; + + if(bEndActive) + { + fAbsolutePosition += fLength; + + if(fTools::more(fAbsolutePosition, fEnd)) + { + break; + } + } + } + + // substract length of current edge + fPositionInEdge -= fEdgeLength; + } + + if(bEndActive && fTools::more(fAbsolutePosition, fEnd)) + { + break; + } + + // prepare next step + aCurrent = aNext; + } + + // keep closed state + aRetval.setClosed(aCandidate.isClosed()); + } + else + { + // source polygon has only one point, return unchanged + aRetval = aCandidate; + } + } + + return aRetval; + } + + B2DPolygon createWaveline(const B2DPolygon& rCandidate, double fWaveWidth, double fWaveHeight) + { + B2DPolygon aRetval; + + if(fWaveWidth < 0.0) + { + fWaveWidth = 0.0; + } + + if(fWaveHeight < 0.0) + { + fWaveHeight = 0.0; + } + + const bool bHasWidth(!fTools::equalZero(fWaveWidth)); + const bool bHasHeight(!fTools::equalZero(fWaveHeight)); + + if(bHasWidth) + { + if(bHasHeight) + { + // width and height, create waveline. First subdivide to reduce input to line segments + // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it + // may be added here again using the original last point from rCandidate. It may + // also be the case that rCandidate was closed. To simplify things it is handled here + // as if it was opened. + // Result from createEdgesOfGivenLength contains no curved segments, handle as straight + // edges. + const B2DPolygon aEqualLenghEdges(createEdgesOfGivenLength(rCandidate, fWaveWidth)); + const sal_uInt32 nPointCount(aEqualLenghEdges.count()); + + if(nPointCount > 1) + { + // iterate over straight edges, add start point + B2DPoint aCurrent(aEqualLenghEdges.getB2DPoint(0)); + aRetval.append(aCurrent); + + for(sal_uInt32 a(0); a < nPointCount - 1; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B2DPoint aNext(aEqualLenghEdges.getB2DPoint(nNextIndex)); + const B2DVector aEdge(aNext - aCurrent); + const B2DVector aPerpendicular(getNormalizedPerpendicular(aEdge)); + const B2DVector aControlOffset((aEdge * 0.467308) - (aPerpendicular * fWaveHeight)); + + // add curve segment + aRetval.appendBezierSegment( + aCurrent + aControlOffset, + aNext - aControlOffset, + aNext); + + // prepare next step + aCurrent = aNext; + } + } + } + else + { + // width but no height -> return original polygon + aRetval = rCandidate; + } + } + else + { + // no width -> no waveline, stay empty and return + } + + return aRetval; + } + ////////////////////////////////////////////////////////////////////// // comparators with tolerance for 2D Polygons |