summaryrefslogtreecommitdiff
path: root/basegfx/source/polygon
diff options
context:
space:
mode:
Diffstat (limited to 'basegfx/source/polygon')
-rw-r--r--basegfx/source/polygon/b2dlinegeometry.cxx725
-rw-r--r--basegfx/source/polygon/b2dpolygon.cxx1592
-rw-r--r--basegfx/source/polygon/b2dpolygonclipper.cxx873
-rw-r--r--basegfx/source/polygon/b2dpolygoncutandtouch.cxx1209
-rw-r--r--basegfx/source/polygon/b2dpolygontools.cxx3594
-rw-r--r--basegfx/source/polygon/b2dpolygontriangulator.cxx466
-rw-r--r--basegfx/source/polygon/b2dpolypolygon.cxx380
-rw-r--r--basegfx/source/polygon/b2dpolypolygoncutter.cxx1014
-rw-r--r--basegfx/source/polygon/b2dpolypolygonrasterconverter.cxx702
-rw-r--r--basegfx/source/polygon/b2dpolypolygontools.cxx585
-rw-r--r--basegfx/source/polygon/b2dsvgpolypolygon.cxx1105
-rw-r--r--basegfx/source/polygon/b3dgeometry.cxx55
-rw-r--r--basegfx/source/polygon/b3dpolygon.cxx1816
-rw-r--r--basegfx/source/polygon/b3dpolygonclipper.cxx574
-rw-r--r--basegfx/source/polygon/b3dpolygontools.cxx1263
-rw-r--r--basegfx/source/polygon/b3dpolypolygon.cxx446
-rw-r--r--basegfx/source/polygon/b3dpolypolygontools.cxx556
-rw-r--r--basegfx/source/polygon/makefile.mk62
18 files changed, 17017 insertions, 0 deletions
diff --git a/basegfx/source/polygon/b2dlinegeometry.cxx b/basegfx/source/polygon/b2dlinegeometry.cxx
new file mode 100644
index 000000000000..0db5efbfb86d
--- /dev/null
+++ b/basegfx/source/polygon/b2dlinegeometry.cxx
@@ -0,0 +1,725 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <cstdio>
+#include <osl/diagnose.h>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace tools
+ {
+ B2DPolyPolygon createAreaGeometryForLineStartEnd(
+ const B2DPolygon& rCandidate,
+ const B2DPolyPolygon& rArrow,
+ bool bStart,
+ double fWidth,
+ double fCandidateLength,
+ double fDockingPosition, // 0->top, 1->bottom
+ double* pConsumedLength)
+ {
+ B2DPolyPolygon aRetval;
+ OSL_ENSURE(rCandidate.count() > 1L, "createAreaGeometryForLineStartEnd: Line polygon has too less points (!)");
+ OSL_ENSURE(rArrow.count() > 0L, "createAreaGeometryForLineStartEnd: Empty arrow PolyPolygon (!)");
+ OSL_ENSURE(fWidth > 0.0, "createAreaGeometryForLineStartEnd: Width too small (!)");
+ OSL_ENSURE(fDockingPosition >= 0.0 && fDockingPosition <= 1.0,
+ "createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0] (!)");
+
+ if(fWidth < 0.0)
+ {
+ fWidth = -fWidth;
+ }
+
+ if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth))
+ {
+ if(fDockingPosition < 0.0)
+ {
+ fDockingPosition = 0.0;
+ }
+ else if(fDockingPosition > 1.0)
+ {
+ fDockingPosition = 1.0;
+ }
+
+ // init return value from arrow
+ aRetval.append(rArrow);
+
+ // get size of the arrow
+ const B2DRange aArrowSize(getRange(rArrow));
+
+ // build ArrowTransform; center in X, align with axis in Y
+ B2DHomMatrix aArrowTransform(basegfx::tools::createTranslateB2DHomMatrix(
+ -aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY()));
+
+ // scale to target size
+ const double fArrowScale(fWidth / (aArrowSize.getRange().getX()));
+ aArrowTransform.scale(fArrowScale, fArrowScale);
+
+ // get arrow size in Y
+ B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY());
+ aUpperCenter *= aArrowTransform;
+ const double fArrowYLength(B2DVector(aUpperCenter).getLength());
+
+ // move arrow to have docking position centered
+ aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition);
+
+ // prepare polygon length
+ if(fTools::equalZero(fCandidateLength))
+ {
+ fCandidateLength = getLength(rCandidate);
+ }
+
+ // get the polygon vector we want to plant this arrow on
+ const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition));
+ const B2DVector aHead(rCandidate.getB2DPoint((bStart) ? 0L : rCandidate.count() - 1L));
+ const B2DVector aTail(getPositionAbsolute(rCandidate,
+ (bStart) ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength));
+
+ // from that vector, take the needed rotation and add rotate for arrow to transformation
+ const B2DVector aTargetDirection(aHead - aTail);
+ const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + (90.0 * F_PI180));
+
+ // rotate around docking position
+ aArrowTransform.rotate(fRotation);
+
+ // move arrow docking position to polygon head
+ aArrowTransform.translate(aHead.getX(), aHead.getY());
+
+ // transform retval and close
+ aRetval.transform(aArrowTransform);
+ aRetval.setClosed(true);
+
+ // if pConsumedLength is asked for, fill it
+ if(pConsumedLength)
+ {
+ *pConsumedLength = fConsumedLength;
+ }
+ }
+
+ return aRetval;
+ }
+ } // end of namespace tools
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ // anonymus namespace for local helpers
+ namespace
+ {
+ bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
+ {
+ // isBezier() is true, already tested by caller
+ const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint());
+
+ if(aEdge.equalZero())
+ {
+ // start and end point the same, but control vectors used -> baloon curve loop
+ // is not a simple edge
+ return false;
+ }
+
+ // get tangentA and scalar with edge
+ const B2DVector aTangentA(rCandidate.getTangent(0.0));
+ const double fScalarAE(aEdge.scalar(aTangentA));
+
+ if(fTools::lessOrEqual(fScalarAE, 0.0))
+ {
+ // angle between TangentA and Edge is bigger or equal 90 degrees
+ return false;
+ }
+
+ // get self-scalars for E and A
+ const double fScalarE(aEdge.scalar(aEdge));
+ const double fScalarA(aTangentA.scalar(aTangentA));
+ const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad);
+
+ if(fTools::moreOrEqual(fScalarA, fLengthCompareE))
+ {
+ // length of TangentA is more than fMaxPartOfEdge of length of edge
+ return false;
+ }
+
+ if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad))
+ {
+ // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
+ return false;
+ }
+
+ // get tangentB and scalar with edge
+ const B2DVector aTangentB(rCandidate.getTangent(1.0));
+ const double fScalarBE(aEdge.scalar(aTangentB));
+
+ if(fTools::lessOrEqual(fScalarBE, 0.0))
+ {
+ // angle between TangentB and Edge is bigger or equal 90 degrees
+ return false;
+ }
+
+ // get self-scalar for B
+ const double fScalarB(aTangentB.scalar(aTangentB));
+
+ if(fTools::moreOrEqual(fScalarB, fLengthCompareE))
+ {
+ // length of TangentB is more than fMaxPartOfEdge of length of edge
+ return false;
+ }
+
+ if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad))
+ {
+ // angle between TangentB and Edge is bigger or equal defined by fMaxCos
+ return false;
+ }
+
+ return true;
+ }
+
+ void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth)
+ {
+ if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad))
+ {
+ rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint());
+ }
+ else
+ {
+ B2DCubicBezier aLeft, aRight;
+ rCandidate.split(0.5, &aLeft, &aRight);
+
+ impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
+ impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
+ }
+ }
+
+ B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(rCandidate.areControlPointsUsed() && nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DPolygon aRetval;
+ B2DCubicBezier aEdge;
+
+ // prepare edge for loop
+ aEdge.setStartPoint(rCandidate.getB2DPoint(0));
+ aRetval.append(aEdge.getStartPoint());
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // fill B2DCubicBezier
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
+ aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+
+ // get rid of unnecessary bezier segments
+ aEdge.testAndSolveTrivialBezier();
+
+ if(aEdge.isBezier())
+ {
+ // before splitting recursively with internal simple criteria, use
+ // ExtremumPosFinder to remove those
+ ::std::vector< double > aExtremumPositions;
+
+ aExtremumPositions.reserve(4);
+ aEdge.getAllExtremumPositions(aExtremumPositions);
+
+ const sal_uInt32 nCount(aExtremumPositions.size());
+
+ if(nCount)
+ {
+ if(nCount > 1)
+ {
+ // create order from left to right
+ ::std::sort(aExtremumPositions.begin(), aExtremumPositions.end());
+ }
+
+ for(sal_uInt32 b(0); b < nCount;)
+ {
+ // split aEdge at next split pos
+ B2DCubicBezier aLeft;
+ const double fSplitPos(aExtremumPositions[b++]);
+
+ aEdge.split(fSplitPos, &aLeft, &aEdge);
+ aLeft.testAndSolveTrivialBezier();
+
+ // consume left part
+ if(aLeft.isBezier())
+ {
+ impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
+ }
+ else
+ {
+ aRetval.append(aLeft.getEndPoint());
+ }
+
+ if(b < nCount)
+ {
+ // correct the remaining split positions to fit to shortened aEdge
+ const double fScaleFactor(1.0 / (1.0 - fSplitPos));
+
+ for(sal_uInt32 c(b); c < nCount; c++)
+ {
+ aExtremumPositions[c] = (aExtremumPositions[c] - fSplitPos) * fScaleFactor;
+ }
+ }
+ }
+
+ // test the shortened rest of aEdge
+ aEdge.testAndSolveTrivialBezier();
+
+ // consume right part
+ if(aEdge.isBezier())
+ {
+ impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
+ }
+ else
+ {
+ aRetval.append(aEdge.getEndPoint());
+ }
+ }
+ else
+ {
+ impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
+ }
+ }
+ else
+ {
+ // straight edge, add point
+ aRetval.append(aEdge.getEndPoint());
+ }
+
+ // prepare edge for next step
+ aEdge.setStartPoint(aEdge.getEndPoint());
+ }
+
+ // copy closed flag and check for double points
+ aRetval.setClosed(rCandidate.isClosed());
+ aRetval.removeDoublePoints();
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolygon createAreaGeometryForEdge(const B2DCubicBezier& rEdge, double fHalfLineWidth)
+ {
+ // create polygon for edge
+ // Unfortunately, while it would be geometrically correct to not add
+ // the in-between points EdgeEnd and EdgeStart, it leads to rounding
+ // errors when converting to integer polygon coordinates for painting
+ if(rEdge.isBezier())
+ {
+ // prepare target and data common for upper and lower
+ B2DPolygon aBezierPolygon;
+ const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint());
+ const double fEdgeLength(aPureEdgeVector.getLength());
+ const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength));
+ const B2DVector aTangentA(rEdge.getTangent(0.0));
+ const B2DVector aTangentB(rEdge.getTangent(1.0));
+
+ // create upper edge.
+ {
+ // create displacement vectors and check if they cut
+ const B2DVector aPerpendStart(getNormalizedPerpendicular(aTangentA) * -fHalfLineWidth);
+ const B2DVector aPerpendEnd(getNormalizedPerpendicular(aTangentB) * -fHalfLineWidth);
+ double fCut(0.0);
+ const tools::CutFlagValue aCut(tools::findCut(
+ rEdge.getStartPoint(), aPerpendStart,
+ rEdge.getEndPoint(), aPerpendEnd,
+ CUTFLAG_ALL, &fCut));
+
+ if(CUTFLAG_NONE != aCut)
+ {
+ // calculate cut point and add
+ const B2DPoint aCutPoint(rEdge.getStartPoint() + (aPerpendStart * fCut));
+ aBezierPolygon.append(aCutPoint);
+ }
+ else
+ {
+ // create scaled bezier segment
+ const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStart);
+ const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEnd);
+ const B2DVector aEdge(aEnd - aStart);
+ const double fLength(aEdge.getLength());
+ const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
+ const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint());
+ const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint());
+
+ aBezierPolygon.append(aStart);
+ aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
+ }
+ }
+
+ // append original in-between point
+ aBezierPolygon.append(rEdge.getEndPoint());
+
+ // create lower edge.
+ {
+ // create displacement vectors and check if they cut
+ const B2DVector aPerpendStart(getNormalizedPerpendicular(aTangentA) * fHalfLineWidth);
+ const B2DVector aPerpendEnd(getNormalizedPerpendicular(aTangentB) * fHalfLineWidth);
+ double fCut(0.0);
+ const tools::CutFlagValue aCut(tools::findCut(
+ rEdge.getEndPoint(), aPerpendEnd,
+ rEdge.getStartPoint(), aPerpendStart,
+ CUTFLAG_ALL, &fCut));
+
+ if(CUTFLAG_NONE != aCut)
+ {
+ // calculate cut point and add
+ const B2DPoint aCutPoint(rEdge.getEndPoint() + (aPerpendEnd * fCut));
+ aBezierPolygon.append(aCutPoint);
+ }
+ else
+ {
+ // create scaled bezier segment
+ const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEnd);
+ const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStart);
+ const B2DVector aEdge(aEnd - aStart);
+ const double fLength(aEdge.getLength());
+ const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
+ const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint());
+ const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint());
+
+ aBezierPolygon.append(aStart);
+ aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
+ }
+ }
+
+ // append original in-between point
+ aBezierPolygon.append(rEdge.getStartPoint());
+
+ // close and return
+ aBezierPolygon.setClosed(true);
+ return aBezierPolygon;
+ }
+ else
+ {
+ // #i101491# emulate rEdge.getTangent call which applies a factor of 0.3 to the
+ // full-length edge vector to have numerically exactly the same results as in the
+ // createAreaGeometryForJoin implementation
+ const B2DVector aEdgeTangent((rEdge.getEndPoint() - rEdge.getStartPoint()) * 0.3);
+ const B2DVector aPerpendEdgeVector(getNormalizedPerpendicular(aEdgeTangent) * fHalfLineWidth);
+ B2DPolygon aEdgePolygon;
+
+ // create upper edge
+ aEdgePolygon.append(rEdge.getStartPoint() - aPerpendEdgeVector);
+ aEdgePolygon.append(rEdge.getEndPoint() - aPerpendEdgeVector);
+
+ // append original in-between point
+ aEdgePolygon.append(rEdge.getEndPoint());
+
+ // create lower edge
+ aEdgePolygon.append(rEdge.getEndPoint() + aPerpendEdgeVector);
+ aEdgePolygon.append(rEdge.getStartPoint() + aPerpendEdgeVector);
+
+ // append original in-between point
+ aEdgePolygon.append(rEdge.getStartPoint());
+
+ // close and return
+ aEdgePolygon.setClosed(true);
+ return aEdgePolygon;
+ }
+ }
+
+ B2DPolygon createAreaGeometryForJoin(
+ const B2DVector& rTangentPrev,
+ const B2DVector& rTangentEdge,
+ const B2DVector& rPerpendPrev,
+ const B2DVector& rPerpendEdge,
+ const B2DPoint& rPoint,
+ double fHalfLineWidth,
+ B2DLineJoin eJoin,
+ double fMiterMinimumAngle)
+ {
+ OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForJoin: LineWidth too small (!)");
+ OSL_ENSURE(B2DLINEJOIN_NONE != eJoin, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)");
+
+ // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint
+ B2DPolygon aEdgePolygon;
+ const B2DPoint aStartPoint(rPoint + rPerpendPrev);
+ const B2DPoint aEndPoint(rPoint + rPerpendEdge);
+
+ // test if for Miter, the angle is too small and the fallback
+ // to bevel needs to be used
+ if(B2DLINEJOIN_MITER == eJoin)
+ {
+ const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge)));
+
+ if((F_PI - fAngle) < fMiterMinimumAngle)
+ {
+ // fallback to bevel
+ eJoin = B2DLINEJOIN_BEVEL;
+ }
+ }
+
+ switch(eJoin)
+ {
+ case B2DLINEJOIN_MITER :
+ {
+ aEdgePolygon.append(aEndPoint);
+ aEdgePolygon.append(rPoint);
+ aEdgePolygon.append(aStartPoint);
+
+ // Look for the cut point between start point along rTangentPrev and
+ // end point along rTangentEdge. -rTangentEdge should be used, but since
+ // the cut value is used for interpolating along the first edge, the negation
+ // is not needed since the same fCut will be found on the first edge.
+ // If it exists, insert it to complete the mitered fill polygon.
+ double fCutPos(0.0);
+ tools::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CUTFLAG_ALL, &fCutPos);
+
+ if(0.0 != fCutPos)
+ {
+ const B2DPoint aCutPoint(interpolate(aStartPoint, aStartPoint + rTangentPrev, fCutPos));
+ aEdgePolygon.append(aCutPoint);
+ }
+
+ break;
+ }
+ case B2DLINEJOIN_ROUND :
+ {
+ // use tooling to add needed EllipseSegment
+ double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX()));
+ double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX()));
+
+ // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
+ if(fAngleStart < 0.0)
+ {
+ fAngleStart += F_2PI;
+ }
+
+ if(fAngleEnd < 0.0)
+ {
+ fAngleEnd += F_2PI;
+ }
+
+ const B2DPolygon aBow(tools::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd));
+
+ if(aBow.count() > 1)
+ {
+ // #i101491#
+ // use the original start/end positions; the ones from bow creation may be numerically
+ // different due to their different creation. To guarantee good merging quality with edges
+ // and edge roundings (and to reduce point count)
+ aEdgePolygon = aBow;
+ aEdgePolygon.setB2DPoint(0, aStartPoint);
+ aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint);
+ aEdgePolygon.append(rPoint);
+
+ break;
+ }
+ else
+ {
+ // wanted fall-through to default
+ }
+ }
+ default: // B2DLINEJOIN_BEVEL
+ {
+ aEdgePolygon.append(aEndPoint);
+ aEdgePolygon.append(rPoint);
+ aEdgePolygon.append(aStartPoint);
+
+ break;
+ }
+ }
+
+ // create last polygon part for edge
+ aEdgePolygon.setClosed(true);
+
+ return aEdgePolygon;
+ }
+ } // end of anonymus namespace
+
+ namespace tools
+ {
+ B2DPolyPolygon createAreaGeometry(
+ const B2DPolygon& rCandidate,
+ double fHalfLineWidth,
+ B2DLineJoin eJoin,
+ double fMaxAllowedAngle,
+ double fMaxPartOfEdge,
+ double fMiterMinimumAngle)
+ {
+ if(fMaxAllowedAngle > F_PI2)
+ {
+ fMaxAllowedAngle = F_PI2;
+ }
+ else if(fMaxAllowedAngle < 0.01 * F_PI2)
+ {
+ fMaxAllowedAngle = 0.01 * F_PI2;
+ }
+
+ if(fMaxPartOfEdge > 1.0)
+ {
+ fMaxPartOfEdge = 1.0;
+ }
+ else if(fMaxPartOfEdge < 0.01)
+ {
+ fMaxPartOfEdge = 0.01;
+ }
+
+ if(fMiterMinimumAngle > F_PI)
+ {
+ fMiterMinimumAngle = F_PI;
+ }
+ else if(fMiterMinimumAngle < 0.01 * F_PI)
+ {
+ fMiterMinimumAngle = 0.01 * F_PI;
+ }
+
+ B2DPolygon aCandidate(rCandidate);
+ const double fMaxCos(cos(fMaxAllowedAngle));
+
+ aCandidate.removeDoublePoints();
+ aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge);
+
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(nPointCount)
+ {
+ B2DPolyPolygon aRetval;
+ const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE != eJoin);
+ const bool bIsClosed(aCandidate.isClosed());
+ const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
+
+ if(nEdgeCount)
+ {
+ B2DCubicBezier aEdge;
+ B2DCubicBezier aPrev;
+
+ // prepare edge
+ aEdge.setStartPoint(aCandidate.getB2DPoint(0));
+
+ if(bIsClosed && bEventuallyCreateLineJoin)
+ {
+ // prepare previous edge
+ const sal_uInt32 nPrevIndex(nPointCount - 1);
+ aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex));
+ aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex));
+ aPrev.setControlPointB(aCandidate.getPrevControlPoint(0));
+ aPrev.setEndPoint(aEdge.getStartPoint());
+ }
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // fill current Edge
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aEdge.setControlPointA(aCandidate.getNextControlPoint(a));
+ aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex));
+ aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex));
+
+ // check and create linejoin
+ if(bEventuallyCreateLineJoin && (bIsClosed || 0 != a))
+ {
+ const B2DVector aTangentPrev(aPrev.getTangent(1.0));
+ const B2DVector aTangentEdge(aEdge.getTangent(0.0));
+ B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge));
+
+ if(ORIENTATION_NEUTRAL == aOrientation)
+ {
+ // they are parallell or empty; if they are both not zero and point
+ // in opposite direction, a half-circle is needed
+ if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero())
+ {
+ const double fAngle(fabs(aTangentPrev.angle(aTangentEdge)));
+
+ if(fTools::equal(fAngle, F_PI))
+ {
+ // for half-circle production, fallback to positive
+ // orientation
+ aOrientation = ORIENTATION_POSITIVE;
+ }
+ }
+ }
+
+ if(ORIENTATION_POSITIVE == aOrientation)
+ {
+ const B2DVector aPerpendPrev(getNormalizedPerpendicular(aTangentPrev) * -fHalfLineWidth);
+ const B2DVector aPerpendEdge(getNormalizedPerpendicular(aTangentEdge) * -fHalfLineWidth);
+
+ aRetval.append(createAreaGeometryForJoin(
+ aTangentPrev, aTangentEdge,
+ aPerpendPrev, aPerpendEdge,
+ aEdge.getStartPoint(), fHalfLineWidth,
+ eJoin, fMiterMinimumAngle));
+ }
+ else if(ORIENTATION_NEGATIVE == aOrientation)
+ {
+ const B2DVector aPerpendPrev(getNormalizedPerpendicular(aTangentPrev) * fHalfLineWidth);
+ const B2DVector aPerpendEdge(getNormalizedPerpendicular(aTangentEdge) * fHalfLineWidth);
+
+ aRetval.append(createAreaGeometryForJoin(
+ aTangentEdge, aTangentPrev,
+ aPerpendEdge, aPerpendPrev,
+ aEdge.getStartPoint(), fHalfLineWidth,
+ eJoin, fMiterMinimumAngle));
+ }
+ }
+
+ // create geometry for edge
+ aRetval.append(createAreaGeometryForEdge(aEdge, fHalfLineWidth));
+
+ // prepare next step
+ if(bEventuallyCreateLineJoin)
+ {
+ aPrev = aEdge;
+ }
+
+ aEdge.setStartPoint(aEdge.getEndPoint());
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return B2DPolyPolygon(rCandidate);
+ }
+ }
+ } // end of namespace tools
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+// eof
diff --git a/basegfx/source/polygon/b2dpolygon.cxx b/basegfx/source/polygon/b2dpolygon.cxx
new file mode 100644
index 000000000000..dc16938a3f99
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolygon.cxx
@@ -0,0 +1,1592 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <osl/diagnose.h>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <rtl/instance.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <boost/scoped_ptr.hpp>
+#include <vector>
+#include <algorithm>
+
+//////////////////////////////////////////////////////////////////////////////
+
+class CoordinateData2D
+{
+ basegfx::B2DPoint maPoint;
+
+public:
+ CoordinateData2D()
+ : maPoint()
+ {}
+
+ explicit CoordinateData2D(const basegfx::B2DPoint& rData)
+ : maPoint(rData)
+ {}
+
+ const basegfx::B2DPoint& getCoordinate() const
+ {
+ return maPoint;
+ }
+
+ void setCoordinate(const basegfx::B2DPoint& rValue)
+ {
+ if(rValue != maPoint)
+ maPoint = rValue;
+ }
+
+ bool operator==(const CoordinateData2D& rData ) const
+ {
+ return (maPoint == rData.getCoordinate());
+ }
+
+ void transform(const basegfx::B2DHomMatrix& rMatrix)
+ {
+ maPoint *= rMatrix;
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class CoordinateDataArray2D
+{
+ typedef ::std::vector< CoordinateData2D > CoordinateData2DVector;
+
+ CoordinateData2DVector maVector;
+
+public:
+ explicit CoordinateDataArray2D(sal_uInt32 nCount)
+ : maVector(nCount)
+ {
+ }
+
+ explicit CoordinateDataArray2D(const CoordinateDataArray2D& rOriginal)
+ : maVector(rOriginal.maVector)
+ {
+ }
+
+ CoordinateDataArray2D(const CoordinateDataArray2D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maVector(rOriginal.maVector.begin() + nIndex, rOriginal.maVector.begin() + (nIndex + nCount))
+ {
+ }
+
+ sal_uInt32 count() const
+ {
+ return maVector.size();
+ }
+
+ bool operator==(const CoordinateDataArray2D& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ const basegfx::B2DPoint& getCoordinate(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex].getCoordinate();
+ }
+
+ void setCoordinate(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue)
+ {
+ maVector[nIndex].setCoordinate(rValue);
+ }
+
+ void reserve(sal_uInt32 nCount)
+ {
+ maVector.reserve(nCount);
+ }
+
+ void append(const CoordinateData2D& rValue)
+ {
+ maVector.push_back(rValue);
+ }
+
+ void insert(sal_uInt32 nIndex, const CoordinateData2D& rValue, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rValue
+ CoordinateData2DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ maVector.insert(aIndex, nCount, rValue);
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const CoordinateDataArray2D& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maVector.size());
+
+ if(nCount)
+ {
+ // insert data
+ CoordinateData2DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ CoordinateData2DVector::const_iterator aStart(rSource.maVector.begin());
+ CoordinateData2DVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // remove point data
+ CoordinateData2DVector::iterator aStart(maVector.begin());
+ aStart += nIndex;
+ const CoordinateData2DVector::iterator aEnd(aStart + nCount);
+ maVector.erase(aStart, aEnd);
+ }
+ }
+
+ void flip(bool bIsClosed)
+ {
+ if(maVector.size() > 1)
+ {
+ // to keep the same point at index 0, just flip all points except the
+ // first one when closed
+ const sal_uInt32 nHalfSize(bIsClosed ? (maVector.size() - 1) >> 1 : maVector.size() >> 1);
+ CoordinateData2DVector::iterator aStart(bIsClosed ? maVector.begin() + 1 : maVector.begin());
+ CoordinateData2DVector::iterator aEnd(maVector.end() - 1);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ ::std::swap(*aStart, *aEnd);
+ aStart++;
+ aEnd--;
+ }
+ }
+ }
+
+ void removeDoublePointsAtBeginEnd()
+ {
+ // remove from end as long as there are at least two points
+ // and begin/end are equal
+ while((maVector.size() > 1) && (maVector[0] == maVector[maVector.size() - 1]))
+ {
+ maVector.pop_back();
+ }
+ }
+
+ void removeDoublePointsWholeTrack()
+ {
+ sal_uInt32 nIndex(0);
+
+ // test as long as there are at least two points and as long as the index
+ // is smaller or equal second last point
+ while((maVector.size() > 1) && (nIndex <= maVector.size() - 2))
+ {
+ if(maVector[nIndex] == maVector[nIndex + 1])
+ {
+ // if next is same as index, delete next
+ maVector.erase(maVector.begin() + (nIndex + 1));
+ }
+ else
+ {
+ // if different, step forward
+ nIndex++;
+ }
+ }
+ }
+
+ void transform(const basegfx::B2DHomMatrix& rMatrix)
+ {
+ CoordinateData2DVector::iterator aStart(maVector.begin());
+ CoordinateData2DVector::iterator aEnd(maVector.end());
+
+ for(; aStart != aEnd; aStart++)
+ {
+ aStart->transform(rMatrix);
+ }
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class ControlVectorPair2D
+{
+ basegfx::B2DVector maPrevVector;
+ basegfx::B2DVector maNextVector;
+
+public:
+ const basegfx::B2DVector& getPrevVector() const
+ {
+ return maPrevVector;
+ }
+
+ void setPrevVector(const basegfx::B2DVector& rValue)
+ {
+ if(rValue != maPrevVector)
+ maPrevVector = rValue;
+ }
+
+ const basegfx::B2DVector& getNextVector() const
+ {
+ return maNextVector;
+ }
+
+ void setNextVector(const basegfx::B2DVector& rValue)
+ {
+ if(rValue != maNextVector)
+ maNextVector = rValue;
+ }
+
+ bool operator==(const ControlVectorPair2D& rData) const
+ {
+ return (maPrevVector == rData.getPrevVector() && maNextVector == rData.getNextVector());
+ }
+
+ void flip()
+ {
+ ::std::swap(maPrevVector, maNextVector);
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class ControlVectorArray2D
+{
+ typedef ::std::vector< ControlVectorPair2D > ControlVectorPair2DVector;
+
+ ControlVectorPair2DVector maVector;
+ sal_uInt32 mnUsedVectors;
+
+public:
+ explicit ControlVectorArray2D(sal_uInt32 nCount)
+ : maVector(nCount),
+ mnUsedVectors(0)
+ {}
+
+ ControlVectorArray2D(const ControlVectorArray2D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maVector(),
+ mnUsedVectors(0)
+ {
+ ControlVectorPair2DVector::const_iterator aStart(rOriginal.maVector.begin());
+ aStart += nIndex;
+ ControlVectorPair2DVector::const_iterator aEnd(aStart);
+ aEnd += nCount;
+ maVector.reserve(nCount);
+
+ for(; aStart != aEnd; aStart++)
+ {
+ if(!aStart->getPrevVector().equalZero())
+ mnUsedVectors++;
+
+ if(!aStart->getNextVector().equalZero())
+ mnUsedVectors++;
+
+ maVector.push_back(*aStart);
+ }
+ }
+
+ sal_uInt32 count() const
+ {
+ return maVector.size();
+ }
+
+ bool operator==(const ControlVectorArray2D& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ bool isUsed() const
+ {
+ return (0 != mnUsedVectors);
+ }
+
+ const basegfx::B2DVector& getPrevVector(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex].getPrevVector();
+ }
+
+ void setPrevVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue)
+ {
+ bool bWasUsed(mnUsedVectors && !maVector[nIndex].getPrevVector().equalZero());
+ bool bIsUsed(!rValue.equalZero());
+
+ if(bWasUsed)
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex].setPrevVector(rValue);
+ }
+ else
+ {
+ maVector[nIndex].setPrevVector(basegfx::B2DVector::getEmptyVector());
+ mnUsedVectors--;
+ }
+ }
+ else
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex].setPrevVector(rValue);
+ mnUsedVectors++;
+ }
+ }
+ }
+
+ const basegfx::B2DVector& getNextVector(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex].getNextVector();
+ }
+
+ void setNextVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue)
+ {
+ bool bWasUsed(mnUsedVectors && !maVector[nIndex].getNextVector().equalZero());
+ bool bIsUsed(!rValue.equalZero());
+
+ if(bWasUsed)
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex].setNextVector(rValue);
+ }
+ else
+ {
+ maVector[nIndex].setNextVector(basegfx::B2DVector::getEmptyVector());
+ mnUsedVectors--;
+ }
+ }
+ else
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex].setNextVector(rValue);
+ mnUsedVectors++;
+ }
+ }
+ }
+
+ void append(const ControlVectorPair2D& rValue)
+ {
+ maVector.push_back(rValue);
+
+ if(!rValue.getPrevVector().equalZero())
+ mnUsedVectors += 1;
+
+ if(!rValue.getNextVector().equalZero())
+ mnUsedVectors += 1;
+ }
+
+ void insert(sal_uInt32 nIndex, const ControlVectorPair2D& rValue, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rValue
+ ControlVectorPair2DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ maVector.insert(aIndex, nCount, rValue);
+
+ if(!rValue.getPrevVector().equalZero())
+ mnUsedVectors += nCount;
+
+ if(!rValue.getNextVector().equalZero())
+ mnUsedVectors += nCount;
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ControlVectorArray2D& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maVector.size());
+
+ if(nCount)
+ {
+ // insert data
+ ControlVectorPair2DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ ControlVectorPair2DVector::const_iterator aStart(rSource.maVector.begin());
+ ControlVectorPair2DVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+
+ for(; aStart != aEnd; aStart++)
+ {
+ if(!aStart->getPrevVector().equalZero())
+ mnUsedVectors++;
+
+ if(!aStart->getNextVector().equalZero())
+ mnUsedVectors++;
+ }
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ const ControlVectorPair2DVector::iterator aDeleteStart(maVector.begin() + nIndex);
+ const ControlVectorPair2DVector::iterator aDeleteEnd(aDeleteStart + nCount);
+ ControlVectorPair2DVector::const_iterator aStart(aDeleteStart);
+
+ for(; mnUsedVectors && aStart != aDeleteEnd; aStart++)
+ {
+ if(!aStart->getPrevVector().equalZero())
+ mnUsedVectors--;
+
+ if(mnUsedVectors && !aStart->getNextVector().equalZero())
+ mnUsedVectors--;
+ }
+
+ // remove point data
+ maVector.erase(aDeleteStart, aDeleteEnd);
+ }
+ }
+
+ void flip(bool bIsClosed)
+ {
+ if(maVector.size() > 1)
+ {
+ // to keep the same point at index 0, just flip all points except the
+ // first one when closed
+ const sal_uInt32 nHalfSize(bIsClosed ? (maVector.size() - 1) >> 1 : maVector.size() >> 1);
+ ControlVectorPair2DVector::iterator aStart(bIsClosed ? maVector.begin() + 1 : maVector.begin());
+ ControlVectorPair2DVector::iterator aEnd(maVector.end() - 1);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ // swap Prev and Next
+ aStart->flip();
+ aEnd->flip();
+
+ // swap entries
+ ::std::swap(*aStart, *aEnd);
+
+ aStart++;
+ aEnd--;
+ }
+
+ if(aStart == aEnd)
+ {
+ // swap Prev and Next at middle element (if exists)
+ aStart->flip();
+ }
+
+ if(bIsClosed)
+ {
+ // swap Prev and Next at start element
+ maVector.begin()->flip();
+ }
+ }
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class ImplBufferedData
+{
+private:
+ // Possibility to hold the last subdivision
+ boost::scoped_ptr< basegfx::B2DPolygon > mpDefaultSubdivision;
+
+ // Possibility to hold the last B2DRange calculation
+ boost::scoped_ptr< basegfx::B2DRange > mpB2DRange;
+
+public:
+ ImplBufferedData()
+ : mpDefaultSubdivision(),
+ mpB2DRange()
+ {}
+
+ const basegfx::B2DPolygon& getDefaultAdaptiveSubdivision(const basegfx::B2DPolygon& rSource) const
+ {
+ if(!mpDefaultSubdivision)
+ {
+ const_cast< ImplBufferedData* >(this)->mpDefaultSubdivision.reset(new basegfx::B2DPolygon(basegfx::tools::adaptiveSubdivideByCount(rSource, 9)));
+ }
+
+ return *mpDefaultSubdivision;
+ }
+
+ const basegfx::B2DRange& getB2DRange(const basegfx::B2DPolygon& rSource) const
+ {
+ if(!mpB2DRange)
+ {
+ basegfx::B2DRange aNewRange;
+ const sal_uInt32 nPointCount(rSource.count());
+
+ if(nPointCount)
+ {
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ aNewRange.expand(rSource.getB2DPoint(a));
+ }
+
+ if(rSource.areControlPointsUsed())
+ {
+ const sal_uInt32 nEdgeCount(rSource.isClosed() ? nPointCount : nPointCount - 1);
+
+ if(nEdgeCount)
+ {
+ basegfx::B2DCubicBezier aEdge;
+ aEdge.setStartPoint(rSource.getB2DPoint(0));
+
+ for(sal_uInt32 b(0); b < nEdgeCount; b++)
+ {
+ const sal_uInt32 nNextIndex((b + 1) % nPointCount);
+ aEdge.setControlPointA(rSource.getNextControlPoint(b));
+ aEdge.setControlPointB(rSource.getPrevControlPoint(nNextIndex));
+ aEdge.setEndPoint(rSource.getB2DPoint(nNextIndex));
+
+ if(aEdge.isBezier())
+ {
+ const basegfx::B2DRange aBezierRangeWithControlPoints(aEdge.getRange());
+
+ if(!aNewRange.isInside(aBezierRangeWithControlPoints))
+ {
+ // the range with control points of the current edge is not completely
+ // inside the current range without control points. Expand current range by
+ // subdividing the bezier segment.
+ // Ideal here is a subdivision at the extreme values, so use
+ // getAllExtremumPositions to get all extremas in one run
+ ::std::vector< double > aExtremas;
+
+ aExtremas.reserve(4);
+ aEdge.getAllExtremumPositions(aExtremas);
+
+ const sal_uInt32 nExtremaCount(aExtremas.size());
+
+ for(sal_uInt32 c(0); c < nExtremaCount; c++)
+ {
+ aNewRange.expand(aEdge.interpolatePoint(aExtremas[c]));
+ }
+ }
+ }
+
+ // prepare next edge
+ aEdge.setStartPoint(aEdge.getEndPoint());
+ }
+ }
+ }
+ }
+
+ const_cast< ImplBufferedData* >(this)->mpB2DRange.reset(new basegfx::B2DRange(aNewRange));
+ }
+
+ return *mpB2DRange;
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class ImplB2DPolygon
+{
+private:
+ // The point vector. This vector exists always and defines the
+ // count of members.
+ CoordinateDataArray2D maPoints;
+
+ // The control point vectors. This vectors are created on demand
+ // and may be zero.
+ boost::scoped_ptr< ControlVectorArray2D > mpControlVector;
+
+ // buffered data for e.g. default subdivision and range
+ boost::scoped_ptr< ImplBufferedData > mpBufferedData;
+
+ // flag which decides if this polygon is opened or closed
+ bool mbIsClosed;
+
+public:
+ const basegfx::B2DPolygon& getDefaultAdaptiveSubdivision(const basegfx::B2DPolygon& rSource) const
+ {
+ if(!mpControlVector || !mpControlVector->isUsed())
+ {
+ return rSource;
+ }
+
+ if(!mpBufferedData)
+ {
+ const_cast< ImplB2DPolygon* >(this)->mpBufferedData.reset(new ImplBufferedData);
+ }
+
+ return mpBufferedData->getDefaultAdaptiveSubdivision(rSource);
+ }
+
+ const basegfx::B2DRange& getB2DRange(const basegfx::B2DPolygon& rSource) const
+ {
+ if(!mpBufferedData)
+ {
+ const_cast< ImplB2DPolygon* >(this)->mpBufferedData.reset(new ImplBufferedData);
+ }
+
+ return mpBufferedData->getB2DRange(rSource);
+ }
+
+ ImplB2DPolygon()
+ : maPoints(0),
+ mpControlVector(),
+ mpBufferedData(),
+ mbIsClosed(false)
+ {}
+
+ ImplB2DPolygon(const ImplB2DPolygon& rToBeCopied)
+ : maPoints(rToBeCopied.maPoints),
+ mpControlVector(),
+ mpBufferedData(),
+ mbIsClosed(rToBeCopied.mbIsClosed)
+ {
+ // complete initialization using copy
+ if(rToBeCopied.mpControlVector && rToBeCopied.mpControlVector->isUsed())
+ {
+ mpControlVector.reset( new ControlVectorArray2D(*rToBeCopied.mpControlVector) );
+ }
+ }
+
+ ImplB2DPolygon(const ImplB2DPolygon& rToBeCopied, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maPoints(rToBeCopied.maPoints, nIndex, nCount),
+ mpControlVector(),
+ mpBufferedData(),
+ mbIsClosed(rToBeCopied.mbIsClosed)
+ {
+ // complete initialization using partly copy
+ if(rToBeCopied.mpControlVector && rToBeCopied.mpControlVector->isUsed())
+ {
+ mpControlVector.reset( new ControlVectorArray2D(*rToBeCopied.mpControlVector, nIndex, nCount) );
+
+ if(!mpControlVector->isUsed())
+ mpControlVector.reset();
+ }
+ }
+
+ ImplB2DPolygon& operator=( const ImplB2DPolygon& rToBeCopied )
+ {
+ maPoints = rToBeCopied.maPoints;
+ mpControlVector.reset();
+ mpBufferedData.reset();
+ mbIsClosed = rToBeCopied.mbIsClosed;
+
+ // complete initialization using copy
+ if(rToBeCopied.mpControlVector && rToBeCopied.mpControlVector->isUsed())
+ mpControlVector.reset( new ControlVectorArray2D(*rToBeCopied.mpControlVector) );
+
+ return *this;
+ }
+
+ sal_uInt32 count() const
+ {
+ return maPoints.count();
+ }
+
+ bool isClosed() const
+ {
+ return mbIsClosed;
+ }
+
+ void setClosed(bool bNew)
+ {
+ if(bNew != mbIsClosed)
+ {
+ mpBufferedData.reset();
+ mbIsClosed = bNew;
+ }
+ }
+
+ bool operator==(const ImplB2DPolygon& rCandidate) const
+ {
+ if(mbIsClosed == rCandidate.mbIsClosed)
+ {
+ if(maPoints == rCandidate.maPoints)
+ {
+ bool bControlVectorsAreEqual(true);
+
+ if(mpControlVector)
+ {
+ if(rCandidate.mpControlVector)
+ {
+ bControlVectorsAreEqual = ((*mpControlVector) == (*rCandidate.mpControlVector));
+ }
+ else
+ {
+ // candidate has no control vector, so it's assumed all unused.
+ bControlVectorsAreEqual = !mpControlVector->isUsed();
+ }
+ }
+ else
+ {
+ if(rCandidate.mpControlVector)
+ {
+ // we have no control vector, so it's assumed all unused.
+ bControlVectorsAreEqual = !rCandidate.mpControlVector->isUsed();
+ }
+ }
+
+ if(bControlVectorsAreEqual)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ const basegfx::B2DPoint& getPoint(sal_uInt32 nIndex) const
+ {
+ return maPoints.getCoordinate(nIndex);
+ }
+
+ void setPoint(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue)
+ {
+ mpBufferedData.reset();
+ maPoints.setCoordinate(nIndex, rValue);
+ }
+
+ void reserve(sal_uInt32 nCount)
+ {
+ maPoints.reserve(nCount);
+ }
+
+ void append(const basegfx::B2DPoint& rPoint)
+ {
+ mpBufferedData.reset(); // TODO: is this needed?
+ const CoordinateData2D aCoordinate(rPoint);
+ maPoints.append(aCoordinate);
+
+ if(mpControlVector)
+ {
+ const ControlVectorPair2D aVectorPair;
+ mpControlVector->append(aVectorPair);
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const basegfx::B2DPoint& rPoint, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ mpBufferedData.reset();
+ CoordinateData2D aCoordinate(rPoint);
+ maPoints.insert(nIndex, aCoordinate, nCount);
+
+ if(mpControlVector)
+ {
+ ControlVectorPair2D aVectorPair;
+ mpControlVector->insert(nIndex, aVectorPair, nCount);
+ }
+ }
+ }
+
+ const basegfx::B2DVector& getPrevControlVector(sal_uInt32 nIndex) const
+ {
+ if(mpControlVector)
+ {
+ return mpControlVector->getPrevVector(nIndex);
+ }
+ else
+ {
+ return basegfx::B2DVector::getEmptyVector();
+ }
+ }
+
+ void setPrevControlVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue)
+ {
+ if(!mpControlVector)
+ {
+ if(!rValue.equalZero())
+ {
+ mpBufferedData.reset();
+ mpControlVector.reset( new ControlVectorArray2D(maPoints.count()) );
+ mpControlVector->setPrevVector(nIndex, rValue);
+ }
+ }
+ else
+ {
+ mpBufferedData.reset();
+ mpControlVector->setPrevVector(nIndex, rValue);
+
+ if(!mpControlVector->isUsed())
+ mpControlVector.reset();
+ }
+ }
+
+ const basegfx::B2DVector& getNextControlVector(sal_uInt32 nIndex) const
+ {
+ if(mpControlVector)
+ {
+ return mpControlVector->getNextVector(nIndex);
+ }
+ else
+ {
+ return basegfx::B2DVector::getEmptyVector();
+ }
+ }
+
+ void setNextControlVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue)
+ {
+ if(!mpControlVector)
+ {
+ if(!rValue.equalZero())
+ {
+ mpBufferedData.reset();
+ mpControlVector.reset( new ControlVectorArray2D(maPoints.count()) );
+ mpControlVector->setNextVector(nIndex, rValue);
+ }
+ }
+ else
+ {
+ mpBufferedData.reset();
+ mpControlVector->setNextVector(nIndex, rValue);
+
+ if(!mpControlVector->isUsed())
+ mpControlVector.reset();
+ }
+ }
+
+ bool areControlPointsUsed() const
+ {
+ return (mpControlVector && mpControlVector->isUsed());
+ }
+
+ void resetControlVectors(sal_uInt32 nIndex)
+ {
+ setPrevControlVector(nIndex, basegfx::B2DVector::getEmptyVector());
+ setNextControlVector(nIndex, basegfx::B2DVector::getEmptyVector());
+ }
+
+ void resetControlVectors()
+ {
+ mpBufferedData.reset();
+ mpControlVector.reset();
+ }
+
+ void setControlVectors(sal_uInt32 nIndex, const basegfx::B2DVector& rPrev, const basegfx::B2DVector& rNext)
+ {
+ setPrevControlVector(nIndex, rPrev);
+ setNextControlVector(nIndex, rNext);
+ }
+
+ void appendBezierSegment(const basegfx::B2DVector& rNext, const basegfx::B2DVector& rPrev, const basegfx::B2DPoint& rPoint)
+ {
+ mpBufferedData.reset();
+ const sal_uInt32 nCount(maPoints.count());
+
+ if(nCount)
+ {
+ setNextControlVector(nCount - 1, rNext);
+ }
+
+ insert(nCount, rPoint, 1);
+ setPrevControlVector(nCount, rPrev);
+ }
+
+ void insert(sal_uInt32 nIndex, const ImplB2DPolygon& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maPoints.count());
+
+ if(nCount)
+ {
+ mpBufferedData.reset();
+
+ if(rSource.mpControlVector && rSource.mpControlVector->isUsed() && !mpControlVector)
+ {
+ mpControlVector.reset( new ControlVectorArray2D(maPoints.count()) );
+ }
+
+ maPoints.insert(nIndex, rSource.maPoints);
+
+ if(rSource.mpControlVector)
+ {
+ mpControlVector->insert(nIndex, *rSource.mpControlVector);
+
+ if(!mpControlVector->isUsed())
+ mpControlVector.reset();
+ }
+ else if(mpControlVector)
+ {
+ ControlVectorPair2D aVectorPair;
+ mpControlVector->insert(nIndex, aVectorPair, nCount);
+ }
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ mpBufferedData.reset();
+ maPoints.remove(nIndex, nCount);
+
+ if(mpControlVector)
+ {
+ mpControlVector->remove(nIndex, nCount);
+
+ if(!mpControlVector->isUsed())
+ mpControlVector.reset();
+ }
+ }
+ }
+
+ void flip()
+ {
+ if(maPoints.count() > 1)
+ {
+ mpBufferedData.reset();
+
+ // flip points
+ maPoints.flip(mbIsClosed);
+
+ if(mpControlVector)
+ {
+ // flip control vector
+ mpControlVector->flip(mbIsClosed);
+ }
+ }
+ }
+
+ bool hasDoublePoints() const
+ {
+ if(mbIsClosed)
+ {
+ // check for same start and end point
+ const sal_uInt32 nIndex(maPoints.count() - 1);
+
+ if(maPoints.getCoordinate(0) == maPoints.getCoordinate(nIndex))
+ {
+ if(mpControlVector)
+ {
+ if(mpControlVector->getNextVector(nIndex).equalZero() && mpControlVector->getPrevVector(0).equalZero())
+ {
+ return true;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+
+ // test for range
+ for(sal_uInt32 a(0); a < maPoints.count() - 1; a++)
+ {
+ if(maPoints.getCoordinate(a) == maPoints.getCoordinate(a + 1))
+ {
+ if(mpControlVector)
+ {
+ if(mpControlVector->getNextVector(a).equalZero() && mpControlVector->getPrevVector(a + 1).equalZero())
+ {
+ return true;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ void removeDoublePointsAtBeginEnd()
+ {
+ // Only remove DoublePoints at Begin and End when poly is closed
+ if(mbIsClosed)
+ {
+ mpBufferedData.reset();
+
+ if(mpControlVector)
+ {
+ bool bRemove;
+
+ do
+ {
+ bRemove = false;
+
+ if(maPoints.count() > 1)
+ {
+ const sal_uInt32 nIndex(maPoints.count() - 1);
+
+ if(maPoints.getCoordinate(0) == maPoints.getCoordinate(nIndex))
+ {
+ if(mpControlVector)
+ {
+ if(mpControlVector->getNextVector(nIndex).equalZero() && mpControlVector->getPrevVector(0).equalZero())
+ {
+ bRemove = true;
+ }
+ }
+ else
+ {
+ bRemove = true;
+ }
+ }
+ }
+
+ if(bRemove)
+ {
+ const sal_uInt32 nIndex(maPoints.count() - 1);
+
+ if(mpControlVector && !mpControlVector->getPrevVector(nIndex).equalZero())
+ {
+ mpControlVector->setPrevVector(0, mpControlVector->getPrevVector(nIndex));
+ }
+
+ remove(nIndex, 1);
+ }
+ }
+ while(bRemove);
+ }
+ else
+ {
+ maPoints.removeDoublePointsAtBeginEnd();
+ }
+ }
+ }
+
+ void removeDoublePointsWholeTrack()
+ {
+ mpBufferedData.reset();
+
+ if(mpControlVector)
+ {
+ sal_uInt32 nIndex(0);
+
+ // test as long as there are at least two points and as long as the index
+ // is smaller or equal second last point
+ while((maPoints.count() > 1) && (nIndex <= maPoints.count() - 2))
+ {
+ bool bRemove(maPoints.getCoordinate(nIndex) == maPoints.getCoordinate(nIndex + 1));
+
+ if(bRemove)
+ {
+ if(mpControlVector)
+ {
+ if(!mpControlVector->getNextVector(nIndex).equalZero() || !mpControlVector->getPrevVector(nIndex + 1).equalZero())
+ {
+ bRemove = false;
+ }
+ }
+ }
+
+ if(bRemove)
+ {
+ if(mpControlVector && !mpControlVector->getPrevVector(nIndex).equalZero())
+ {
+ mpControlVector->setPrevVector(nIndex + 1, mpControlVector->getPrevVector(nIndex));
+ }
+
+ // if next is same as index and the control vectors are unused, delete index
+ remove(nIndex, 1);
+ }
+ else
+ {
+ // if different, step forward
+ nIndex++;
+ }
+ }
+ }
+ else
+ {
+ maPoints.removeDoublePointsWholeTrack();
+ }
+ }
+
+ void transform(const basegfx::B2DHomMatrix& rMatrix)
+ {
+ mpBufferedData.reset();
+
+ if(mpControlVector)
+ {
+ for(sal_uInt32 a(0); a < maPoints.count(); a++)
+ {
+ basegfx::B2DPoint aCandidate = maPoints.getCoordinate(a);
+
+ if(mpControlVector->isUsed())
+ {
+ const basegfx::B2DVector& rPrevVector(mpControlVector->getPrevVector(a));
+ const basegfx::B2DVector& rNextVector(mpControlVector->getNextVector(a));
+
+ if(!rPrevVector.equalZero())
+ {
+ basegfx::B2DVector aPrevVector(rMatrix * rPrevVector);
+ mpControlVector->setPrevVector(a, aPrevVector);
+ }
+
+ if(!rNextVector.equalZero())
+ {
+ basegfx::B2DVector aNextVector(rMatrix * rNextVector);
+ mpControlVector->setNextVector(a, aNextVector);
+ }
+ }
+
+ aCandidate *= rMatrix;
+ maPoints.setCoordinate(a, aCandidate);
+ }
+
+ if(!mpControlVector->isUsed())
+ mpControlVector.reset();
+ }
+ else
+ {
+ maPoints.transform(rMatrix);
+ }
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace
+ {
+ struct DefaultPolygon: public rtl::Static<B2DPolygon::ImplType, DefaultPolygon> {};
+ }
+
+ B2DPolygon::B2DPolygon()
+ : mpPolygon(DefaultPolygon::get())
+ {}
+
+ B2DPolygon::B2DPolygon(const B2DPolygon& rPolygon)
+ : mpPolygon(rPolygon.mpPolygon)
+ {}
+
+ B2DPolygon::B2DPolygon(const B2DPolygon& rPolygon, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : mpPolygon(ImplB2DPolygon(*rPolygon.mpPolygon, nIndex, nCount))
+ {
+ // TODO(P2): one extra temporary here (cow_wrapper copies
+ // given ImplB2DPolygon into its internal impl_t wrapper type)
+ OSL_ENSURE(nIndex + nCount <= rPolygon.mpPolygon->count(), "B2DPolygon constructor outside range (!)");
+ }
+
+ B2DPolygon::~B2DPolygon()
+ {
+ }
+
+ B2DPolygon& B2DPolygon::operator=(const B2DPolygon& rPolygon)
+ {
+ mpPolygon = rPolygon.mpPolygon;
+ return *this;
+ }
+
+ void B2DPolygon::makeUnique()
+ {
+ mpPolygon.make_unique();
+ }
+
+ bool B2DPolygon::operator==(const B2DPolygon& rPolygon) const
+ {
+ if(mpPolygon.same_object(rPolygon.mpPolygon))
+ return true;
+
+ return ((*mpPolygon) == (*rPolygon.mpPolygon));
+ }
+
+ bool B2DPolygon::operator!=(const B2DPolygon& rPolygon) const
+ {
+ return !(*this == rPolygon);
+ }
+
+ sal_uInt32 B2DPolygon::count() const
+ {
+ return mpPolygon->count();
+ }
+
+ B2DPoint B2DPolygon::getB2DPoint(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ return mpPolygon->getPoint(nIndex);
+ }
+
+ void B2DPolygon::setB2DPoint(sal_uInt32 nIndex, const B2DPoint& rValue)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ if(getB2DPoint(nIndex) != rValue)
+ {
+ mpPolygon->setPoint(nIndex, rValue);
+ }
+ }
+
+ void B2DPolygon::reserve(sal_uInt32 nCount)
+ {
+ mpPolygon->reserve(nCount);
+ }
+
+ void B2DPolygon::insert(sal_uInt32 nIndex, const B2DPoint& rPoint, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex <= mpPolygon->count(), "B2DPolygon Insert outside range (!)");
+
+ if(nCount)
+ {
+ mpPolygon->insert(nIndex, rPoint, nCount);
+ }
+ }
+
+ void B2DPolygon::append(const B2DPoint& rPoint, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ mpPolygon->insert(mpPolygon->count(), rPoint, nCount);
+ }
+ }
+
+ void B2DPolygon::append(const B2DPoint& rPoint)
+ {
+ mpPolygon->append(rPoint);
+ }
+
+ B2DPoint B2DPolygon::getPrevControlPoint(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ if(mpPolygon->areControlPointsUsed())
+ {
+ return mpPolygon->getPoint(nIndex) + mpPolygon->getPrevControlVector(nIndex);
+ }
+ else
+ {
+ return mpPolygon->getPoint(nIndex);
+ }
+ }
+
+ B2DPoint B2DPolygon::getNextControlPoint(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ if(mpPolygon->areControlPointsUsed())
+ {
+ return mpPolygon->getPoint(nIndex) + mpPolygon->getNextControlVector(nIndex);
+ }
+ else
+ {
+ return mpPolygon->getPoint(nIndex);
+ }
+ }
+
+ void B2DPolygon::setPrevControlPoint(sal_uInt32 nIndex, const B2DPoint& rValue)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+ const basegfx::B2DVector aNewVector(rValue - mpPolygon->getPoint(nIndex));
+
+ if(mpPolygon->getPrevControlVector(nIndex) != aNewVector)
+ {
+ mpPolygon->setPrevControlVector(nIndex, aNewVector);
+ }
+ }
+
+ void B2DPolygon::setNextControlPoint(sal_uInt32 nIndex, const B2DPoint& rValue)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+ const basegfx::B2DVector aNewVector(rValue - mpPolygon->getPoint(nIndex));
+
+ if(mpPolygon->getNextControlVector(nIndex) != aNewVector)
+ {
+ mpPolygon->setNextControlVector(nIndex, aNewVector);
+ }
+ }
+
+ void B2DPolygon::setControlPoints(sal_uInt32 nIndex, const basegfx::B2DPoint& rPrev, const basegfx::B2DPoint& rNext)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+ const B2DPoint aPoint(mpPolygon->getPoint(nIndex));
+ const basegfx::B2DVector aNewPrev(rPrev - aPoint);
+ const basegfx::B2DVector aNewNext(rNext - aPoint);
+
+ if(mpPolygon->getPrevControlVector(nIndex) != aNewPrev || mpPolygon->getNextControlVector(nIndex) != aNewNext)
+ {
+ mpPolygon->setControlVectors(nIndex, aNewPrev, aNewNext);
+ }
+ }
+
+ void B2DPolygon::resetPrevControlPoint(sal_uInt32 nIndex)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ if(mpPolygon->areControlPointsUsed() && !mpPolygon->getPrevControlVector(nIndex).equalZero())
+ {
+ mpPolygon->setPrevControlVector(nIndex, B2DVector::getEmptyVector());
+ }
+ }
+
+ void B2DPolygon::resetNextControlPoint(sal_uInt32 nIndex)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ if(mpPolygon->areControlPointsUsed() && !mpPolygon->getNextControlVector(nIndex).equalZero())
+ {
+ mpPolygon->setNextControlVector(nIndex, B2DVector::getEmptyVector());
+ }
+ }
+
+ void B2DPolygon::resetControlPoints(sal_uInt32 nIndex)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ if(mpPolygon->areControlPointsUsed() &&
+ (!mpPolygon->getPrevControlVector(nIndex).equalZero() || !mpPolygon->getNextControlVector(nIndex).equalZero()))
+ {
+ mpPolygon->resetControlVectors(nIndex);
+ }
+ }
+
+ void B2DPolygon::resetControlPoints()
+ {
+ if(mpPolygon->areControlPointsUsed())
+ {
+ mpPolygon->resetControlVectors();
+ }
+ }
+
+ void B2DPolygon::appendBezierSegment(
+ const B2DPoint& rNextControlPoint,
+ const B2DPoint& rPrevControlPoint,
+ const B2DPoint& rPoint)
+ {
+ const B2DVector aNewNextVector(mpPolygon->count() ? B2DVector(rNextControlPoint - mpPolygon->getPoint(mpPolygon->count() - 1)) : B2DVector::getEmptyVector());
+ const B2DVector aNewPrevVector(rPrevControlPoint - rPoint);
+
+ if(aNewNextVector.equalZero() && aNewPrevVector.equalZero())
+ {
+ mpPolygon->insert(mpPolygon->count(), rPoint, 1);
+ }
+ else
+ {
+ mpPolygon->appendBezierSegment(aNewNextVector, aNewPrevVector, rPoint);
+ }
+ }
+
+ bool B2DPolygon::areControlPointsUsed() const
+ {
+ return mpPolygon->areControlPointsUsed();
+ }
+
+ bool B2DPolygon::isPrevControlPointUsed(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ return (mpPolygon->areControlPointsUsed() && !mpPolygon->getPrevControlVector(nIndex).equalZero());
+ }
+
+ bool B2DPolygon::isNextControlPointUsed(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ return (mpPolygon->areControlPointsUsed() && !mpPolygon->getNextControlVector(nIndex).equalZero());
+ }
+
+ B2VectorContinuity B2DPolygon::getContinuityInPoint(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ if(mpPolygon->areControlPointsUsed())
+ {
+ const B2DVector& rPrev(mpPolygon->getPrevControlVector(nIndex));
+ const B2DVector& rNext(mpPolygon->getNextControlVector(nIndex));
+
+ return getContinuity(rPrev, rNext);
+ }
+ else
+ {
+ return CONTINUITY_NONE;
+ }
+ }
+
+ bool B2DPolygon::isBezierSegment(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+
+ if(mpPolygon->areControlPointsUsed())
+ {
+ // Check if the edge exists
+ const bool bNextIndexValidWithoutClose(nIndex + 1 < mpPolygon->count());
+
+ if(bNextIndexValidWithoutClose || mpPolygon->isClosed())
+ {
+ const sal_uInt32 nNextIndex(bNextIndexValidWithoutClose ? nIndex + 1 : 0);
+ return (!mpPolygon->getPrevControlVector(nNextIndex).equalZero()
+ || !mpPolygon->getNextControlVector(nIndex).equalZero());
+ }
+ else
+ {
+ // no valid edge -> no bezier segment, even when local next
+ // vector may be used
+ return false;
+ }
+ }
+ else
+ {
+ // no control points -> no bezier segment
+ return false;
+ }
+ }
+
+ void B2DPolygon::getBezierSegment(sal_uInt32 nIndex, B2DCubicBezier& rTarget) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)");
+ const bool bNextIndexValidWithoutClose(nIndex + 1 < mpPolygon->count());
+
+ if(bNextIndexValidWithoutClose || mpPolygon->isClosed())
+ {
+ const sal_uInt32 nNextIndex(bNextIndexValidWithoutClose ? nIndex + 1 : 0);
+ rTarget.setStartPoint(mpPolygon->getPoint(nIndex));
+ rTarget.setEndPoint(mpPolygon->getPoint(nNextIndex));
+
+ if(mpPolygon->areControlPointsUsed())
+ {
+ rTarget.setControlPointA(rTarget.getStartPoint() + mpPolygon->getNextControlVector(nIndex));
+ rTarget.setControlPointB(rTarget.getEndPoint() + mpPolygon->getPrevControlVector(nNextIndex));
+ }
+ else
+ {
+ // no bezier, reset control poins at rTarget
+ rTarget.setControlPointA(rTarget.getStartPoint());
+ rTarget.setControlPointB(rTarget.getEndPoint());
+ }
+ }
+ else
+ {
+ // no valid edge at all, reset rTarget to current point
+ const B2DPoint aPoint(mpPolygon->getPoint(nIndex));
+ rTarget.setStartPoint(aPoint);
+ rTarget.setEndPoint(aPoint);
+ rTarget.setControlPointA(aPoint);
+ rTarget.setControlPointB(aPoint);
+ }
+ }
+
+ B2DPolygon B2DPolygon::getDefaultAdaptiveSubdivision() const
+ {
+ return mpPolygon->getDefaultAdaptiveSubdivision(*this);
+ }
+
+ B2DRange B2DPolygon::getB2DRange() const
+ {
+ return mpPolygon->getB2DRange(*this);
+ }
+
+ void B2DPolygon::insert(sal_uInt32 nIndex, const B2DPolygon& rPoly, sal_uInt32 nIndex2, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex <= mpPolygon->count(), "B2DPolygon Insert outside range (!)");
+
+ if(rPoly.count())
+ {
+ if(!nCount)
+ {
+ nCount = rPoly.count();
+ }
+
+ if(0 == nIndex2 && nCount == rPoly.count())
+ {
+ mpPolygon->insert(nIndex, *rPoly.mpPolygon);
+ }
+ else
+ {
+ OSL_ENSURE(nIndex2 + nCount <= rPoly.mpPolygon->count(), "B2DPolygon Insert outside range (!)");
+ ImplB2DPolygon aTempPoly(*rPoly.mpPolygon, nIndex2, nCount);
+ mpPolygon->insert(nIndex, aTempPoly);
+ }
+ }
+ }
+
+ void B2DPolygon::append(const B2DPolygon& rPoly, sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(rPoly.count())
+ {
+ if(!nCount)
+ {
+ nCount = rPoly.count();
+ }
+
+ if(0 == nIndex && nCount == rPoly.count())
+ {
+ mpPolygon->insert(mpPolygon->count(), *rPoly.mpPolygon);
+ }
+ else
+ {
+ OSL_ENSURE(nIndex + nCount <= rPoly.mpPolygon->count(), "B2DPolygon Append outside range (!)");
+ ImplB2DPolygon aTempPoly(*rPoly.mpPolygon, nIndex, nCount);
+ mpPolygon->insert(mpPolygon->count(), aTempPoly);
+ }
+ }
+ }
+
+ void B2DPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex + nCount <= mpPolygon->count(), "B2DPolygon Remove outside range (!)");
+
+ if(nCount)
+ {
+ mpPolygon->remove(nIndex, nCount);
+ }
+ }
+
+ void B2DPolygon::clear()
+ {
+ mpPolygon = DefaultPolygon::get();
+ }
+
+ bool B2DPolygon::isClosed() const
+ {
+ return mpPolygon->isClosed();
+ }
+
+ void B2DPolygon::setClosed(bool bNew)
+ {
+ if(isClosed() != bNew)
+ {
+ mpPolygon->setClosed(bNew);
+ }
+ }
+
+ void B2DPolygon::flip()
+ {
+ if(count() > 1)
+ {
+ mpPolygon->flip();
+ }
+ }
+
+ bool B2DPolygon::hasDoublePoints() const
+ {
+ return (mpPolygon->count() > 1 && mpPolygon->hasDoublePoints());
+ }
+
+ void B2DPolygon::removeDoublePoints()
+ {
+ if(hasDoublePoints())
+ {
+ mpPolygon->removeDoublePointsAtBeginEnd();
+ mpPolygon->removeDoublePointsWholeTrack();
+ }
+ }
+
+ void B2DPolygon::transform(const B2DHomMatrix& rMatrix)
+ {
+ if(mpPolygon->count() && !rMatrix.isIdentity())
+ {
+ mpPolygon->transform(rMatrix);
+ }
+ }
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+// eof
diff --git a/basegfx/source/polygon/b2dpolygonclipper.cxx b/basegfx/source/polygon/b2dpolygonclipper.cxx
new file mode 100644
index 000000000000..6e5c7701e2ec
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolygonclipper.cxx
@@ -0,0 +1,873 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <osl/diagnose.h>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dpolygoncutandtouch.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/tools/rectcliptools.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace tools
+ {
+ B2DPolyPolygon clipPolygonOnParallelAxis(const B2DPolygon& rCandidate, bool bParallelToXAxis, bool bAboveAxis, double fValueOnOtherAxis, bool bStroke)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(rCandidate.count())
+ {
+ const B2DRange aCandidateRange(getRange(rCandidate));
+
+ if(bParallelToXAxis && fTools::moreOrEqual(aCandidateRange.getMinY(), fValueOnOtherAxis))
+ {
+ // completely above and on the clip line. also true for curves.
+ if(bAboveAxis)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(bParallelToXAxis && fTools::lessOrEqual(aCandidateRange.getMaxY(), fValueOnOtherAxis))
+ {
+ // completely below and on the clip line. also true for curves.
+ if(!bAboveAxis)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(!bParallelToXAxis && fTools::moreOrEqual(aCandidateRange.getMinX(), fValueOnOtherAxis))
+ {
+ // completely right of and on the clip line. also true for curves.
+ if(bAboveAxis)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(!bParallelToXAxis && fTools::lessOrEqual(aCandidateRange.getMaxX(), fValueOnOtherAxis))
+ {
+ // completely left of and on the clip line. also true for curves.
+ if(!bAboveAxis)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else
+ {
+ // add cuts with axis to polygon, including bezier segments
+ // Build edge to cut with. Make it a little big longer than needed for
+ // numerical stability. We want to cut against the edge seen as endless
+ // ray here, but addPointsAtCuts() will limit itself to the
+ // edge's range ]0.0 .. 1.0[.
+ const double fSmallExtension((aCandidateRange.getWidth() + aCandidateRange.getHeight()) * (0.5 * 0.1));
+ const B2DPoint aStart(
+ bParallelToXAxis ? aCandidateRange.getMinX() - fSmallExtension : fValueOnOtherAxis,
+ bParallelToXAxis ? fValueOnOtherAxis : aCandidateRange.getMinY() - fSmallExtension);
+ const B2DPoint aEnd(
+ bParallelToXAxis ? aCandidateRange.getMaxX() + fSmallExtension : fValueOnOtherAxis,
+ bParallelToXAxis ? fValueOnOtherAxis : aCandidateRange.getMaxY() + fSmallExtension);
+ const B2DPolygon aCandidate(addPointsAtCuts(rCandidate, aStart, aEnd));
+ const sal_uInt32 nPointCount(aCandidate.count());
+ const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+ B2DCubicBezier aEdge;
+ B2DPolygon aRun;
+
+ for(sal_uInt32 a(0L); a < nEdgeCount; a++)
+ {
+ aCandidate.getBezierSegment(a, aEdge);
+ const B2DPoint aTestPoint(aEdge.interpolatePoint(0.5));
+ const bool bInside(bParallelToXAxis ?
+ fTools::moreOrEqual(aTestPoint.getY(), fValueOnOtherAxis) == bAboveAxis :
+ fTools::moreOrEqual(aTestPoint.getX(), fValueOnOtherAxis) == bAboveAxis);
+
+ if(bInside)
+ {
+ if(!aRun.count() || !aRun.getB2DPoint(aRun.count() - 1).equal(aEdge.getStartPoint()))
+ {
+ aRun.append(aEdge.getStartPoint());
+ }
+
+ if(aEdge.isBezier())
+ {
+ aRun.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint());
+ }
+ else
+ {
+ aRun.append(aEdge.getEndPoint());
+ }
+ }
+ else
+ {
+ if(bStroke && aRun.count())
+ {
+ aRetval.append(aRun);
+ aRun.clear();
+ }
+ }
+ }
+
+ if(aRun.count())
+ {
+ if(bStroke)
+ {
+ // try to merge this last and first polygon; they may have been
+ // the former polygon's start/end point
+ if(aRetval.count())
+ {
+ const B2DPolygon aStartPolygon(aRetval.getB2DPolygon(0));
+
+ if(aStartPolygon.count() && aStartPolygon.getB2DPoint(0).equal(aRun.getB2DPoint(aRun.count() - 1)))
+ {
+ // append start polygon to aRun, remove from result set
+ aRun.append(aStartPolygon); aRun.removeDoublePoints();
+ aRetval.remove(0);
+ }
+ }
+
+ aRetval.append(aRun);
+ }
+ else
+ {
+ // set closed flag and correct last point (which is added double now).
+ closeWithGeometryChange(aRun);
+ aRetval.append(aRun);
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon clipPolyPolygonOnParallelAxis(const B2DPolyPolygon& rCandidate, bool bParallelToXAxis, bool bAboveAxis, double fValueOnOtherAxis, bool bStroke)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolyPolygon aClippedPolyPolygon(clipPolygonOnParallelAxis(rCandidate.getB2DPolygon(a), bParallelToXAxis, bAboveAxis, fValueOnOtherAxis, bStroke));
+
+ if(aClippedPolyPolygon.count())
+ {
+ aRetval.append(aClippedPolyPolygon);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon clipPolygonOnRange(const B2DPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ if(!nCount)
+ {
+ // source is empty
+ return aRetval;
+ }
+
+ if(rRange.isEmpty())
+ {
+ if(bInside)
+ {
+ // nothing is inside an empty range
+ return aRetval;
+ }
+ else
+ {
+ // everything is outside an empty range
+ return B2DPolyPolygon(rCandidate);
+ }
+ }
+
+ const B2DRange aCandidateRange(getRange(rCandidate));
+
+ if(rRange.isInside(aCandidateRange))
+ {
+ // candidate is completely inside given range
+ if(bInside)
+ {
+ // nothing to do
+ return B2DPolyPolygon(rCandidate);
+ }
+ else
+ {
+ // nothing is outside, then
+ return aRetval;
+ }
+ }
+
+ if(!bInside)
+ {
+ // cutting off the outer parts of filled polygons at parallell
+ // lines to the axes is only possible for the inner part, not for
+ // the outer part which means cutting a hole into the original polygon.
+ // This is because the inner part is a logical AND-operation of
+ // the four implied half-planes, but the outer part is not.
+ // It is possible for strokes, but with creating unnecessary extra
+ // cuts, so using clipPolygonOnPolyPolygon is better there, too.
+ // This needs to be done with the topology knowlegde and is unfurtunately
+ // more expensive, too.
+ const B2DPolygon aClip(createPolygonFromRect(rRange));
+
+ return clipPolygonOnPolyPolygon(rCandidate, B2DPolyPolygon(aClip), bInside, bStroke);
+ }
+
+ // clip against the four axes of the range
+ // against X-Axis, lower value
+ aRetval = clipPolygonOnParallelAxis(rCandidate, true, bInside, rRange.getMinY(), bStroke);
+
+ if(aRetval.count())
+ {
+ // against Y-Axis, lower value
+ if(1L == aRetval.count())
+ {
+ aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0L), false, bInside, rRange.getMinX(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnParallelAxis(aRetval, false, bInside, rRange.getMinX(), bStroke);
+ }
+
+ if(aRetval.count())
+ {
+ // against X-Axis, higher value
+ if(1L == aRetval.count())
+ {
+ aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0L), true, !bInside, rRange.getMaxY(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnParallelAxis(aRetval, true, !bInside, rRange.getMaxY(), bStroke);
+ }
+
+ if(aRetval.count())
+ {
+ // against Y-Axis, higher value
+ if(1L == aRetval.count())
+ {
+ aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0L), false, !bInside, rRange.getMaxX(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnParallelAxis(aRetval, false, !bInside, rRange.getMaxX(), bStroke);
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon clipPolyPolygonOnRange(const B2DPolyPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ if(!nPolygonCount)
+ {
+ // source is empty
+ return aRetval;
+ }
+
+ if(rRange.isEmpty())
+ {
+ if(bInside)
+ {
+ // nothing is inside an empty range
+ return aRetval;
+ }
+ else
+ {
+ // everything is outside an empty range
+ return rCandidate;
+ }
+ }
+
+ if(bInside)
+ {
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolyPolygon aClippedPolyPolygon(clipPolygonOnRange(rCandidate.getB2DPolygon(a), rRange, bInside, bStroke));
+
+ if(aClippedPolyPolygon.count())
+ {
+ aRetval.append(aClippedPolyPolygon);
+ }
+ }
+ }
+ else
+ {
+ // for details, see comment in clipPolygonOnRange for the "cutting off
+ // the outer parts of filled polygons at parallell lines" explanations
+ const B2DPolygon aClip(createPolygonFromRect(rRange));
+
+ return clipPolyPolygonOnPolyPolygon(rCandidate, B2DPolyPolygon(aClip), bInside, bStroke);
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon clipPolygonOnEdge(const B2DPolygon& rCandidate, const B2DPoint& rPointA, const B2DPoint& rPointB, bool bAbove, bool bStroke)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(rPointA.equal(rPointB))
+ {
+ // edge has no length, return polygon
+ aRetval.append(rCandidate);
+ }
+ else if(rCandidate.count())
+ {
+ const B2DVector aEdge(rPointB - rPointA);
+ B2DPolygon aCandidate(rCandidate);
+
+ // translate and rotate polygon so that given edge is on x axis
+ B2DHomMatrix aMatrixTransform(basegfx::tools::createTranslateB2DHomMatrix(-rPointA.getX(), -rPointA.getY()));
+ aMatrixTransform.rotate(-atan2(aEdge.getY(), aEdge.getX()));
+ aCandidate.transform(aMatrixTransform);
+
+ // call clip method on X-Axis
+ aRetval = clipPolygonOnParallelAxis(aCandidate, true, bAbove, 0.0, bStroke);
+
+ if(aRetval.count())
+ {
+ // if there is a result, it needs to be transformed back
+ aMatrixTransform.invert();
+ aRetval.transform(aMatrixTransform);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon clipPolyPolygonOnEdge(const B2DPolyPolygon& rCandidate, const B2DPoint& rPointA, const B2DPoint& rPointB, bool bAbove, bool bStroke)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(rPointA.equal(rPointB))
+ {
+ // edge has no length, return polygon
+ aRetval = rCandidate;
+ }
+ else if(rCandidate.count())
+ {
+ const B2DVector aEdge(rPointB - rPointA);
+ B2DPolyPolygon aCandidate(rCandidate);
+
+ // translate and rotate polygon so that given edge is on x axis
+ B2DHomMatrix aMatrixTransform(basegfx::tools::createTranslateB2DHomMatrix(-rPointA.getX(), -rPointA.getY()));
+ aMatrixTransform.rotate(-atan2(aEdge.getY(), aEdge.getX()));
+ aCandidate.transform(aMatrixTransform);
+
+ // call clip method on X-Axis
+ aRetval = clipPolyPolygonOnParallelAxis(aCandidate, true, bAbove, 0.0, bStroke);
+
+ if(aRetval.count())
+ {
+ // if there is a result, it needs to be transformed back
+ aMatrixTransform.invert();
+ aRetval.transform(aMatrixTransform);
+ }
+ }
+
+ return aRetval;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ B2DPolyPolygon clipPolyPolygonOnPolyPolygon(const B2DPolyPolygon& rCandidate, const B2DPolyPolygon& rClip, bool bInside, bool bStroke)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(rCandidate.count() && rClip.count())
+ {
+ if(bStroke)
+ {
+ // line clipping, create line snippets by first adding all cut points and
+ // then marching along the edges and detecting if they are inside or outside
+ // the clip polygon
+ for(sal_uInt32 a(0); a < rCandidate.count(); a++)
+ {
+ // add cuts with clip to polygon, including bezier segments
+ const B2DPolygon aCandidate(addPointsAtCuts(rCandidate.getB2DPolygon(a), rClip));
+ const sal_uInt32 nPointCount(aCandidate.count());
+ const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+ B2DCubicBezier aEdge;
+ B2DPolygon aRun;
+
+ for(sal_uInt32 b(0); b < nEdgeCount; b++)
+ {
+ aCandidate.getBezierSegment(b, aEdge);
+ const B2DPoint aTestPoint(aEdge.interpolatePoint(0.5));
+ const bool bIsInside(tools::isInside(rClip, aTestPoint) == bInside);
+
+ if(bIsInside)
+ {
+ if(!aRun.count())
+ {
+ aRun.append(aEdge.getStartPoint());
+ }
+
+ if(aEdge.isBezier())
+ {
+ aRun.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint());
+ }
+ else
+ {
+ aRun.append(aEdge.getEndPoint());
+ }
+ }
+ else
+ {
+ if(aRun.count())
+ {
+ aRetval.append(aRun);
+ aRun.clear();
+ }
+ }
+ }
+
+ if(aRun.count())
+ {
+ // try to merge this last and first polygon; they may have been
+ // the former polygon's start/end point
+ if(aRetval.count())
+ {
+ const B2DPolygon aStartPolygon(aRetval.getB2DPolygon(0));
+
+ if(aStartPolygon.count() && aStartPolygon.getB2DPoint(0).equal(aRun.getB2DPoint(aRun.count() - 1)))
+ {
+ // append start polygon to aRun, remove from result set
+ aRun.append(aStartPolygon); aRun.removeDoublePoints();
+ aRetval.remove(0);
+ }
+ }
+
+ aRetval.append(aRun);
+ }
+ }
+ }
+ else
+ {
+ // area clipping
+ B2DPolyPolygon aMergePolyPolygonA(rClip);
+
+ // First solve all polygon-self and polygon-polygon intersections.
+ // Also get rid of some not-needed polygons (neutral, no area -> when
+ // no intersections, these are tubes).
+ // Now it is possible to correct the orientations in the cut-free
+ // polygons to values corresponding to painting the PolyPolygon with
+ // a XOR-WindingRule.
+ aMergePolyPolygonA = solveCrossovers(aMergePolyPolygonA);
+ aMergePolyPolygonA = stripNeutralPolygons(aMergePolyPolygonA);
+ aMergePolyPolygonA = correctOrientations(aMergePolyPolygonA);
+
+ if(!bInside)
+ {
+ // if we want to get the outside of the clip polygon, make
+ // it a 'Hole' in topological sense
+ aMergePolyPolygonA.flip();
+ }
+
+ B2DPolyPolygon aMergePolyPolygonB(rCandidate);
+
+ // prepare 2nd source polygon in same way
+ aMergePolyPolygonB = solveCrossovers(aMergePolyPolygonB);
+ aMergePolyPolygonB = stripNeutralPolygons(aMergePolyPolygonB);
+ aMergePolyPolygonB = correctOrientations(aMergePolyPolygonB);
+
+ // to clip against each other, concatenate and solve all
+ // polygon-polygon crossovers. polygon-self do not need to
+ // be solved again, they were solved in the preparation.
+ aRetval.append(aMergePolyPolygonA);
+ aRetval.append(aMergePolyPolygonB);
+ aRetval = solveCrossovers(aRetval);
+
+ // now remove neutral polygons (closed, but no area). In a last
+ // step throw away all polygons which have a depth of less than 1
+ // which means there was no logical AND at their position. For the
+ // not-inside solution, the clip was flipped to define it as 'Hole',
+ // so the removal rule is different here; remove all with a depth
+ // of less than 0 (aka holes).
+ aRetval = stripNeutralPolygons(aRetval);
+ aRetval = stripDispensablePolygons(aRetval, bInside);
+ }
+ }
+
+ return aRetval;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ B2DPolyPolygon clipPolygonOnPolyPolygon(const B2DPolygon& rCandidate, const B2DPolyPolygon& rClip, bool bInside, bool bStroke)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(rCandidate.count() && rClip.count())
+ {
+ aRetval = clipPolyPolygonOnPolyPolygon(B2DPolyPolygon(rCandidate), rClip, bInside, bStroke);
+ }
+
+ return aRetval;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ /*
+ * let a plane be defined as
+ *
+ * v.n+d=0
+ *
+ * and a ray be defined as
+ *
+ * a+(b-a)*t=0
+ *
+ * substitute and rearranging yields
+ *
+ * t = -(a.n+d)/(n.(b-a))
+ *
+ * if the denominator is zero, the line is either
+ * contained in the plane or parallel to the plane.
+ * in either case, there is no intersection.
+ * if numerator and denominator are both zero, the
+ * ray is contained in the plane.
+ *
+ */
+ struct scissor_plane {
+ double nx,ny; // plane normal
+ double d; // [-] minimum distance from origin
+ sal_uInt32 clipmask; // clipping mask, e.g. 1000 1000
+ };
+
+ /*
+ *
+ * polygon clipping rules (straight out of Foley and Van Dam)
+ * ===========================================================
+ * current |next |emit
+ * ____________________________________
+ * inside |inside |next
+ * inside |outside |intersect with clip plane
+ * outside |outside |nothing
+ * outside |inside |intersect with clip plane follwed by next
+ *
+ */
+ sal_uInt32 scissorLineSegment( ::basegfx::B2DPoint *in_vertex, // input buffer
+ sal_uInt32 in_count, // number of verts in input buffer
+ ::basegfx::B2DPoint *out_vertex, // output buffer
+ scissor_plane *pPlane, // scissoring plane
+ const ::basegfx::B2DRectangle &rR ) // clipping rectangle
+ {
+ ::basegfx::B2DPoint *curr;
+ ::basegfx::B2DPoint *next;
+
+ sal_uInt32 out_count=0;
+
+ // process all the verts
+ for(sal_uInt32 i=0; i<in_count; i++) {
+
+ // vertices are relative to the coordinate
+ // system defined by the rectangle.
+ curr = &in_vertex[i];
+ next = &in_vertex[(i+1)%in_count];
+
+ // perform clipping judgement & mask against current plane.
+ sal_uInt32 clip = pPlane->clipmask & ((getCohenSutherlandClipFlags(*curr,rR)<<4)|getCohenSutherlandClipFlags(*next,rR));
+
+ if(clip==0) { // both verts are inside
+ out_vertex[out_count++] = *next;
+ }
+ else if((clip&0x0f) && (clip&0xf0)) { // both verts are outside
+ }
+ else if((clip&0x0f) && (clip&0xf0)==0) { // curr is inside, next is outside
+
+ // direction vector from 'current' to 'next', *not* normalized
+ // to bring 't' into the [0<=x<=1] intervall.
+ ::basegfx::B2DPoint dir((*next)-(*curr));
+
+ double denominator = ( pPlane->nx*dir.getX() +
+ pPlane->ny*dir.getY() );
+ double numerator = ( pPlane->nx*curr->getX() +
+ pPlane->ny*curr->getY() +
+ pPlane->d );
+ double t = -numerator/denominator;
+
+ // calculate the actual point of intersection
+ ::basegfx::B2DPoint intersection( curr->getX()+t*dir.getX(),
+ curr->getY()+t*dir.getY() );
+
+ out_vertex[out_count++] = intersection;
+ }
+ else if((clip&0x0f)==0 && (clip&0xf0)) { // curr is outside, next is inside
+
+ // direction vector from 'current' to 'next', *not* normalized
+ // to bring 't' into the [0<=x<=1] intervall.
+ ::basegfx::B2DPoint dir((*next)-(*curr));
+
+ double denominator = ( pPlane->nx*dir.getX() +
+ pPlane->ny*dir.getY() );
+ double numerator = ( pPlane->nx*curr->getX() +
+ pPlane->ny*curr->getY() +
+ pPlane->d );
+ double t = -numerator/denominator;
+
+ // calculate the actual point of intersection
+ ::basegfx::B2DPoint intersection( curr->getX()+t*dir.getX(),
+ curr->getY()+t*dir.getY() );
+
+ out_vertex[out_count++] = intersection;
+ out_vertex[out_count++] = *next;
+ }
+ }
+
+ return out_count;
+ }
+
+ B2DPolygon clipTriangleListOnRange( const B2DPolygon& rCandidate,
+ const B2DRange& rRange )
+ {
+ B2DPolygon aResult;
+
+ if( !(rCandidate.count()%3) )
+ {
+ const int scissor_plane_count = 4;
+
+ scissor_plane sp[scissor_plane_count];
+
+ sp[0].nx = +1.0;
+ sp[0].ny = +0.0;
+ sp[0].d = -(rRange.getMinX());
+ sp[0].clipmask = (RectClipFlags::LEFT << 4) | RectClipFlags::LEFT; // 0001 0001
+ sp[1].nx = -1.0;
+ sp[1].ny = +0.0;
+ sp[1].d = +(rRange.getMaxX());
+ sp[1].clipmask = (RectClipFlags::RIGHT << 4) | RectClipFlags::RIGHT; // 0010 0010
+ sp[2].nx = +0.0;
+ sp[2].ny = +1.0;
+ sp[2].d = -(rRange.getMinY());
+ sp[2].clipmask = (RectClipFlags::TOP << 4) | RectClipFlags::TOP; // 0100 0100
+ sp[3].nx = +0.0;
+ sp[3].ny = -1.0;
+ sp[3].d = +(rRange.getMaxY());
+ sp[3].clipmask = (RectClipFlags::BOTTOM << 4) | RectClipFlags::BOTTOM; // 1000 1000
+
+ // retrieve the number of vertices of the triangulated polygon
+ const sal_uInt32 nVertexCount = rCandidate.count();
+
+ if(nVertexCount)
+ {
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Upper bound for the maximal number of vertices when intersecting an
+ // axis-aligned rectangle with a triangle in E2
+ //
+ // The rectangle and the triangle are in general position, and have 4 and 3
+ // vertices, respectively.
+ //
+ // Lemma: Since the rectangle is a convex polygon ( see
+ // http://mathworld.wolfram.com/ConvexPolygon.html for a definition), and
+ // has no holes, it follows that any straight line will intersect the
+ // rectangle's border line at utmost two times (with the usual
+ // tie-breaking rule, if the intersection exactly hits an already existing
+ // rectangle vertex, that this intersection is only attributed to one of
+ // the adjoining edges). Thus, having a rectangle intersected with
+ // a half-plane (one side of a straight line denotes 'inside', the
+ // other 'outside') will at utmost add _one_ vertex to the resulting
+ // intersection polygon (adding two intersection vertices, and removing at
+ // least one rectangle vertex):
+ //
+ // *
+ // +--+-----------------+
+ // | * |
+ // |* |
+ // + |
+ // *| |
+ // * | |
+ // +--------------------+
+ //
+ // Proof: If the straight line intersects the rectangle two
+ // times, it does so for distinct edges, i.e. the intersection has
+ // minimally one of the rectangle's vertices on either side of the straight
+ // line (but maybe more). Thus, the intersection with a half-plane has
+ // minimally _one_ rectangle vertex removed from the resulting clip
+ // polygon, and therefore, a clip against a half-plane has the net effect
+ // of adding at utmost _one_ vertex to the resulting clip polygon.
+ //
+ // Theorem: The intersection of a rectangle and a triangle results in a
+ // polygon with at utmost 7 vertices.
+ //
+ // Proof: The inside of the triangle can be described as the consecutive
+ // intersection with three half-planes. Together with the lemma above, this
+ // results in at utmost 3 additional vertices added to the already existing 4
+ // rectangle vertices.
+ //
+ // This upper bound is attained with the following example configuration:
+ //
+ // *
+ // ***
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ----*2--------3 *
+ // | ** |*
+ // 1* 4
+ // **| *|
+ // ** | * |
+ // **| * |
+ // 7* * |
+ // --*6-----5-----
+ // ** *
+ // **
+ //
+ // As we need to scissor all triangles against the
+ // output rectangle we employ an output buffer for the
+ // resulting vertices. the question is how large this
+ // buffer needs to be compared to the number of
+ // incoming vertices. this buffer needs to hold at
+ // most the number of original vertices times '7'. see
+ // figure above for an example. scissoring triangles
+ // with the cohen-sutherland line clipping algorithm
+ // as implemented here will result in a triangle fan
+ // which will be rendered as separate triangles to
+ // avoid pipeline stalls for each scissored
+ // triangle. creating separate triangles from a
+ // triangle fan produces (n-2)*3 vertices where n is
+ // the number of vertices of the original triangle
+ // fan. for the maximum number of 7 vertices of
+ // resulting triangle fans we therefore need 15 times
+ // the number of original vertices.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+
+ //const size_t nBufferSize = sizeof(vertex)*(nVertexCount*16);
+ //vertex *pVertices = (vertex*)alloca(nBufferSize);
+ //sal_uInt32 nNumOutput = 0;
+
+ // we need to clip this triangle against the output rectangle
+ // to ensure that the resulting texture coordinates are in
+ // the valid range from [0<=st<=1]. under normal circustances
+ // we could use the BORDERCOLOR renderstate but some cards
+ // seem to ignore this feature.
+ ::basegfx::B2DPoint stack[3];
+ unsigned int clipflag = 0;
+
+ for(sal_uInt32 nIndex=0; nIndex<nVertexCount; ++nIndex)
+ {
+ // rotate stack
+ stack[0] = stack[1];
+ stack[1] = stack[2];
+ stack[2] = rCandidate.getB2DPoint(nIndex);
+
+ // clipping judgement
+ clipflag |= !(rRange.isInside(stack[2]));
+
+ if(nIndex > 1)
+ {
+ // consume vertices until a single seperate triangle has been visited.
+ if(!((nIndex+1)%3))
+ {
+ // if any of the last three vertices was outside
+ // we need to scissor against the destination rectangle
+ if(clipflag & 7)
+ {
+ ::basegfx::B2DPoint buf0[16];
+ ::basegfx::B2DPoint buf1[16];
+
+ sal_uInt32 vertex_count = 3;
+
+ // clip against all 4 planes passing the result of
+ // each plane as the input to the next using a double buffer
+ vertex_count = scissorLineSegment(stack,vertex_count,buf1,&sp[0],rRange);
+ vertex_count = scissorLineSegment(buf1,vertex_count,buf0,&sp[1],rRange);
+ vertex_count = scissorLineSegment(buf0,vertex_count,buf1,&sp[2],rRange);
+ vertex_count = scissorLineSegment(buf1,vertex_count,buf0,&sp[3],rRange);
+
+ if(vertex_count >= 3)
+ {
+ // convert triangle fan back to triangle list.
+ ::basegfx::B2DPoint v0(buf0[0]);
+ ::basegfx::B2DPoint v1(buf0[1]);
+ for(sal_uInt32 i=2; i<vertex_count; ++i)
+ {
+ ::basegfx::B2DPoint v2(buf0[i]);
+ aResult.append(v0);
+ aResult.append(v1);
+ aResult.append(v2);
+ v1 = v2;
+ }
+ }
+ }
+ else
+ {
+ // the last triangle has not been altered, simply copy to result
+ for(sal_uInt32 i=0; i<3; ++i)
+ aResult.append(stack[i]);
+ }
+ }
+ }
+
+ clipflag <<= 1;
+ }
+ }
+ }
+
+ return aResult;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ } // end of namespace tools
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+// eof
diff --git a/basegfx/source/polygon/b2dpolygoncutandtouch.cxx b/basegfx/source/polygon/b2dpolygoncutandtouch.cxx
new file mode 100644
index 000000000000..9eab4b26c8b3
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolygoncutandtouch.cxx
@@ -0,0 +1,1209 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <basegfx/polygon/b2dpolygoncutandtouch.hxx>
+#include <osl/diagnose.h>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+
+#include <vector>
+#include <algorithm>
+
+//////////////////////////////////////////////////////////////////////////////
+// defines
+
+#define SUBDIVIDE_FOR_CUT_TEST_COUNT (50)
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace
+ {
+ ////////////////////////////////////////////////////////////////////////////////
+
+ class temporaryPoint
+ {
+ B2DPoint maPoint; // the new point
+ sal_uInt32 mnIndex; // index after which to insert
+ double mfCut; // parametric cut description [0.0 .. 1.0]
+
+ public:
+ temporaryPoint(const B2DPoint& rNewPoint, sal_uInt32 nIndex, double fCut)
+ : maPoint(rNewPoint),
+ mnIndex(nIndex),
+ mfCut(fCut)
+ {
+ }
+
+ bool operator<(const temporaryPoint& rComp) const
+ {
+ if(mnIndex == rComp.mnIndex)
+ {
+ return (mfCut < rComp.mfCut);
+ }
+
+ return (mnIndex < rComp.mnIndex);
+ }
+
+ const B2DPoint& getPoint() const { return maPoint; }
+ sal_uInt32 getIndex() const { return mnIndex; }
+ double getCut() const { return mfCut; }
+ };
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ typedef ::std::vector< temporaryPoint > temporaryPointVector;
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ class temporaryPolygonData
+ {
+ B2DPolygon maPolygon;
+ B2DRange maRange;
+ temporaryPointVector maPoints;
+
+ public:
+ const B2DPolygon& getPolygon() const { return maPolygon; }
+ void setPolygon(const B2DPolygon& rNew) { maPolygon = rNew; maRange = tools::getRange(maPolygon); }
+ const B2DRange& getRange() const { return maRange; }
+ temporaryPointVector& getTemporaryPointVector() { return maPoints; }
+ };
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ B2DPolygon mergeTemporaryPointsAndPolygon(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints)
+ {
+ // #i76891# mergeTemporaryPointsAndPolygon redesigned to be able to correctly handle
+ // single edges with/without control points
+ // #i101491# added counter for non-changing element count
+ const sal_uInt32 nTempPointCount(rTempPoints.size());
+
+ if(nTempPointCount)
+ {
+ B2DPolygon aRetval;
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nCount)
+ {
+ // sort temp points to assure increasing fCut values and increasing indices
+ ::std::sort(rTempPoints.begin(), rTempPoints.end());
+
+ // prepare loop
+ B2DCubicBezier aEdge;
+ sal_uInt32 nNewInd(0L);
+
+ // add start point
+ aRetval.append(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0L); a < nCount; a++)
+ {
+ // get edge
+ rCandidate.getBezierSegment(a, aEdge);
+
+ if(aEdge.isBezier())
+ {
+ // control vectors involved for this edge
+ double fLeftStart(0.0);
+
+ // now add all points targeted to be at this index
+ while(nNewInd < nTempPointCount && rTempPoints[nNewInd].getIndex() == a)
+ {
+ const temporaryPoint& rTempPoint = rTempPoints[nNewInd++];
+
+ // split curve segment. Splits need to come sorted and need to be < 1.0. Also,
+ // since original segment is consumed from left to right, the cut values need
+ // to be scaled to the remaining part
+ B2DCubicBezier aLeftPart;
+ const double fRelativeSplitPoint((rTempPoint.getCut() - fLeftStart) / (1.0 - fLeftStart));
+ aEdge.split(fRelativeSplitPoint, &aLeftPart, &aEdge);
+ fLeftStart = rTempPoint.getCut();
+
+ // add left bow
+ aRetval.appendBezierSegment(aLeftPart.getControlPointA(), aLeftPart.getControlPointB(), rTempPoint.getPoint());
+ }
+
+ // add remaining bow
+ aRetval.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint());
+ }
+ else
+ {
+ // add all points targeted to be at this index
+ while(nNewInd < nTempPointCount && rTempPoints[nNewInd].getIndex() == a)
+ {
+ const temporaryPoint& rTempPoint = rTempPoints[nNewInd++];
+ const B2DPoint aNewPoint(rTempPoint.getPoint());
+
+ // do not add points double
+ if(!aRetval.getB2DPoint(aRetval.count() - 1L).equal(aNewPoint))
+ {
+ aRetval.append(aNewPoint);
+ }
+ }
+
+ // add edge end point
+ aRetval.append(aEdge.getEndPoint());
+ }
+ }
+ }
+
+ if(rCandidate.isClosed())
+ {
+ // set closed flag and correct last point (which is added double now).
+ tools::closeWithGeometryChange(aRetval);
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void adaptAndTransferCutsWithBezierSegment(
+ const temporaryPointVector& rPointVector, const B2DPolygon& rPolygon,
+ sal_uInt32 nInd, temporaryPointVector& rTempPoints)
+ {
+ // assuming that the subdivision to create rPolygon used aequidistant pieces
+ // (as in adaptiveSubdivideByCount) it is now possible to calculate back the
+ // cut positions in the polygon to relative cut positions on the original bezier
+ // segment.
+ const sal_uInt32 nTempPointCount(rPointVector.size());
+ const sal_uInt32 nEdgeCount(rPolygon.count() ? rPolygon.count() - 1L : 0L);
+
+ if(nTempPointCount && nEdgeCount)
+ {
+ for(sal_uInt32 a(0L); a < nTempPointCount; a++)
+ {
+ const temporaryPoint& rTempPoint = rPointVector[a];
+ const double fCutPosInPolygon((double)rTempPoint.getIndex() + rTempPoint.getCut());
+ const double fRelativeCutPos(fCutPosInPolygon / (double)nEdgeCount);
+ rTempPoints.push_back(temporaryPoint(rTempPoint.getPoint(), nInd, fRelativeCutPos));
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace
+ {
+ ////////////////////////////////////////////////////////////////////////////////
+ // predefines for calls to this methods before method implementation
+
+ void findCuts(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints);
+ void findTouches(const B2DPolygon& rEdgePolygon, const B2DPolygon& rPointPolygon, temporaryPointVector& rTempPoints);
+ void findCuts(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB);
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void findEdgeCutsTwoEdges(
+ const B2DPoint& rCurrA, const B2DPoint& rNextA,
+ const B2DPoint& rCurrB, const B2DPoint& rNextB,
+ sal_uInt32 nIndA, sal_uInt32 nIndB,
+ temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
+ {
+ // no null length edges
+ if(!(rCurrA.equal(rNextA) || rCurrB.equal(rNextB)))
+ {
+ // no common start/end points, this can be no cuts
+ if(!(rCurrB.equal(rCurrA) || rCurrB.equal(rNextA) || rNextB.equal(rCurrA) || rNextB.equal(rNextA)))
+ {
+ const B2DVector aVecA(rNextA - rCurrA);
+ const B2DVector aVecB(rNextB - rCurrB);
+ double fCut(aVecA.cross(aVecB));
+
+ if(!fTools::equalZero(fCut))
+ {
+ const double fZero(0.0);
+ const double fOne(1.0);
+ fCut = (aVecB.getY() * (rCurrB.getX() - rCurrA.getX()) + aVecB.getX() * (rCurrA.getY() - rCurrB.getY())) / fCut;
+
+ if(fTools::more(fCut, fZero) && fTools::less(fCut, fOne))
+ {
+ // it's a candidate, but also need to test parameter value of cut on line 2
+ double fCut2;
+
+ // choose the more precise version
+ if(fabs(aVecB.getX()) > fabs(aVecB.getY()))
+ {
+ fCut2 = (rCurrA.getX() + (fCut * aVecA.getX()) - rCurrB.getX()) / aVecB.getX();
+ }
+ else
+ {
+ fCut2 = (rCurrA.getY() + (fCut * aVecA.getY()) - rCurrB.getY()) / aVecB.getY();
+ }
+
+ if(fTools::more(fCut2, fZero) && fTools::less(fCut2, fOne))
+ {
+ // cut is in range, add point. Two edges can have only one cut, but
+ // add a cut point to each list. The lists may be the same for
+ // self intersections.
+ const B2DPoint aCutPoint(interpolate(rCurrA, rNextA, fCut));
+ rTempPointsA.push_back(temporaryPoint(aCutPoint, nIndA, fCut));
+ rTempPointsB.push_back(temporaryPoint(aCutPoint, nIndB, fCut2));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void findCutsAndTouchesAndCommonForBezier(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
+ {
+ // #i76891#
+ // This new method is necessary since in findEdgeCutsBezierAndEdge and in findEdgeCutsTwoBeziers
+ // it is not sufficient to use findCuts() recursively. This will indeed find the cuts between the
+ // segments of the two temporarily adaptive subdivided bezier segments, but not the touches or
+ // equal points of them.
+ // It would be possible to find the toches using findTouches(), but at last with commpn points
+ // the adding of cut points (temporary points) would fail. But for these temporarily adaptive
+ // subdivided bezier segments, common points may be not very likely, but the bug shows that it
+ // happens.
+ // Touch points are a little bit more likely than common points. All in all it is best to use
+ // a specialized method here which can profit from knowing that it is working on a special
+ // family of B2DPolygons: no curve segments included and not closed.
+ OSL_ENSURE(!rCandidateA.areControlPointsUsed() && !rCandidateB.areControlPointsUsed(), "findCutsAndTouchesAndCommonForBezier only works with subdivided polygons (!)");
+ OSL_ENSURE(!rCandidateA.isClosed() && !rCandidateB.isClosed(), "findCutsAndTouchesAndCommonForBezier only works with opened polygons (!)");
+ const sal_uInt32 nPointCountA(rCandidateA.count());
+ const sal_uInt32 nPointCountB(rCandidateB.count());
+
+ if(nPointCountA > 1 && nPointCountB > 1)
+ {
+ const sal_uInt32 nEdgeCountA(nPointCountA - 1);
+ const sal_uInt32 nEdgeCountB(nPointCountB - 1);
+ B2DPoint aCurrA(rCandidateA.getB2DPoint(0L));
+
+ for(sal_uInt32 a(0L); a < nEdgeCountA; a++)
+ {
+ const B2DPoint aNextA(rCandidateA.getB2DPoint(a + 1L));
+ const B2DRange aRangeA(aCurrA, aNextA);
+ B2DPoint aCurrB(rCandidateB.getB2DPoint(0L));
+
+ for(sal_uInt32 b(0L); b < nEdgeCountB; b++)
+ {
+ const B2DPoint aNextB(rCandidateB.getB2DPoint(b + 1L));
+ const B2DRange aRangeB(aCurrB, aNextB);
+
+ if(aRangeA.overlaps(aRangeB))
+ {
+ // no null length edges
+ if(!(aCurrA.equal(aNextA) || aCurrB.equal(aNextB)))
+ {
+ const B2DVector aVecA(aNextA - aCurrA);
+ const B2DVector aVecB(aNextB - aCurrB);
+ double fCutA(aVecA.cross(aVecB));
+
+ if(!fTools::equalZero(fCutA))
+ {
+ const double fZero(0.0);
+ const double fOne(1.0);
+ fCutA = (aVecB.getY() * (aCurrB.getX() - aCurrA.getX()) + aVecB.getX() * (aCurrA.getY() - aCurrB.getY())) / fCutA;
+
+ // use range [0.0 .. 1.0[, thus in the loop, all direct aCurrA cuts will be registered
+ // as 0.0 cut. The 1.0 cut will be registered in the next loop step
+ if(fTools::moreOrEqual(fCutA, fZero) && fTools::less(fCutA, fOne))
+ {
+ // it's a candidate, but also need to test parameter value of cut on line 2
+ double fCutB;
+
+ // choose the more precise version
+ if(fabs(aVecB.getX()) > fabs(aVecB.getY()))
+ {
+ fCutB = (aCurrA.getX() + (fCutA * aVecA.getX()) - aCurrB.getX()) / aVecB.getX();
+ }
+ else
+ {
+ fCutB = (aCurrA.getY() + (fCutA * aVecA.getY()) - aCurrB.getY()) / aVecB.getY();
+ }
+
+ // use range [0.0 .. 1.0[, thus in the loop, all direct aCurrA cuts will be registered
+ // as 0.0 cut. The 1.0 cut will be registered in the next loop step
+ if(fTools::moreOrEqual(fCutB, fZero) && fTools::less(fCutB, fOne))
+ {
+ // cut is in both ranges. Add points for A and B
+ if(fTools::equalZero(fCutA))
+ {
+ // ignore for start point in first edge; this is handled
+ // by outer methods and would just produce a double point
+ if(a)
+ {
+ rTempPointsA.push_back(temporaryPoint(aCurrA, a, 0.0));
+ }
+ }
+ else
+ {
+ const B2DPoint aCutPoint(interpolate(aCurrA, aNextA, fCutA));
+ rTempPointsA.push_back(temporaryPoint(aCutPoint, a, fCutA));
+ }
+
+ if(fTools::equalZero(fCutB))
+ {
+ // ignore for start point in first edge; this is handled
+ // by outer methods and would just produce a double point
+ if(b)
+ {
+ rTempPointsB.push_back(temporaryPoint(aCurrB, b, 0.0));
+ }
+ }
+ else
+ {
+ const B2DPoint aCutPoint(interpolate(aCurrB, aNextB, fCutB));
+ rTempPointsB.push_back(temporaryPoint(aCutPoint, b, fCutB));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // prepare next step
+ aCurrB = aNextB;
+ }
+
+ // prepare next step
+ aCurrA = aNextA;
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void findEdgeCutsBezierAndEdge(
+ const B2DCubicBezier& rCubicA,
+ const B2DPoint& rCurrB, const B2DPoint& rNextB,
+ sal_uInt32 nIndA, sal_uInt32 nIndB,
+ temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
+ {
+ // find all cuts between given bezier segment and edge. Add an entry to the tempPoints
+ // for each common point with the cut value describing the relative position on given
+ // bezier segment and edge.
+ B2DPolygon aTempPolygonA;
+ B2DPolygon aTempPolygonEdge;
+ temporaryPointVector aTempPointVectorA;
+ temporaryPointVector aTempPointVectorEdge;
+
+ // create subdivided polygons and find cuts between them
+ // Keep adaptiveSubdivideByCount due to needed quality
+ aTempPolygonA.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
+ aTempPolygonA.append(rCubicA.getStartPoint());
+ rCubicA.adaptiveSubdivideByCount(aTempPolygonA, SUBDIVIDE_FOR_CUT_TEST_COUNT);
+ aTempPolygonEdge.append(rCurrB);
+ aTempPolygonEdge.append(rNextB);
+
+ // #i76891# using findCuts recursively is not sufficient here
+ findCutsAndTouchesAndCommonForBezier(aTempPolygonA, aTempPolygonEdge, aTempPointVectorA, aTempPointVectorEdge);
+
+ if(aTempPointVectorA.size())
+ {
+ // adapt tempVector entries to segment
+ adaptAndTransferCutsWithBezierSegment(aTempPointVectorA, aTempPolygonA, nIndA, rTempPointsA);
+ }
+
+ // append remapped tempVector entries for edge to tempPoints for edge
+ for(sal_uInt32 a(0L); a < aTempPointVectorEdge.size(); a++)
+ {
+ const temporaryPoint& rTempPoint = aTempPointVectorEdge[a];
+ rTempPointsB.push_back(temporaryPoint(rTempPoint.getPoint(), nIndB, rTempPoint.getCut()));
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void findEdgeCutsTwoBeziers(
+ const B2DCubicBezier& rCubicA,
+ const B2DCubicBezier& rCubicB,
+ sal_uInt32 nIndA, sal_uInt32 nIndB,
+ temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
+ {
+ // find all cuts between the two given bezier segments. Add an entry to the tempPoints
+ // for each common point with the cut value describing the relative position on given
+ // bezier segments.
+ B2DPolygon aTempPolygonA;
+ B2DPolygon aTempPolygonB;
+ temporaryPointVector aTempPointVectorA;
+ temporaryPointVector aTempPointVectorB;
+
+ // create subdivided polygons and find cuts between them
+ // Keep adaptiveSubdivideByCount due to needed quality
+ aTempPolygonA.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
+ aTempPolygonA.append(rCubicA.getStartPoint());
+ rCubicA.adaptiveSubdivideByCount(aTempPolygonA, SUBDIVIDE_FOR_CUT_TEST_COUNT);
+ aTempPolygonB.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
+ aTempPolygonB.append(rCubicB.getStartPoint());
+ rCubicB.adaptiveSubdivideByCount(aTempPolygonB, SUBDIVIDE_FOR_CUT_TEST_COUNT);
+
+ // #i76891# using findCuts recursively is not sufficient here
+ findCutsAndTouchesAndCommonForBezier(aTempPolygonA, aTempPolygonB, aTempPointVectorA, aTempPointVectorB);
+
+ if(aTempPointVectorA.size())
+ {
+ // adapt tempVector entries to segment
+ adaptAndTransferCutsWithBezierSegment(aTempPointVectorA, aTempPolygonA, nIndA, rTempPointsA);
+ }
+
+ if(aTempPointVectorB.size())
+ {
+ // adapt tempVector entries to segment
+ adaptAndTransferCutsWithBezierSegment(aTempPointVectorB, aTempPolygonB, nIndB, rTempPointsB);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void findEdgeCutsOneBezier(
+ const B2DCubicBezier& rCubicA,
+ sal_uInt32 nInd, temporaryPointVector& rTempPoints)
+ {
+ // avoid expensive part of this method if possible
+ // TODO: use hasAnyExtremum() method instead when it becomes available
+ double fDummy;
+ const bool bHasAnyExtremum = rCubicA.getMinimumExtremumPosition( fDummy );
+ if( !bHasAnyExtremum )
+ return;
+
+ // find all self-intersections on the given bezier segment. Add an entry to the tempPoints
+ // for each self intersection point with the cut value describing the relative position on given
+ // bezier segment.
+ B2DPolygon aTempPolygon;
+ temporaryPointVector aTempPointVector;
+
+ // create subdivided polygon and find cuts on it
+ // Keep adaptiveSubdivideByCount due to needed quality
+ aTempPolygon.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
+ aTempPolygon.append(rCubicA.getStartPoint());
+ rCubicA.adaptiveSubdivideByCount(aTempPolygon, SUBDIVIDE_FOR_CUT_TEST_COUNT);
+ findCuts(aTempPolygon, aTempPointVector);
+
+ if(aTempPointVector.size())
+ {
+ // adapt tempVector entries to segment
+ adaptAndTransferCutsWithBezierSegment(aTempPointVector, aTempPolygon, nInd, rTempPoints);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void findCuts(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints)
+ {
+ // find out if there are edges with intersections (self-cuts). If yes, add
+ // entries to rTempPoints accordingly
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+
+ if(nEdgeCount)
+ {
+ const bool bCurvesInvolved(rCandidate.areControlPointsUsed());
+
+ if(bCurvesInvolved)
+ {
+ B2DCubicBezier aCubicA;
+ B2DCubicBezier aCubicB;
+
+ for(sal_uInt32 a(0L); a < nEdgeCount - 1L; a++)
+ {
+ rCandidate.getBezierSegment(a, aCubicA);
+ aCubicA.testAndSolveTrivialBezier();
+ const bool bEdgeAIsCurve(aCubicA.isBezier());
+ const B2DRange aRangeA(aCubicA.getRange());
+
+ if(bEdgeAIsCurve)
+ {
+ // curved segments may have self-intersections, do not forget those (!)
+ findEdgeCutsOneBezier(aCubicA, a, rTempPoints);
+ }
+
+ for(sal_uInt32 b(a + 1L); b < nEdgeCount; b++)
+ {
+ rCandidate.getBezierSegment(b, aCubicB);
+ aCubicB.testAndSolveTrivialBezier();
+ const bool bEdgeBIsCurve(aCubicB.isBezier());
+ const B2DRange aRangeB(aCubicB.getRange());
+
+ // only overlapping segments need to be tested
+ // consecutive segments touch of course
+ bool bOverlap = false;
+ if( b > a+1)
+ bOverlap = aRangeA.overlaps(aRangeB);
+ else
+ bOverlap = aRangeA.overlapsMore(aRangeB);
+ if( bOverlap)
+ {
+ if(bEdgeAIsCurve && bEdgeBIsCurve)
+ {
+ // test for bezier-bezier cuts
+ findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, rTempPoints, rTempPoints);
+ }
+ else if(bEdgeAIsCurve)
+ {
+ // test for bezier-edge cuts
+ findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, rTempPoints, rTempPoints);
+ }
+ else if(bEdgeBIsCurve)
+ {
+ // test for bezier-edge cuts
+ findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, rTempPoints, rTempPoints);
+ }
+ else
+ {
+ // test for simple edge-edge cuts
+ findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(),
+ a, b, rTempPoints, rTempPoints);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ B2DPoint aCurrA(rCandidate.getB2DPoint(0L));
+
+ for(sal_uInt32 a(0L); a < nEdgeCount - 1L; a++)
+ {
+ const B2DPoint aNextA(rCandidate.getB2DPoint(a + 1L == nPointCount ? 0L : a + 1L));
+ const B2DRange aRangeA(aCurrA, aNextA);
+ B2DPoint aCurrB(rCandidate.getB2DPoint(a + 1L));
+
+ for(sal_uInt32 b(a + 1L); b < nEdgeCount; b++)
+ {
+ const B2DPoint aNextB(rCandidate.getB2DPoint(b + 1L == nPointCount ? 0L : b + 1L));
+ const B2DRange aRangeB(aCurrB, aNextB);
+
+ // consecutive segments touch of course
+ bool bOverlap = false;
+ if( b > a+1)
+ bOverlap = aRangeA.overlaps(aRangeB);
+ else
+ bOverlap = aRangeA.overlapsMore(aRangeB);
+ if( bOverlap)
+ {
+ findEdgeCutsTwoEdges(aCurrA, aNextA, aCurrB, aNextB, a, b, rTempPoints, rTempPoints);
+ }
+
+ // prepare next step
+ aCurrB = aNextB;
+ }
+
+ // prepare next step
+ aCurrA = aNextA;
+ }
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace
+ {
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void findTouchesOnEdge(
+ const B2DPoint& rCurr, const B2DPoint& rNext, const B2DPolygon& rPointPolygon,
+ sal_uInt32 nInd, temporaryPointVector& rTempPoints)
+ {
+ // find out if points from rPointPolygon are positioned on given edge. If Yes, add
+ // points there to represent touches (which may be enter or leave nodes later).
+ const sal_uInt32 nPointCount(rPointPolygon.count());
+
+ if(nPointCount)
+ {
+ const B2DRange aRange(rCurr, rNext);
+ const B2DVector aEdgeVector(rNext - rCurr);
+ B2DVector aNormalizedEdgeVector(aEdgeVector);
+ aNormalizedEdgeVector.normalize();
+ bool bTestUsingX(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY()));
+
+ for(sal_uInt32 a(0L); a < nPointCount; a++)
+ {
+ const B2DPoint aTestPoint(rPointPolygon.getB2DPoint(a));
+
+ if(aRange.isInside(aTestPoint))
+ {
+ if(!aTestPoint.equal(rCurr) && !aTestPoint.equal(rNext))
+ {
+ const B2DVector aTestVector(aTestPoint - rCurr);
+
+ if(areParallel(aNormalizedEdgeVector, aTestVector))
+ {
+ const double fCut((bTestUsingX)
+ ? aTestVector.getX() / aEdgeVector.getX()
+ : aTestVector.getY() / aEdgeVector.getY());
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ if(fTools::more(fCut, fZero) && fTools::less(fCut, fOne))
+ {
+ rTempPoints.push_back(temporaryPoint(aTestPoint, nInd, fCut));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void findTouchesOnCurve(
+ const B2DCubicBezier& rCubicA, const B2DPolygon& rPointPolygon,
+ sal_uInt32 nInd, temporaryPointVector& rTempPoints)
+ {
+ // find all points from rPointPolygon which touch the given bezier segment. Add an entry
+ // for each touch to the given pointVector. The cut for that entry is the relative position on
+ // the given bezier segment.
+ B2DPolygon aTempPolygon;
+ temporaryPointVector aTempPointVector;
+
+ // create subdivided polygon and find cuts on it
+ // Keep adaptiveSubdivideByCount due to needed quality
+ aTempPolygon.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
+ aTempPolygon.append(rCubicA.getStartPoint());
+ rCubicA.adaptiveSubdivideByCount(aTempPolygon, SUBDIVIDE_FOR_CUT_TEST_COUNT);
+ findTouches(aTempPolygon, rPointPolygon, aTempPointVector);
+
+ if(aTempPointVector.size())
+ {
+ // adapt tempVector entries to segment
+ adaptAndTransferCutsWithBezierSegment(aTempPointVector, aTempPolygon, nInd, rTempPoints);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void findTouches(const B2DPolygon& rEdgePolygon, const B2DPolygon& rPointPolygon, temporaryPointVector& rTempPoints)
+ {
+ // find out if points from rPointPolygon touch edges from rEdgePolygon. If yes,
+ // add entries to rTempPoints
+ const sal_uInt32 nPointCount(rPointPolygon.count());
+ const sal_uInt32 nEdgePointCount(rEdgePolygon.count());
+
+ if(nPointCount && nEdgePointCount)
+ {
+ const sal_uInt32 nEdgeCount(rEdgePolygon.isClosed() ? nEdgePointCount : nEdgePointCount - 1L);
+ B2DPoint aCurr(rEdgePolygon.getB2DPoint(0));
+
+ for(sal_uInt32 a(0L); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nEdgePointCount);
+ const B2DPoint aNext(rEdgePolygon.getB2DPoint(nNextIndex));
+
+ if(!aCurr.equal(aNext))
+ {
+ bool bHandleAsSimpleEdge(true);
+
+ if(rEdgePolygon.areControlPointsUsed())
+ {
+ const B2DPoint aNextControlPoint(rEdgePolygon.getNextControlPoint(a));
+ const B2DPoint aPrevControlPoint(rEdgePolygon.getPrevControlPoint(nNextIndex));
+ const bool bEdgeIsCurve(!aNextControlPoint.equal(aCurr) || !aPrevControlPoint.equal(aNext));
+
+ if(bEdgeIsCurve)
+ {
+ bHandleAsSimpleEdge = false;
+ const B2DCubicBezier aCubicA(aCurr, aNextControlPoint, aPrevControlPoint, aNext);
+ findTouchesOnCurve(aCubicA, rPointPolygon, a, rTempPoints);
+ }
+ }
+
+ if(bHandleAsSimpleEdge)
+ {
+ findTouchesOnEdge(aCurr, aNext, rPointPolygon, a, rTempPoints);
+ }
+ }
+
+ // next step
+ aCurr = aNext;
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace
+ {
+ ////////////////////////////////////////////////////////////////////////////////
+
+ void findCuts(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
+ {
+ // find out if edges from both polygons cut. If so, add entries to rTempPoints which
+ // should be added to the polygons accordingly
+ const sal_uInt32 nPointCountA(rCandidateA.count());
+ const sal_uInt32 nPointCountB(rCandidateB.count());
+
+ if(nPointCountA && nPointCountB)
+ {
+ const sal_uInt32 nEdgeCountA(rCandidateA.isClosed() ? nPointCountA : nPointCountA - 1L);
+ const sal_uInt32 nEdgeCountB(rCandidateB.isClosed() ? nPointCountB : nPointCountB - 1L);
+
+ if(nEdgeCountA && nEdgeCountB)
+ {
+ const bool bCurvesInvolved(rCandidateA.areControlPointsUsed() || rCandidateB.areControlPointsUsed());
+
+ if(bCurvesInvolved)
+ {
+ B2DCubicBezier aCubicA;
+ B2DCubicBezier aCubicB;
+
+ for(sal_uInt32 a(0L); a < nEdgeCountA; a++)
+ {
+ rCandidateA.getBezierSegment(a, aCubicA);
+ aCubicA.testAndSolveTrivialBezier();
+ const bool bEdgeAIsCurve(aCubicA.isBezier());
+ const B2DRange aRangeA(aCubicA.getRange());
+
+ for(sal_uInt32 b(0L); b < nEdgeCountB; b++)
+ {
+ rCandidateB.getBezierSegment(b, aCubicB);
+ aCubicB.testAndSolveTrivialBezier();
+ const bool bEdgeBIsCurve(aCubicB.isBezier());
+ const B2DRange aRangeB(aCubicB.getRange());
+
+ // consecutive segments touch of course
+ bool bOverlap = false;
+ if( b > a+1)
+ bOverlap = aRangeA.overlaps(aRangeB);
+ else
+ bOverlap = aRangeA.overlapsMore(aRangeB);
+ if( bOverlap)
+ {
+ if(bEdgeAIsCurve && bEdgeBIsCurve)
+ {
+ // test for bezier-bezier cuts
+ findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, rTempPointsA, rTempPointsB);
+ }
+ else if(bEdgeAIsCurve)
+ {
+ // test for bezier-edge cuts
+ findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, rTempPointsA, rTempPointsB);
+ }
+ else if(bEdgeBIsCurve)
+ {
+ // test for bezier-edge cuts
+ findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, rTempPointsB, rTempPointsA);
+ }
+ else
+ {
+ // test for simple edge-edge cuts
+ findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(),
+ a, b, rTempPointsA, rTempPointsB);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ B2DPoint aCurrA(rCandidateA.getB2DPoint(0L));
+
+ for(sal_uInt32 a(0L); a < nEdgeCountA; a++)
+ {
+ const B2DPoint aNextA(rCandidateA.getB2DPoint(a + 1L == nPointCountA ? 0L : a + 1L));
+ const B2DRange aRangeA(aCurrA, aNextA);
+ B2DPoint aCurrB(rCandidateB.getB2DPoint(0L));
+
+ for(sal_uInt32 b(0L); b < nEdgeCountB; b++)
+ {
+ const B2DPoint aNextB(rCandidateB.getB2DPoint(b + 1L == nPointCountB ? 0L : b + 1L));
+ const B2DRange aRangeB(aCurrB, aNextB);
+
+ // consecutive segments touch of course
+ bool bOverlap = false;
+ if( b > a+1)
+ bOverlap = aRangeA.overlaps(aRangeB);
+ else
+ bOverlap = aRangeA.overlapsMore(aRangeB);
+ if( bOverlap)
+ {
+ // test for simple edge-edge cuts
+ findEdgeCutsTwoEdges(aCurrA, aNextA, aCurrB, aNextB, a, b, rTempPointsA, rTempPointsB);
+ }
+
+ // prepare next step
+ aCurrB = aNextB;
+ }
+
+ // prepare next step
+ aCurrA = aNextA;
+ }
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace tools
+ {
+ ////////////////////////////////////////////////////////////////////////////////
+
+ B2DPolygon addPointsAtCutsAndTouches(const B2DPolygon& rCandidate)
+ {
+ if(rCandidate.count())
+ {
+ temporaryPointVector aTempPoints;
+
+ findTouches(rCandidate, rCandidate, aTempPoints);
+ findCuts(rCandidate, aTempPoints);
+
+ return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints);
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ B2DPolyPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rCandidate, bool bSelfIntersections)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nCount)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(1L == nCount)
+ {
+ if(bSelfIntersections)
+ {
+ // remove self intersections
+ aRetval.append(addPointsAtCutsAndTouches(rCandidate.getB2DPolygon(0L)));
+ }
+ else
+ {
+ // copy source
+ aRetval = rCandidate;
+ }
+ }
+ else
+ {
+ // first solve self cuts and self touches for all contained single polygons
+ temporaryPolygonData *pTempData = new temporaryPolygonData[nCount];
+ sal_uInt32 a, b;
+
+ for(a = 0L; a < nCount; a++)
+ {
+ if(bSelfIntersections)
+ {
+ // use polygons with solved self intersections
+ pTempData[a].setPolygon(addPointsAtCutsAndTouches(rCandidate.getB2DPolygon(a)));
+ }
+ else
+ {
+ // copy given polygons
+ pTempData[a].setPolygon(rCandidate.getB2DPolygon(a));
+ }
+ }
+
+ // now cuts and touches between the polygons
+ for(a = 0L; a < nCount; a++)
+ {
+ for(b = 0L; b < nCount; b++)
+ {
+ if(a != b)
+ {
+ // look for touches, compare each edge polygon to all other points
+ if(pTempData[a].getRange().overlaps(pTempData[b].getRange()))
+ {
+ findTouches(pTempData[a].getPolygon(), pTempData[b].getPolygon(), pTempData[a].getTemporaryPointVector());
+ }
+ }
+
+ if(a < b)
+ {
+ // look for cuts, compare each edge polygon to following ones
+ if(pTempData[a].getRange().overlaps(pTempData[b].getRange()))
+ {
+ findCuts(pTempData[a].getPolygon(), pTempData[b].getPolygon(), pTempData[a].getTemporaryPointVector(), pTempData[b].getTemporaryPointVector());
+ }
+ }
+ }
+ }
+
+ // consolidate the result
+ for(a = 0L; a < nCount; a++)
+ {
+ aRetval.append(mergeTemporaryPointsAndPolygon(pTempData[a].getPolygon(), pTempData[a].getTemporaryPointVector()));
+ }
+
+ delete[] pTempData;
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ B2DPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rMask, const B2DPolygon& rCandidate)
+ {
+ if(rCandidate.count())
+ {
+ temporaryPointVector aTempPoints;
+ temporaryPointVector aTempPointsUnused;
+
+ for(sal_uInt32 a(0L); a < rMask.count(); a++)
+ {
+ const B2DPolygon aPartMask(rMask.getB2DPolygon(a));
+
+ findTouches(rCandidate, aPartMask, aTempPoints);
+ findCuts(rCandidate, aPartMask, aTempPoints, aTempPointsUnused);
+ }
+
+ return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints);
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ B2DPolyPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rMask, const B2DPolyPolygon& rCandidate)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(addPointsAtCutsAndTouches(rMask, rCandidate.getB2DPolygon(a)));
+ }
+
+ return aRetval;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate, const B2DPoint& rStart, const B2DPoint& rEnd)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nCount && !rStart.equal(rEnd))
+ {
+ const B2DRange aPolygonRange(rCandidate.getB2DRange());
+ const B2DRange aEdgeRange(rStart, rEnd);
+
+ if(aPolygonRange.overlaps(aEdgeRange))
+ {
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nCount : nCount - 1);
+ temporaryPointVector aTempPoints;
+ temporaryPointVector aUnusedTempPoints;
+ B2DCubicBezier aCubic;
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ rCandidate.getBezierSegment(a, aCubic);
+ B2DRange aCubicRange(aCubic.getStartPoint(), aCubic.getEndPoint());
+
+ if(aCubic.isBezier())
+ {
+ aCubicRange.expand(aCubic.getControlPointA());
+ aCubicRange.expand(aCubic.getControlPointB());
+
+ if(aCubicRange.overlaps(aEdgeRange))
+ {
+ findEdgeCutsBezierAndEdge(aCubic, rStart, rEnd, a, 0, aTempPoints, aUnusedTempPoints);
+ }
+ }
+ else
+ {
+ if(aCubicRange.overlaps(aEdgeRange))
+ {
+ findEdgeCutsTwoEdges(aCubic.getStartPoint(), aCubic.getEndPoint(), rStart, rEnd, a, 0, aTempPoints, aUnusedTempPoints);
+ }
+ }
+ }
+
+ return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints);
+ }
+ }
+
+ return rCandidate;
+ }
+
+ B2DPolyPolygon addPointsAtCuts(const B2DPolyPolygon& rCandidate, const B2DPoint& rStart, const B2DPoint& rEnd)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0); a < rCandidate.count(); a++)
+ {
+ aRetval.append(addPointsAtCuts(rCandidate.getB2DPolygon(a), rStart, rEnd));
+ }
+
+ return aRetval;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate, const B2DPolyPolygon& rPolyMask)
+ {
+ const sal_uInt32 nCountA(rCandidate.count());
+ const sal_uInt32 nCountM(rPolyMask.count());
+
+ if(nCountA && nCountM)
+ {
+ const B2DRange aRangeA(rCandidate.getB2DRange());
+ const B2DRange aRangeM(rPolyMask.getB2DRange());
+
+ if(aRangeA.overlaps(aRangeM))
+ {
+ const sal_uInt32 nEdgeCountA(rCandidate.isClosed() ? nCountA : nCountA - 1);
+ temporaryPointVector aTempPointsA;
+ temporaryPointVector aUnusedTempPointsB;
+
+ for(sal_uInt32 m(0); m < nCountM; m++)
+ {
+ const B2DPolygon aMask(rPolyMask.getB2DPolygon(m));
+ const sal_uInt32 nCountB(aMask.count());
+
+ if(nCountB)
+ {
+ B2DCubicBezier aCubicA;
+ B2DCubicBezier aCubicB;
+
+ for(sal_uInt32 a(0); a < nEdgeCountA; a++)
+ {
+ rCandidate.getBezierSegment(a, aCubicA);
+ const bool bCubicAIsCurve(aCubicA.isBezier());
+ B2DRange aCubicRangeA(aCubicA.getStartPoint(), aCubicA.getEndPoint());
+
+ if(bCubicAIsCurve)
+ {
+ aCubicRangeA.expand(aCubicA.getControlPointA());
+ aCubicRangeA.expand(aCubicA.getControlPointB());
+ }
+
+ for(sal_uInt32 b(0); b < nCountB; b++)
+ {
+ aMask.getBezierSegment(b, aCubicB);
+ const bool bCubicBIsCurve(aCubicB.isBezier());
+ B2DRange aCubicRangeB(aCubicB.getStartPoint(), aCubicB.getEndPoint());
+
+ if(bCubicBIsCurve)
+ {
+ aCubicRangeB.expand(aCubicB.getControlPointA());
+ aCubicRangeB.expand(aCubicB.getControlPointB());
+ }
+
+ if(aCubicRangeA.overlaps(aCubicRangeB))
+ {
+ if(bCubicAIsCurve && bCubicBIsCurve)
+ {
+ findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, aTempPointsA, aUnusedTempPointsB);
+ }
+ else if(bCubicAIsCurve)
+ {
+ findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, aTempPointsA, aUnusedTempPointsB);
+ }
+ else if(bCubicBIsCurve)
+ {
+ findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, aUnusedTempPointsB, aTempPointsA);
+ }
+ else
+ {
+ findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, aTempPointsA, aUnusedTempPointsB);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return mergeTemporaryPointsAndPolygon(rCandidate, aTempPointsA);
+ }
+ }
+
+ return rCandidate;
+ }
+
+ B2DPolyPolygon addPointsAtCuts(const B2DPolyPolygon& rCandidate, const B2DPolyPolygon& rMask)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0); a < rCandidate.count(); a++)
+ {
+ aRetval.append(addPointsAtCuts(rCandidate.getB2DPolygon(a), rMask));
+ }
+
+ return aRetval;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+
+ } // end of namespace tools
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+// eof
diff --git a/basegfx/source/polygon/b2dpolygontools.cxx b/basegfx/source/polygon/b2dpolygontools.cxx
new file mode 100644
index 000000000000..28e5bb1f430d
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolygontools.cxx
@@ -0,0 +1,3594 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/point/b3dpoint.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/curve/b2dbeziertools.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <osl/mutex.hxx>
+
+#include <numeric>
+#include <limits>
+
+// #i37443#
+#define ANGLE_BOUND_START_VALUE (2.25)
+#define ANGLE_BOUND_MINIMUM_VALUE (0.1)
+#define COUNT_SUBDIVIDE_DEFAULT (4L)
+#ifdef DBG_UTIL
+static double fAngleBoundStartValue = ANGLE_BOUND_START_VALUE;
+#endif
+#define STEPSPERQUARTER (3)
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace tools
+ {
+ void openWithGeometryChange(B2DPolygon& rCandidate)
+ {
+ if(rCandidate.isClosed())
+ {
+ if(rCandidate.count())
+ {
+ rCandidate.append(rCandidate.getB2DPoint(0));
+
+ if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(0))
+ {
+ rCandidate.setPrevControlPoint(rCandidate.count() - 1, rCandidate.getPrevControlPoint(0));
+ rCandidate.resetPrevControlPoint(0);
+ }
+ }
+
+ rCandidate.setClosed(false);
+ }
+ }
+
+ void closeWithGeometryChange(B2DPolygon& rCandidate)
+ {
+ if(!rCandidate.isClosed())
+ {
+ while(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
+ {
+ if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(rCandidate.count() - 1))
+ {
+ rCandidate.setPrevControlPoint(0, rCandidate.getPrevControlPoint(rCandidate.count() - 1));
+ }
+
+ rCandidate.remove(rCandidate.count() - 1);
+ }
+
+ rCandidate.setClosed(true);
+ }
+ }
+
+ void checkClosed(B2DPolygon& rCandidate)
+ {
+ // #i80172# Removed unnecessary assertion
+ // OSL_ENSURE(!rCandidate.isClosed(), "checkClosed: already closed (!)");
+
+ if(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
+ {
+ closeWithGeometryChange(rCandidate);
+ }
+ }
+
+ // Get successor and predecessor indices. Returning the same index means there
+ // is none. Same for successor.
+ sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
+
+ if(nIndex)
+ {
+ return nIndex - 1L;
+ }
+ else if(rCandidate.count())
+ {
+ return rCandidate.count() - 1L;
+ }
+ else
+ {
+ return nIndex;
+ }
+ }
+
+ sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
+
+ if(nIndex + 1L < rCandidate.count())
+ {
+ return nIndex + 1L;
+ }
+ else if(nIndex + 1L == rCandidate.count())
+ {
+ return 0L;
+ }
+ else
+ {
+ return nIndex;
+ }
+ }
+
+ B2VectorOrientation getOrientation(const B2DPolygon& rCandidate)
+ {
+ B2VectorOrientation eRetval(ORIENTATION_NEUTRAL);
+
+ if(rCandidate.count() > 2L || rCandidate.areControlPointsUsed())
+ {
+ const double fSignedArea(getSignedArea(rCandidate));
+
+ if(fTools::equalZero(fSignedArea))
+ {
+ // ORIENTATION_NEUTRAL, already set
+ }
+ if(fSignedArea > 0.0)
+ {
+ eRetval = ORIENTATION_POSITIVE;
+ }
+ else if(fSignedArea < 0.0)
+ {
+ eRetval = ORIENTATION_NEGATIVE;
+ }
+ }
+
+ return eRetval;
+ }
+
+ B2VectorContinuity getContinuityInPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ return rCandidate.getContinuityInPoint(nIndex);
+ }
+
+ B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ B2DPolygon aRetval;
+
+ if(nPointCount)
+ {
+ // prepare edge-oriented loop
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rCandidate.getB2DPoint(0));
+
+ // perf: try to avoid too many realloctions by guessing the result's pointcount
+ aRetval.reserve(nPointCount*4);
+
+ // add start point (always)
+ aRetval.append(aBezier.getStartPoint());
+
+ for(sal_uInt32 a(0L); a < nEdgeCount; a++)
+ {
+ // get next and control points
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+ aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
+ aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aBezier.testAndSolveTrivialBezier();
+
+ if(aBezier.isBezier())
+ {
+ // add curved edge and generate DistanceBound
+ double fBound(0.0);
+
+ if(0.0 == fDistanceBound)
+ {
+ // If not set, use B2DCubicBezier functionality to guess a rough value
+ const double fRoughLength((aBezier.getEdgeLength() + aBezier.getControlPolygonLength()) / 2.0);
+
+ // take 1/100th of the rough curve length
+ fBound = fRoughLength * 0.01;
+ }
+ else
+ {
+ // use given bound value
+ fBound = fDistanceBound;
+ }
+
+ // make sure bound value is not too small. The base units are 1/100th mm, thus
+ // just make sure it's not smaller then 1/100th of that
+ if(fBound < 0.01)
+ {
+ fBound = 0.01;
+ }
+
+ // call adaptive subdivide which adds edges to aRetval accordingly
+ aBezier.adaptiveSubdivideByDistance(aRetval, fBound);
+ }
+ else
+ {
+ // add non-curved edge
+ aRetval.append(aBezier.getEndPoint());
+ }
+
+ // prepare next step
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(rCandidate.isClosed())
+ {
+ // set closed flag and correct last point (which is added double now).
+ closeWithGeometryChange(aRetval);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolygon adaptiveSubdivideByAngle(const B2DPolygon& rCandidate, double fAngleBound)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ B2DPolygon aRetval;
+
+ if(nPointCount)
+ {
+ // prepare edge-oriented loop
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rCandidate.getB2DPoint(0));
+
+ // perf: try to avoid too many realloctions by guessing the result's pointcount
+ aRetval.reserve(nPointCount*4);
+
+ // add start point (always)
+ aRetval.append(aBezier.getStartPoint());
+
+ // #i37443# prepare convenient AngleBound if none was given
+ if(0.0 == fAngleBound)
+ {
+#ifdef DBG_UTIL
+ fAngleBound = fAngleBoundStartValue;
+#else
+ fAngleBound = ANGLE_BOUND_START_VALUE;
+#endif
+ }
+ else if(fTools::less(fAngleBound, ANGLE_BOUND_MINIMUM_VALUE))
+ {
+ fAngleBound = 0.1;
+ }
+
+ for(sal_uInt32 a(0L); a < nEdgeCount; a++)
+ {
+ // get next and control points
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+ aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
+ aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aBezier.testAndSolveTrivialBezier();
+
+ if(aBezier.isBezier())
+ {
+ // call adaptive subdivide
+ aBezier.adaptiveSubdivideByAngle(aRetval, fAngleBound, true);
+ }
+ else
+ {
+ // add non-curved edge
+ aRetval.append(aBezier.getEndPoint());
+ }
+
+ // prepare next step
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(rCandidate.isClosed())
+ {
+ // set closed flag and correct last point (which is added double now).
+ closeWithGeometryChange(aRetval);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolygon adaptiveSubdivideByCount(const B2DPolygon& rCandidate, sal_uInt32 nCount)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ B2DPolygon aRetval;
+
+ if(nPointCount)
+ {
+ // prepare edge-oriented loop
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rCandidate.getB2DPoint(0));
+
+ // perf: try to avoid too many realloctions by guessing the result's pointcount
+ aRetval.reserve(nPointCount*4);
+
+ // add start point (always)
+ aRetval.append(aBezier.getStartPoint());
+
+ // #i37443# prepare convenient count if none was given
+ if(0L == nCount)
+ {
+ nCount = COUNT_SUBDIVIDE_DEFAULT;
+ }
+
+ for(sal_uInt32 a(0L); a < nEdgeCount; a++)
+ {
+ // get next and control points
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+ aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
+ aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aBezier.testAndSolveTrivialBezier();
+
+ if(aBezier.isBezier())
+ {
+ // call adaptive subdivide
+ aBezier.adaptiveSubdivideByCount(aRetval, nCount);
+ }
+ else
+ {
+ // add non-curved edge
+ aRetval.append(aBezier.getEndPoint());
+ }
+
+ // prepare next step
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(rCandidate.isClosed())
+ {
+ // set closed flag and correct last point (which is added double now).
+ closeWithGeometryChange(aRetval);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ bool isInside(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder)
+ {
+ const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
+
+ if(bWithBorder && isPointOnPolygon(aCandidate, rPoint, true))
+ {
+ return true;
+ }
+ else
+ {
+ bool bRetval(false);
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(nPointCount)
+ {
+ B2DPoint aCurrentPoint(aCandidate.getB2DPoint(nPointCount - 1L));
+
+ for(sal_uInt32 a(0L); a < nPointCount; a++)
+ {
+ const B2DPoint aPreviousPoint(aCurrentPoint);
+ aCurrentPoint = aCandidate.getB2DPoint(a);
+
+ // cross-over in Y?
+ const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY()));
+ const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY()));
+
+ if(bCompYA != bCompYB)
+ {
+ // cross-over in X?
+ const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX()));
+ const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX()));
+
+ if(bCompXA == bCompXB)
+ {
+ if(bCompXA)
+ {
+ bRetval = !bRetval;
+ }
+ }
+ else
+ {
+ const double fCompare(
+ aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) *
+ (aPreviousPoint.getX() - aCurrentPoint.getX()) /
+ (aPreviousPoint.getY() - aCurrentPoint.getY()));
+
+ if(fTools::more(fCompare, rPoint.getX()))
+ {
+ bRetval = !bRetval;
+ }
+ }
+ }
+ }
+ }
+
+ return bRetval;
+ }
+ }
+
+ bool isInside(const B2DPolygon& rCandidate, const B2DPolygon& rPolygon, bool bWithBorder)
+ {
+ 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++)
+ {
+ const B2DPoint aTestPoint(aPolygon.getB2DPoint(a));
+
+ if(!isInside(aCandidate, aTestPoint, bWithBorder))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ B2DRange getRangeWithControlPoints(const B2DPolygon& rCandidate)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ B2DRange aRetval;
+
+ if(nPointCount)
+ {
+ const bool bControlPointsUsed(rCandidate.areControlPointsUsed());
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ aRetval.expand(rCandidate.getB2DPoint(a));
+
+ if(bControlPointsUsed)
+ {
+ aRetval.expand(rCandidate.getNextControlPoint(a));
+ aRetval.expand(rCandidate.getPrevControlPoint(a));
+ }
+ }
+ }
+
+ 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() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
+ double fRetval(0.0);
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(nPointCount > 2)
+ {
+ for(sal_uInt32 a(0L); a < nPointCount; a++)
+ {
+ const B2DPoint aPreviousPoint(aCandidate.getB2DPoint((!a) ? nPointCount - 1L : a - 1L));
+ const B2DPoint aCurrentPoint(aCandidate.getB2DPoint(a));
+
+ fRetval += aPreviousPoint.getX() * aCurrentPoint.getY();
+ fRetval -= aPreviousPoint.getY() * aCurrentPoint.getX();
+ }
+
+ 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;
+ }
+
+ double getArea(const B2DPolygon& rCandidate)
+ {
+ double fRetval(0.0);
+
+ if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
+ {
+ fRetval = getSignedArea(rCandidate);
+ const double fZero(0.0);
+
+ if(fTools::less(fRetval, fZero))
+ {
+ fRetval = -fRetval;
+ }
+ }
+
+ return fRetval;
+ }
+
+ double getEdgeLength(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ OSL_ENSURE(nIndex < nPointCount, "getEdgeLength: Access to polygon out of range (!)");
+ double fRetval(0.0);
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ 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();
+ }
+ }
+
+ return fRetval;
+ }
+
+ double getLength(const B2DPolygon& rCandidate)
+ {
+ double fRetval(0.0);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ 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;
+ }
+ }
+ }
+
+ return fRetval;
+ }
+
+ B2DPoint getPositionAbsolute(const B2DPolygon& rCandidate, double fDistance, double fLength)
+ {
+ B2DPoint aRetval;
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if( 1L == nPointCount )
+ {
+ // only one point (i.e. no edge) - simply take that point
+ aRetval = rCandidate.getB2DPoint(0);
+ }
+ else if(nPointCount > 1L)
+ {
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ sal_uInt32 nIndex(0L);
+ bool bIndexDone(false);
+
+ // get length if not given
+ if(fTools::equalZero(fLength))
+ {
+ fLength = getLength(rCandidate);
+ }
+
+ if(fTools::less(fDistance, 0.0))
+ {
+ // handle fDistance < 0.0
+ if(rCandidate.isClosed())
+ {
+ // if fDistance < 0.0 increment with multiple of fLength
+ sal_uInt32 nCount(sal_uInt32(-fDistance / fLength));
+ fDistance += double(nCount + 1L) * fLength;
+ }
+ else
+ {
+ // crop to polygon start
+ fDistance = 0.0;
+ bIndexDone = true;
+ }
+ }
+ else if(fTools::moreOrEqual(fDistance, fLength))
+ {
+ // handle fDistance >= fLength
+ if(rCandidate.isClosed())
+ {
+ // if fDistance >= fLength decrement with multiple of fLength
+ sal_uInt32 nCount(sal_uInt32(fDistance / fLength));
+ fDistance -= (double)(nCount) * fLength;
+ }
+ else
+ {
+ // crop to polygon end
+ fDistance = 0.0;
+ nIndex = nEdgeCount;
+ bIndexDone = true;
+ }
+ }
+
+ // look for correct index. fDistance is now [0.0 .. fLength[
+ 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
+ aRetval = rCandidate.getB2DPoint(nIndex);
+
+ // if fDistance != 0.0, move that length on the edge. The edge
+ // length is in fEdgeLength.
+ if(!fTools::equalZero(fDistance))
+ {
+ if(fTools::moreOrEqual(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
+ 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);
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPoint getPositionRelative(const B2DPolygon& rCandidate, double fDistance, double fLength)
+ {
+ // get length if not given
+ if(fTools::equalZero(fLength))
+ {
+ fLength = getLength(rCandidate);
+ }
+
+ // multiply fDistance with real length to get absolute position and
+ // use getPositionAbsolute
+ return getPositionAbsolute(rCandidate, fDistance * fLength, fLength);
+ }
+
+ B2DPolygon getSnippetAbsolute(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ // get length if not given
+ if(fTools::equalZero(fLength))
+ {
+ fLength = getLength(rCandidate);
+ }
+
+ // test and correct fFrom
+ if(fTools::less(fFrom, 0.0))
+ {
+ fFrom = 0.0;
+ }
+
+ // test and correct fTo
+ if(fTools::more(fTo, fLength))
+ {
+ fTo = fLength;
+ }
+
+ // test and correct relationship of fFrom, fTo
+ if(fTools::more(fFrom, fTo))
+ {
+ fFrom = fTo = (fFrom + fTo) / 2.0;
+ }
+
+ if(fTools::equalZero(fFrom) && fTools::equal(fTo, fLength))
+ {
+ // no change, result is the whole polygon
+ return rCandidate;
+ }
+ else
+ {
+ B2DPolygon aRetval;
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ double fPositionOfStart(0.0);
+ bool bStartDone(false);
+ bool bEndDone(false);
+
+ for(sal_uInt32 a(0L); !(bStartDone && bEndDone) && a < nEdgeCount; a++)
+ {
+ const double fEdgeLength(getEdgeLength(rCandidate, a));
+
+ if(!bStartDone)
+ {
+ 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;
+ }
+ }
+ }
+
+ if(!bEndDone && fTools::moreOrEqual(fTo, fPositionOfStart) && fTools::less(fTo, fPositionOfStart + fEdgeLength))
+ {
+ // calculate and add end point
+ if(fTools::equalZero(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
+ {
+ 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(fTo - fPositionOfStart));
+ B2DCubicBezier aLeft;
+
+ aBezierSegment.split(fBezierDistance, &aLeft, 0);
+ aRetval.append(aLeft.getEndPoint());
+ aRetval.setPrevControlPoint(aRetval.count() - 1, aLeft.getControlPointB());
+ bDone = true;
+ }
+ }
+
+ if(!bDone)
+ {
+ const double fRelValue((fTo - fPositionOfStart) / fEdgeLength);
+ aRetval.append(interpolate(aStart, aEnd, fRelValue));
+ }
+ }
+
+ bEndDone = true;
+ }
+
+ if(!bEndDone)
+ {
+ if(bStartDone)
+ {
+ // add segments end point
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aRetval.append(rCandidate.getB2DPoint(nNextIndex));
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
+ aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(nNextIndex));
+ }
+ }
+
+ // increment fPositionOfStart
+ fPositionOfStart += fEdgeLength;
+ }
+ }
+ return aRetval;
+ }
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolygon getSnippetRelative(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength)
+ {
+ // get length if not given
+ if(fTools::equalZero(fLength))
+ {
+ fLength = getLength(rCandidate);
+ }
+
+ // multiply distances with real length to get absolute position and
+ // use getSnippetAbsolute
+ return getSnippetAbsolute(rCandidate, fFrom * fLength, fTo * fLength, fLength);
+ }
+
+ CutFlagValue findCut(
+ const B2DPolygon& rCandidate,
+ sal_uInt32 nIndex1, sal_uInt32 nIndex2,
+ CutFlagValue aCutFlags,
+ double* pCut1, double* pCut2)
+ {
+ CutFlagValue aRetval(CUTFLAG_NONE);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nIndex1 < nPointCount && nIndex2 < nPointCount && nIndex1 != nIndex2)
+ {
+ sal_uInt32 nEnd1(getIndexOfSuccessor(nIndex1, rCandidate));
+ sal_uInt32 nEnd2(getIndexOfSuccessor(nIndex2, rCandidate));
+
+ const B2DPoint aStart1(rCandidate.getB2DPoint(nIndex1));
+ const B2DPoint aEnd1(rCandidate.getB2DPoint(nEnd1));
+ const B2DVector aVector1(aEnd1 - aStart1);
+
+ const B2DPoint aStart2(rCandidate.getB2DPoint(nIndex2));
+ const B2DPoint aEnd2(rCandidate.getB2DPoint(nEnd2));
+ const B2DVector aVector2(aEnd2 - aStart2);
+
+ aRetval = findCut(
+ aStart1, aVector1, aStart2, aVector2,
+ aCutFlags, pCut1, pCut2);
+ }
+
+ return aRetval;
+ }
+
+ CutFlagValue findCut(
+ const B2DPolygon& rCandidate1, sal_uInt32 nIndex1,
+ const B2DPolygon& rCandidate2, sal_uInt32 nIndex2,
+ CutFlagValue aCutFlags,
+ double* pCut1, double* pCut2)
+ {
+ CutFlagValue aRetval(CUTFLAG_NONE);
+ const sal_uInt32 nPointCount1(rCandidate1.count());
+ const sal_uInt32 nPointCount2(rCandidate2.count());
+
+ if(nIndex1 < nPointCount1 && nIndex2 < nPointCount2)
+ {
+ sal_uInt32 nEnd1(getIndexOfSuccessor(nIndex1, rCandidate1));
+ sal_uInt32 nEnd2(getIndexOfSuccessor(nIndex2, rCandidate2));
+
+ const B2DPoint aStart1(rCandidate1.getB2DPoint(nIndex1));
+ const B2DPoint aEnd1(rCandidate1.getB2DPoint(nEnd1));
+ const B2DVector aVector1(aEnd1 - aStart1);
+
+ const B2DPoint aStart2(rCandidate2.getB2DPoint(nIndex2));
+ const B2DPoint aEnd2(rCandidate2.getB2DPoint(nEnd2));
+ const B2DVector aVector2(aEnd2 - aStart2);
+
+ aRetval = findCut(
+ aStart1, aVector1, aStart2, aVector2,
+ aCutFlags, pCut1, pCut2);
+ }
+
+ return aRetval;
+ }
+
+ CutFlagValue findCut(
+ const B2DPoint& rEdge1Start, const B2DVector& rEdge1Delta,
+ const B2DPoint& rEdge2Start, const B2DVector& rEdge2Delta,
+ CutFlagValue aCutFlags,
+ double* pCut1, double* pCut2)
+ {
+ CutFlagValue aRetval(CUTFLAG_NONE);
+ double fCut1(0.0);
+ double fCut2(0.0);
+ bool bFinished(!((bool)(aCutFlags & CUTFLAG_ALL)));
+
+ // test for same points?
+ if(!bFinished
+ && (aCutFlags & (CUTFLAG_START1|CUTFLAG_END1))
+ && (aCutFlags & (CUTFLAG_START2|CUTFLAG_END2)))
+ {
+ // same startpoint?
+ if(!bFinished && (aCutFlags & (CUTFLAG_START1|CUTFLAG_START2)) == (CUTFLAG_START1|CUTFLAG_START2))
+ {
+ if(rEdge1Start.equal(rEdge2Start))
+ {
+ bFinished = true;
+ aRetval = (CUTFLAG_START1|CUTFLAG_START2);
+ }
+ }
+
+ // same endpoint?
+ if(!bFinished && (aCutFlags & (CUTFLAG_END1|CUTFLAG_END2)) == (CUTFLAG_END1|CUTFLAG_END2))
+ {
+ const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
+ const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
+
+ if(aEnd1.equal(aEnd2))
+ {
+ bFinished = true;
+ aRetval = (CUTFLAG_END1|CUTFLAG_END2);
+ fCut1 = fCut2 = 1.0;
+ }
+ }
+
+ // startpoint1 == endpoint2?
+ if(!bFinished && (aCutFlags & (CUTFLAG_START1|CUTFLAG_END2)) == (CUTFLAG_START1|CUTFLAG_END2))
+ {
+ const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
+
+ if(rEdge1Start.equal(aEnd2))
+ {
+ bFinished = true;
+ aRetval = (CUTFLAG_START1|CUTFLAG_END2);
+ fCut1 = 0.0;
+ fCut2 = 1.0;
+ }
+ }
+
+ // startpoint2 == endpoint1?
+ if(!bFinished&& (aCutFlags & (CUTFLAG_START2|CUTFLAG_END1)) == (CUTFLAG_START2|CUTFLAG_END1))
+ {
+ const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
+
+ if(rEdge2Start.equal(aEnd1))
+ {
+ bFinished = true;
+ aRetval = (CUTFLAG_START2|CUTFLAG_END1);
+ fCut1 = 1.0;
+ fCut2 = 0.0;
+ }
+ }
+ }
+
+ if(!bFinished && (aCutFlags & CUTFLAG_LINE))
+ {
+ if(!bFinished && (aCutFlags & CUTFLAG_START1))
+ {
+ // start1 on line 2 ?
+ if(isPointOnEdge(rEdge1Start, rEdge2Start, rEdge2Delta, &fCut2))
+ {
+ bFinished = true;
+ aRetval = (CUTFLAG_LINE|CUTFLAG_START1);
+ }
+ }
+
+ if(!bFinished && (aCutFlags & CUTFLAG_START2))
+ {
+ // start2 on line 1 ?
+ if(isPointOnEdge(rEdge2Start, rEdge1Start, rEdge1Delta, &fCut1))
+ {
+ bFinished = true;
+ aRetval = (CUTFLAG_LINE|CUTFLAG_START2);
+ }
+ }
+
+ if(!bFinished && (aCutFlags & CUTFLAG_END1))
+ {
+ // end1 on line 2 ?
+ const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
+
+ if(isPointOnEdge(aEnd1, rEdge2Start, rEdge2Delta, &fCut2))
+ {
+ bFinished = true;
+ aRetval = (CUTFLAG_LINE|CUTFLAG_END1);
+ }
+ }
+
+ if(!bFinished && (aCutFlags & CUTFLAG_END2))
+ {
+ // end2 on line 1 ?
+ const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
+
+ if(isPointOnEdge(aEnd2, rEdge1Start, rEdge1Delta, &fCut1))
+ {
+ bFinished = true;
+ aRetval = (CUTFLAG_LINE|CUTFLAG_END2);
+ }
+ }
+
+ if(!bFinished)
+ {
+ // cut in line1, line2 ?
+ fCut1 = (rEdge1Delta.getX() * rEdge2Delta.getY()) - (rEdge1Delta.getY() * rEdge2Delta.getX());
+
+ if(!fTools::equalZero(fCut1))
+ {
+ fCut1 = (rEdge2Delta.getY() * (rEdge2Start.getX() - rEdge1Start.getX())
+ + rEdge2Delta.getX() * (rEdge1Start.getY() - rEdge2Start.getY())) / fCut1;
+
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ // inside parameter range edge1 AND fCut2 is calcable
+ if(fTools::more(fCut1, fZero) && fTools::less(fCut1, fOne)
+ && (!fTools::equalZero(rEdge2Delta.getX()) || !fTools::equalZero(rEdge2Delta.getY())))
+ {
+ // take the mopre precise calculation of the two possible
+ if(fabs(rEdge2Delta.getX()) > fabs(rEdge2Delta.getY()))
+ {
+ fCut2 = (rEdge1Start.getX() + fCut1
+ * rEdge1Delta.getX() - rEdge2Start.getX()) / rEdge2Delta.getX();
+ }
+ else
+ {
+ fCut2 = (rEdge1Start.getY() + fCut1
+ * rEdge1Delta.getY() - rEdge2Start.getY()) / rEdge2Delta.getY();
+ }
+
+ // inside parameter range edge2, too
+ if(fTools::more(fCut2, fZero) && fTools::less(fCut2, fOne))
+ {
+ bFinished = true;
+ aRetval = CUTFLAG_LINE;
+ }
+ }
+ }
+ }
+ }
+
+ // copy values if wanted
+ if(pCut1)
+ {
+ *pCut1 = fCut1;
+ }
+
+ if(pCut2)
+ {
+ *pCut2 = fCut2;
+ }
+
+ return aRetval;
+ }
+
+ bool isPointOnEdge(
+ const B2DPoint& rPoint,
+ const B2DPoint& rEdgeStart,
+ const B2DVector& rEdgeDelta,
+ double* pCut)
+ {
+ bool bDeltaXIsZero(fTools::equalZero(rEdgeDelta.getX()));
+ bool bDeltaYIsZero(fTools::equalZero(rEdgeDelta.getY()));
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ if(bDeltaXIsZero && bDeltaYIsZero)
+ {
+ // no line, just a point
+ return false;
+ }
+ else if(bDeltaXIsZero)
+ {
+ // vertical line
+ if(fTools::equal(rPoint.getX(), rEdgeStart.getX()))
+ {
+ double fValue = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
+
+ if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
+ {
+ if(pCut)
+ {
+ *pCut = fValue;
+ }
+
+ return true;
+ }
+ }
+ }
+ else if(bDeltaYIsZero)
+ {
+ // horizontal line
+ if(fTools::equal(rPoint.getY(), rEdgeStart.getY()))
+ {
+ double fValue = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
+
+ if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
+ {
+ if(pCut)
+ {
+ *pCut = fValue;
+ }
+
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // any angle line
+ double fTOne = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
+ double fTTwo = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
+
+ if(fTools::equal(fTOne, fTTwo))
+ {
+ // same parameter representation, point is on line. Take
+ // middle value for better results
+ double fValue = (fTOne + fTTwo) / 2.0;
+
+ if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
+ {
+ // point is inside line bounds, too
+ if(pCut)
+ {
+ *pCut = fValue;
+ }
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ void applyLineDashing(const B2DPolygon& rCandidate, const ::std::vector<double>& rDotDashArray, B2DPolyPolygon* pLineTarget, B2DPolyPolygon* pGapTarget, double fDotDashLength)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ const sal_uInt32 nDotDashCount(rDotDashArray.size());
+
+ if(fTools::lessOrEqual(fDotDashLength, 0.0))
+ {
+ fDotDashLength = ::std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
+ }
+
+ if(fTools::more(fDotDashLength, 0.0) && (pLineTarget || pGapTarget) && nPointCount)
+ {
+ // clear targets
+ if(pLineTarget)
+ {
+ pLineTarget->clear();
+ }
+
+ if(pGapTarget)
+ {
+ pGapTarget->clear();
+ }
+
+ // 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;
+
+ // iterate over all edges
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // 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));
+
+ // check if we have a trivial bezier segment -> possible fallback to edge
+ aCurrentEdge.testAndSolveTrivialBezier();
+
+ if(aCurrentEdge.isBezier())
+ {
+ // bezier segment
+ const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge);
+ const double fEdgeLength(aCubicBezierHelper.getLength());
+
+ if(!fTools::equalZero(fEdgeLength))
+ {
+ 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);
+
+ if(bHandleLine || bHandleGap)
+ {
+ 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());
+ }
+
+ // prepare move to next edge
+ fDotDashMovingLength -= fEdgeLength;
+ }
+ }
+ else
+ {
+ // simple edge
+ const double fEdgeLength(aCurrentEdge.getEdgeLength());
+
+ if(!fTools::equalZero(fEdgeLength))
+ {
+ 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)
+ {
+ 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;
+ }
+
+ // append snippet [fLastDotDashMovingLength, fEdgeLength]
+ 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(aCurrentEdge.getEndPoint());
+ }
+
+ // prepare move to next edge
+ fDotDashMovingLength -= fEdgeLength;
+ }
+ }
+
+ // prepare next edge step (end point gets new start point)
+ aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint());
+ }
+
+ // append last intermediate results (if exists)
+ if(aSnippet.count())
+ {
+ if(bIsLine && pLineTarget)
+ {
+ pLineTarget->append(aSnippet);
+ }
+ else if(!bIsLine && pGapTarget)
+ {
+ pGapTarget->append(aSnippet);
+ }
+ }
+
+ // check if start and end polygon may be merged
+ if(pLineTarget)
+ {
+ const sal_uInt32 nCount(pLineTarget->count());
+
+ 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(pLineTarget->getB2DPolygon(0));
+ B2DPolygon aLast(pLineTarget->getB2DPolygon(nCount - 1));
+
+ 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();
+ pLineTarget->setB2DPolygon(0, aLast);
+ pLineTarget->remove(nCount - 1);
+ }
+ }
+ }
+
+ if(pGapTarget)
+ {
+ const sal_uInt32 nCount(pGapTarget->count());
+
+ 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));
+
+ 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);
+ }
+
+ if(pGapTarget)
+ {
+ pGapTarget->append(rCandidate);
+ }
+ }
+ }
+
+ // test if point is inside epsilon-range around an edge defined
+ // by the two given points. Can be used for HitTesting. The epsilon-range
+ // is defined to be the rectangle centered to the given edge, using height
+ // 2 x fDistance, and the circle around both points with radius fDistance.
+ bool isInEpsilonRange(const B2DPoint& rEdgeStart, const B2DPoint& rEdgeEnd, const B2DPoint& rTestPosition, double fDistance)
+ {
+ // build edge vector
+ const B2DVector aEdge(rEdgeEnd - rEdgeStart);
+ bool bDoDistanceTestStart(false);
+ bool bDoDistanceTestEnd(false);
+
+ if(aEdge.equalZero())
+ {
+ // no edge, just a point. Do one of the distance tests.
+ bDoDistanceTestStart = true;
+ }
+ else
+ {
+ // edge has a length. Create perpendicular vector.
+ const B2DVector aPerpend(getPerpendicular(aEdge));
+ double fCut(
+ (aPerpend.getY() * (rTestPosition.getX() - rEdgeStart.getX())
+ + aPerpend.getX() * (rEdgeStart.getY() - rTestPosition.getY())) /
+ (aEdge.getX() * aEdge.getX() + aEdge.getY() * aEdge.getY()));
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ if(fTools::less(fCut, fZero))
+ {
+ // left of rEdgeStart
+ bDoDistanceTestStart = true;
+ }
+ else if(fTools::more(fCut, fOne))
+ {
+ // right of rEdgeEnd
+ bDoDistanceTestEnd = true;
+ }
+ else
+ {
+ // inside line [0.0 .. 1.0]
+ const B2DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut));
+ const B2DVector aDelta(rTestPosition - aCutPoint);
+ const double fDistanceSquare(aDelta.scalar(aDelta));
+
+ if(fDistanceSquare <= fDistance * fDistance)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ if(bDoDistanceTestStart)
+ {
+ const B2DVector aDelta(rTestPosition - rEdgeStart);
+ const double fDistanceSquare(aDelta.scalar(aDelta));
+
+ if(fDistanceSquare <= fDistance * fDistance)
+ {
+ return true;
+ }
+ }
+ else if(bDoDistanceTestEnd)
+ {
+ const B2DVector aDelta(rTestPosition - rEdgeEnd);
+ const double fDistanceSquare(aDelta.scalar(aDelta));
+
+ if(fDistanceSquare <= fDistance * fDistance)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // test if point is inside epsilon-range around the given Polygon. Can be used
+ // for HitTesting. The epsilon-range is defined to be the tube around the polygon
+ // with distance fDistance and rounded edges (start and end point).
+ bool isInEpsilonRange(const B2DPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance)
+ {
+ // force to non-bezier polygon
+ const B2DPolygon aCandidate(rCandidate.getDefaultAdaptiveSubdivision());
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+ B2DPoint aCurrent(aCandidate.getB2DPoint(0));
+
+ if(nEdgeCount)
+ {
+ // edges
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
+
+ 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;
+ }
+
+ B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadius )
+ {
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ if(fTools::lessOrEqual(fRadius, fZero))
+ {
+ // no radius, use rectangle
+ return createPolygonFromRect( rRect );
+ }
+ else if(fTools::moreOrEqual(fRadius, fOne))
+ {
+ // full radius, use ellipse
+ const B2DPoint aCenter(rRect.getCenter());
+ const double fRadiusX(rRect.getWidth() / 2.0);
+ const double fRadiusY(rRect.getHeight() / 2.0);
+
+ return createPolygonFromEllipse( aCenter, fRadiusX, fRadiusY );
+ }
+ else
+ {
+ // create rectangle with two radii between ]0.0 .. 1.0[
+ return createPolygonFromRect( rRect, fRadius, fRadius );
+ }
+ }
+
+ B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadiusX, double fRadiusY )
+ {
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ // crop to useful values
+ if(fTools::less(fRadiusX, fZero))
+ {
+ fRadiusX = fZero;
+ }
+ else if(fTools::more(fRadiusX, fOne))
+ {
+ fRadiusX = fOne;
+ }
+
+ if(fTools::less(fRadiusY, fZero))
+ {
+ fRadiusY = fZero;
+ }
+ else if(fTools::more(fRadiusY, fOne))
+ {
+ fRadiusY = fOne;
+ }
+
+ if(fZero == fRadiusX || fZero == fRadiusY)
+ {
+ 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)
+ {
+ // in both directions full radius, use ellipse
+ const B2DPoint aCenter(rRect.getCenter());
+ const double fRectRadiusX(rRect.getWidth() / 2.0);
+ const double fRectRadiusY(rRect.getHeight() / 2.0);
+
+ return createPolygonFromEllipse( aCenter, fRectRadiusX, fRectRadiusY );
+ }
+ else
+ {
+ B2DPolygon aRetval;
+ const double fBowX((rRect.getWidth() / 2.0) * fRadiusX);
+ 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(-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 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, aTopRight, fKappa), interpolate(aStop, aTopRight, fKappa), aStop);
+ }
+
+ // create third bow
+ {
+ 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 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, aBottomLeft, fKappa), interpolate(aStop, aBottomLeft, fKappa), aStop);
+ }
+
+ // close
+ aRetval.setClosed( true );
+
+ // remove double created points if there are extreme radii envolved
+ if(fOne == fRadiusX || fOne == fRadiusY)
+ {
+ aRetval.removeDoublePoints();
+ }
+
+ return aRetval;
+ }
+ }
+
+ B2DPolygon createPolygonFromRect( const B2DRectangle& rRect )
+ {
+ B2DPolygon aRetval;
+
+ aRetval.append( B2DPoint( rRect.getMinX(), rRect.getMinY() ) );
+ aRetval.append( B2DPoint( rRect.getMaxX(), rRect.getMinY() ) );
+ aRetval.append( B2DPoint( rRect.getMaxX(), rRect.getMaxY() ) );
+ aRetval.append( B2DPoint( rRect.getMinX(), rRect.getMaxY() ) );
+
+ // close
+ aRetval.setClosed( true );
+
+ return aRetval;
+ }
+
+ B2DPolygon createPolygonFromCircle( const B2DPoint& rCenter, double fRadius )
+ {
+ return createPolygonFromEllipse( rCenter, fRadius, fRadius );
+ }
+
+ B2DPolygon impCreateUnitCircle(sal_uInt32 nStartQuadrant)
+ {
+ B2DPolygon aUnitCircle;
+ const double fKappa((M_SQRT2 - 1.0) * 4.0 / 3.0);
+ const double fScaledKappa(fKappa * (1.0 / STEPSPERQUARTER));
+ const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(F_PI2 / STEPSPERQUARTER));
+
+ B2DPoint aPoint(1.0, 0.0);
+ B2DPoint aForward(1.0, fScaledKappa);
+ B2DPoint aBackward(1.0, -fScaledKappa);
+
+ if(0 != nStartQuadrant)
+ {
+ const B2DHomMatrix aQuadrantMatrix(createRotateB2DHomMatrix(F_PI2 * (nStartQuadrant % 4)));
+ aPoint *= aQuadrantMatrix;
+ aBackward *= aQuadrantMatrix;
+ aForward *= aQuadrantMatrix;
+ }
+
+ aUnitCircle.append(aPoint);
+
+ for(sal_uInt32 a(0); a < STEPSPERQUARTER * 4; a++)
+ {
+ aPoint *= aRotateMatrix;
+ aBackward *= aRotateMatrix;
+ aUnitCircle.appendBezierSegment(aForward, aBackward, aPoint);
+ aForward *= aRotateMatrix;
+ }
+
+ aUnitCircle.setClosed(true);
+ aUnitCircle.removeDoublePoints();
+
+ return aUnitCircle;
+ }
+
+ B2DPolygon createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant)
+ {
+ switch(nStartQuadrant % 4)
+ {
+ case 1 :
+ {
+ static B2DPolygon aUnitCircleStartQuadrantOne;
+
+ if(!aUnitCircleStartQuadrantOne.count())
+ {
+ ::osl::Mutex m_mutex;
+ aUnitCircleStartQuadrantOne = impCreateUnitCircle(1);
+ }
+
+ return aUnitCircleStartQuadrantOne;
+ }
+ case 2 :
+ {
+ static B2DPolygon aUnitCircleStartQuadrantTwo;
+
+ if(!aUnitCircleStartQuadrantTwo.count())
+ {
+ ::osl::Mutex m_mutex;
+ aUnitCircleStartQuadrantTwo = impCreateUnitCircle(2);
+ }
+
+ return aUnitCircleStartQuadrantTwo;
+ }
+ case 3 :
+ {
+ static B2DPolygon aUnitCircleStartQuadrantThree;
+
+ if(!aUnitCircleStartQuadrantThree.count())
+ {
+ ::osl::Mutex m_mutex;
+ aUnitCircleStartQuadrantThree = impCreateUnitCircle(3);
+ }
+
+ return aUnitCircleStartQuadrantThree;
+ }
+ default : // case 0 :
+ {
+ static B2DPolygon aUnitCircleStartQuadrantZero;
+
+ if(!aUnitCircleStartQuadrantZero.count())
+ {
+ ::osl::Mutex m_mutex;
+ aUnitCircleStartQuadrantZero = impCreateUnitCircle(0);
+ }
+
+ return aUnitCircleStartQuadrantZero;
+ }
+ }
+ }
+
+ B2DPolygon createPolygonFromEllipse( const B2DPoint& rCenter, double fRadiusX, double fRadiusY )
+ {
+ B2DPolygon aRetval(createPolygonFromUnitCircle());
+ const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
+
+ aRetval.transform(aMatrix);
+
+ return aRetval;
+ }
+
+ B2DPolygon createPolygonFromUnitEllipseSegment( double fStart, double fEnd )
+ {
+ B2DPolygon aRetval;
+
+ // truncate fStart, fEnd to a range of [0.0 .. F_2PI[ where F_2PI
+ // falls back to 0.0 to ensure a unique definition
+ if(fTools::less(fStart, 0.0))
+ {
+ fStart = 0.0;
+ }
+
+ if(fTools::moreOrEqual(fStart, F_2PI))
+ {
+ fStart = 0.0;
+ }
+
+ if(fTools::less(fEnd, 0.0))
+ {
+ fEnd = 0.0;
+ }
+
+ if(fTools::moreOrEqual(fEnd, F_2PI))
+ {
+ fEnd = 0.0;
+ }
+
+ if(fTools::equal(fStart, fEnd))
+ {
+ // same start and end angle, add single point
+ aRetval.append(B2DPoint(cos(fStart), sin(fStart)));
+ }
+ else
+ {
+ const sal_uInt32 nSegments(STEPSPERQUARTER * 4);
+ const double fAnglePerSegment(F_PI2 / STEPSPERQUARTER);
+ const sal_uInt32 nStartSegment(sal_uInt32(fStart / fAnglePerSegment) % nSegments);
+ const sal_uInt32 nEndSegment(sal_uInt32(fEnd / fAnglePerSegment) % nSegments);
+ const double fKappa((M_SQRT2 - 1.0) * 4.0 / 3.0);
+ const double fScaledKappa(fKappa * (1.0 / STEPSPERQUARTER));
+
+ B2DPoint aSegStart(cos(fStart), sin(fStart));
+ aRetval.append(aSegStart);
+
+ if(nStartSegment == nEndSegment && fTools::more(fEnd, fStart))
+ {
+ // start and end in one sector and in the right order, create in one segment
+ const B2DPoint aSegEnd(cos(fEnd), sin(fEnd));
+ const double fFactor(fScaledKappa * ((fEnd - fStart) / fAnglePerSegment));
+
+ aRetval.appendBezierSegment(
+ aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
+ aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
+ aSegEnd);
+ }
+ else
+ {
+ double fSegEndRad((nStartSegment + 1) * fAnglePerSegment);
+ double fFactor(fScaledKappa * ((fSegEndRad - fStart) / fAnglePerSegment));
+ B2DPoint aSegEnd(cos(fSegEndRad), sin(fSegEndRad));
+
+ aRetval.appendBezierSegment(
+ aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
+ aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
+ aSegEnd);
+
+ sal_uInt32 nSegment((nStartSegment + 1) % nSegments);
+ aSegStart = aSegEnd;
+
+ while(nSegment != nEndSegment)
+ {
+ // No end in this sector, add full sector.
+ fSegEndRad = (nSegment + 1) * fAnglePerSegment;
+ aSegEnd = B2DPoint(cos(fSegEndRad), sin(fSegEndRad));
+
+ aRetval.appendBezierSegment(
+ aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fScaledKappa),
+ aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fScaledKappa),
+ aSegEnd);
+
+ nSegment = (nSegment + 1) % nSegments;
+ aSegStart = aSegEnd;
+ }
+
+ // End in this sector
+ const double fSegStartRad(nSegment * fAnglePerSegment);
+ fFactor = fScaledKappa * ((fEnd - fSegStartRad) / fAnglePerSegment);
+ aSegEnd = B2DPoint(cos(fEnd), sin(fEnd));
+
+ aRetval.appendBezierSegment(
+ aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
+ aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
+ aSegEnd);
+ }
+ }
+
+ // remove double points between segments created by segmented creation
+ aRetval.removeDoublePoints();
+
+ return aRetval;
+ }
+
+ B2DPolygon createPolygonFromEllipseSegment( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd )
+ {
+ B2DPolygon aRetval(createPolygonFromUnitEllipseSegment(fStart, fEnd));
+ const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
+
+ aRetval.transform(aMatrix);
+
+ return aRetval;
+ }
+
+ bool hasNeutralPoints(const B2DPolygon& rCandidate)
+ {
+ OSL_ENSURE(!rCandidate.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)");
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 2L)
+ {
+ B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1L));
+ B2DPoint aCurrPoint(rCandidate.getB2DPoint(0L));
+
+ for(sal_uInt32 a(0L); a < nPointCount; a++)
+ {
+ const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
+ const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
+ const B2DVector aNextVec(aNextPoint - aCurrPoint);
+ const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
+
+ if(ORIENTATION_NEUTRAL == aOrientation)
+ {
+ // current has neutral orientation
+ return true;
+ }
+ else
+ {
+ // prepare next
+ aPrevPoint = aCurrPoint;
+ aCurrPoint = aNextPoint;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ B2DPolygon removeNeutralPoints(const B2DPolygon& rCandidate)
+ {
+ if(hasNeutralPoints(rCandidate))
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ B2DPolygon aRetval;
+ B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1L));
+ B2DPoint aCurrPoint(rCandidate.getB2DPoint(0L));
+
+ for(sal_uInt32 a(0L); a < nPointCount; a++)
+ {
+ const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
+ const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
+ const B2DVector aNextVec(aNextPoint - aCurrPoint);
+ const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
+
+ if(ORIENTATION_NEUTRAL == aOrientation)
+ {
+ // current has neutral orientation, leave it out and prepare next
+ aCurrPoint = aNextPoint;
+ }
+ else
+ {
+ // add current point
+ aRetval.append(aCurrPoint);
+
+ // prepare next
+ aPrevPoint = aCurrPoint;
+ aCurrPoint = aNextPoint;
+ }
+ }
+
+ while(aRetval.count() && ORIENTATION_NEUTRAL == getOrientationForIndex(aRetval, 0L))
+ {
+ aRetval.remove(0L);
+ }
+
+ // copy closed state
+ aRetval.setClosed(rCandidate.isClosed());
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ bool isConvex(const B2DPolygon& rCandidate)
+ {
+ OSL_ENSURE(!rCandidate.areControlPointsUsed(), "isConvex: ATM works not for curves (!)");
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 2L)
+ {
+ const B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1L));
+ B2DPoint aCurrPoint(rCandidate.getB2DPoint(0L));
+ B2DVector aCurrVec(aPrevPoint - aCurrPoint);
+ B2VectorOrientation aOrientation(ORIENTATION_NEUTRAL);
+
+ for(sal_uInt32 a(0L); a < nPointCount; a++)
+ {
+ const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
+ const B2DVector aNextVec(aNextPoint - aCurrPoint);
+ const B2VectorOrientation aCurrentOrientation(getOrientation(aNextVec, aCurrVec));
+
+ if(ORIENTATION_NEUTRAL == aOrientation)
+ {
+ // set start value, maybe neutral again
+ aOrientation = aCurrentOrientation;
+ }
+ else
+ {
+ if(ORIENTATION_NEUTRAL != aCurrentOrientation && aCurrentOrientation != aOrientation)
+ {
+ // different orientations found, that's it
+ return false;
+ }
+ }
+
+ // prepare next
+ aCurrPoint = aNextPoint;
+ aCurrVec = -aNextVec;
+ }
+ }
+
+ return true;
+ }
+
+ B2VectorOrientation getOrientationForIndex(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "getOrientationForIndex: index out of range (!)");
+ const B2DPoint aPrev(rCandidate.getB2DPoint(getIndexOfPredecessor(nIndex, rCandidate)));
+ const B2DPoint aCurr(rCandidate.getB2DPoint(nIndex));
+ const B2DPoint aNext(rCandidate.getB2DPoint(getIndexOfSuccessor(nIndex, rCandidate)));
+ const B2DVector aBack(aPrev - aCurr);
+ const B2DVector aForw(aNext - aCurr);
+
+ return getOrientation(aForw, aBack);
+ }
+
+ bool isPointOnLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidate, bool bWithPoints)
+ {
+ if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
+ {
+ // candidate is in epsilon around start or end -> inside
+ return bWithPoints;
+ }
+ else if(rStart.equal(rEnd))
+ {
+ // start and end are equal, but candidate is outside their epsilon -> outside
+ return false;
+ }
+ else
+ {
+ const B2DVector aEdgeVector(rEnd - rStart);
+ const B2DVector aTestVector(rCandidate - rStart);
+
+ if(areParallel(aEdgeVector, aTestVector))
+ {
+ const double fZero(0.0);
+ const double fOne(1.0);
+ const double fParamTestOnCurr(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY())
+ ? aTestVector.getX() / aEdgeVector.getX()
+ : aTestVector.getY() / aEdgeVector.getY());
+
+ if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ bool isPointOnPolygon(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithPoints)
+ {
+ const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(nPointCount > 1L)
+ {
+ const sal_uInt32 nLoopCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+ B2DPoint aCurrentPoint(aCandidate.getB2DPoint(0L));
+
+ for(sal_uInt32 a(0L); a < nLoopCount; a++)
+ {
+ const B2DPoint aNextPoint(aCandidate.getB2DPoint((a + 1L) % nPointCount));
+
+ if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints))
+ {
+ return true;
+ }
+
+ aCurrentPoint = aNextPoint;
+ }
+ }
+ else if(nPointCount && bWithPoints)
+ {
+ return rPoint.equal(aCandidate.getB2DPoint(0L));
+ }
+
+ return false;
+ }
+
+ bool isPointInTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC, const B2DPoint& rCandidate, bool bWithBorder)
+ {
+ if(arePointsOnSameSideOfLine(rA, rB, rC, rCandidate, bWithBorder))
+ {
+ if(arePointsOnSameSideOfLine(rB, rC, rA, rCandidate, bWithBorder))
+ {
+ if(arePointsOnSameSideOfLine(rC, rA, rB, rCandidate, bWithBorder))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ bool arePointsOnSameSideOfLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidateA, const B2DPoint& rCandidateB, bool bWithLine)
+ {
+ const B2DVector aLineVector(rEnd - rStart);
+ const B2DVector aVectorToA(rEnd - rCandidateA);
+ const double fCrossA(aLineVector.cross(aVectorToA));
+
+ if(fTools::equalZero(fCrossA))
+ {
+ // one point on the line
+ return bWithLine;
+ }
+
+ const B2DVector aVectorToB(rEnd - rCandidateB);
+ const double fCrossB(aLineVector.cross(aVectorToB));
+
+ if(fTools::equalZero(fCrossB))
+ {
+ // one point on the line
+ return bWithLine;
+ }
+
+ // return true if they both have the same sign
+ return ((fCrossA > 0.0) == (fCrossB > 0.0));
+ }
+
+ void addTriangleFan(const B2DPolygon& rCandidate, B2DPolygon& rTarget)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nCount > 2L)
+ {
+ const B2DPoint aStart(rCandidate.getB2DPoint(0L));
+ B2DPoint aLast(rCandidate.getB2DPoint(1L));
+
+ for(sal_uInt32 a(2L); a < nCount; a++)
+ {
+ const B2DPoint aCurrent(rCandidate.getB2DPoint(a));
+ rTarget.append(aStart);
+ rTarget.append(aLast);
+ rTarget.append(aCurrent);
+
+ // prepare next
+ aLast = aCurrent;
+ }
+ }
+ }
+
+ namespace
+ {
+ /// return 0 for input of 0, -1 for negative and 1 for positive input
+ inline int lcl_sgn( const double n )
+ {
+ return n == 0.0 ? 0 : 1 - 2*::rtl::math::isSignBitSet(n);
+ }
+ }
+
+ bool isRectangle( const B2DPolygon& rPoly )
+ {
+ // polygon must be closed to resemble a rect, and contain
+ // at least four points.
+ if( !rPoly.isClosed() ||
+ rPoly.count() < 4 )
+ {
+ return false;
+ }
+
+ // number of 90 degree turns the polygon has taken
+ int nNumTurns(0);
+
+ int nVerticalEdgeType=0;
+ int nHorizontalEdgeType=0;
+ bool bNullVertex(true);
+ bool bCWPolygon(false); // when true, polygon is CW
+ // oriented, when false, CCW
+ bool bOrientationSet(false); // when false, polygon
+ // orientation has not yet
+ // been determined.
+
+ // scan all _edges_ (which involves coming back to point 0
+ // for the last edge - thus the modulo operation below)
+ const sal_Int32 nCount( rPoly.count() );
+ for( sal_Int32 i=0; i<nCount; ++i )
+ {
+ const B2DPoint& rPoint0( rPoly.getB2DPoint(i % nCount) );
+ const B2DPoint& rPoint1( rPoly.getB2DPoint((i+1) % nCount) );
+
+ // is 0 for zero direction vector, 1 for south edge and -1
+ // for north edge (standard screen coordinate system)
+ int nCurrVerticalEdgeType( lcl_sgn( rPoint1.getY() - rPoint0.getY() ) );
+
+ // is 0 for zero direction vector, 1 for east edge and -1
+ // for west edge (standard screen coordinate system)
+ int nCurrHorizontalEdgeType( lcl_sgn(rPoint1.getX() - rPoint0.getX()) );
+
+ if( nCurrVerticalEdgeType && nCurrHorizontalEdgeType )
+ return false; // oblique edge - for sure no rect
+
+ const bool bCurrNullVertex( !nCurrVerticalEdgeType && !nCurrHorizontalEdgeType );
+
+ // current vertex is equal to previous - just skip,
+ // until we have a real edge
+ if( bCurrNullVertex )
+ continue;
+
+ // if previous edge has two identical points, because
+ // no previous edge direction was available, simply
+ // take this first non-null edge as the start
+ // direction. That's what will happen here, if
+ // bNullVertex is false
+ if( !bNullVertex )
+ {
+ // 2D cross product - is 1 for CW and -1 for CCW turns
+ const int nCrossProduct( nHorizontalEdgeType*nCurrVerticalEdgeType -
+ nVerticalEdgeType*nCurrHorizontalEdgeType );
+
+ if( !nCrossProduct )
+ continue; // no change in orientation -
+ // collinear edges - just go on
+
+ // if polygon orientation is not set, we'll
+ // determine it now
+ if( !bOrientationSet )
+ {
+ bCWPolygon = nCrossProduct == 1;
+ bOrientationSet = true;
+ }
+ else
+ {
+ // if current turn orientation is not equal
+ // initial orientation, this is not a
+ // rectangle (as rectangles have consistent
+ // orientation).
+ if( (nCrossProduct == 1) != bCWPolygon )
+ return false;
+ }
+
+ ++nNumTurns;
+
+ // More than four 90 degree turns are an
+ // indication that this must not be a rectangle.
+ if( nNumTurns > 4 )
+ return false;
+ }
+
+ // store current state for the next turn
+ nVerticalEdgeType = nCurrVerticalEdgeType;
+ nHorizontalEdgeType = nCurrHorizontalEdgeType;
+ bNullVertex = false; // won't reach this line,
+ // if bCurrNullVertex is
+ // true - see above
+ }
+
+ return true;
+ }
+
+ B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon& rCandidate, double fZCoordinate)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ // call myself recursively with subdivided input
+ const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
+ return createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate);
+ }
+ else
+ {
+ B3DPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ B2DPoint aPoint(rCandidate.getB2DPoint(a));
+ aRetval.append(B3DPoint(aPoint.getX(), aPoint.getY(), fZCoordinate));
+ }
+
+ // copy closed state
+ aRetval.setClosed(rCandidate.isClosed());
+
+ return aRetval;
+ }
+ }
+
+ B2DPolygon createB2DPolygonFromB3DPolygon(const B3DPolygon& rCandidate, const B3DHomMatrix& rMat)
+ {
+ B2DPolygon aRetval;
+ const sal_uInt32 nCount(rCandidate.count());
+ const bool bIsIdentity(rMat.isIdentity());
+
+ for(sal_uInt32 a(0L); a < nCount; a++)
+ {
+ B3DPoint aCandidate(rCandidate.getB3DPoint(a));
+
+ if(!bIsIdentity)
+ {
+ aCandidate *= rMat;
+ }
+
+ aRetval.append(B2DPoint(aCandidate.getX(), aCandidate.getY()));
+ }
+
+ // copy closed state
+ aRetval.setClosed(rCandidate.isClosed());
+
+ 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();
+ }
+ 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()));
+ const double fCut(fDividend / fDivisor);
+
+ if(fCut < 0.0)
+ {
+ // not in line range, get distance to PointA
+ rCut = 0.0;
+ return aVector2.getLength();
+ }
+ else if(fCut > 1.0)
+ {
+ // not in line range, get distance to PointB
+ rCut = 1.0;
+ const B2DVector aVector(rTestPoint - rPointB);
+ return aVector.getLength();
+ }
+ else
+ {
+ // in line range
+ const B2DPoint aCutPoint(rPointA + fCut * aVector1);
+ const B2DVector aVector(rTestPoint - aCutPoint);
+ rCut = fCut;
+ return aVector.getLength();
+ }
+ }
+ }
+
+ double getSmallestDistancePointToPolygon(const B2DPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rEdgeIndex, double& rCut)
+ {
+ double fRetval(DBL_MAX);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1L)
+ {
+ const double fZero(0.0);
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+ B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0L); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+ double fEdgeDist;
+ double fNewCut;
+ bool bEdgeIsCurve(false);
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
+ aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aBezier.testAndSolveTrivialBezier();
+ bEdgeIsCurve = aBezier.isBezier();
+ }
+
+ if(bEdgeIsCurve)
+ {
+ fEdgeDist = aBezier.getSmallestDistancePointToBezierSegment(rTestPoint, fNewCut);
+ }
+ else
+ {
+ fEdgeDist = getSmallestDistancePointToEdge(aBezier.getStartPoint(), aBezier.getEndPoint(), rTestPoint, fNewCut);
+ }
+
+ if(DBL_MAX == fRetval || fEdgeDist < fRetval)
+ {
+ fRetval = fEdgeDist;
+ rEdgeIndex = a;
+ rCut = fNewCut;
+
+ if(fTools::equal(fRetval, fZero))
+ {
+ // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
+ fRetval = 0.0;
+ break;
+ }
+ }
+
+ // prepare next step
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(1.0 == rCut)
+ {
+ // correct rEdgeIndex when not last point
+ if(rCandidate.isClosed())
+ {
+ rEdgeIndex = getIndexOfSuccessor(rEdgeIndex, rCandidate);
+ rCut = 0.0;
+ }
+ else
+ {
+ if(rEdgeIndex != nEdgeCount - 1L)
+ {
+ rEdgeIndex++;
+ rCut = 0.0;
+ }
+ }
+ }
+ }
+
+ return fRetval;
+ }
+
+ B2DPoint distort(const B2DPoint& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
+ {
+ if(fTools::equalZero(rOriginal.getWidth()) || fTools::equalZero(rOriginal.getHeight()))
+ {
+ return rCandidate;
+ }
+ else
+ {
+ const double fRelativeX((rCandidate.getX() - rOriginal.getMinX()) / rOriginal.getWidth());
+ const double fRelativeY((rCandidate.getY() - rOriginal.getMinY()) / rOriginal.getHeight());
+ const double fOneMinusRelativeX(1.0 - fRelativeX);
+ const double fOneMinusRelativeY(1.0 - fRelativeY);
+ const double fNewX((fOneMinusRelativeY) * ((fOneMinusRelativeX) * rTopLeft.getX() + fRelativeX * rTopRight.getX()) +
+ fRelativeY * ((fOneMinusRelativeX) * rBottomLeft.getX() + fRelativeX * rBottomRight.getX()));
+ const double fNewY((fOneMinusRelativeX) * ((fOneMinusRelativeY) * rTopLeft.getY() + fRelativeY * rBottomLeft.getY()) +
+ fRelativeX * ((fOneMinusRelativeY) * rTopRight.getY() + fRelativeY * rBottomRight.getY()));
+
+ return B2DPoint(fNewX, fNewY);
+ }
+ }
+
+ B2DPolygon distort(const B2DPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount && 0.0 != rOriginal.getWidth() && 0.0 != rOriginal.getHeight())
+ {
+ B2DPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPointCount; a++)
+ {
+ aRetval.append(distort(rCandidate.getB2DPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ if(!rCandidate.getPrevControlPoint(a).equalZero())
+ {
+ aRetval.setPrevControlPoint(a, distort(rCandidate.getPrevControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
+ }
+
+ if(!rCandidate.getNextControlPoint(a).equalZero())
+ {
+ aRetval.setNextControlPoint(a, distort(rCandidate.getNextControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
+ }
+ }
+ }
+
+ aRetval.setClosed(rCandidate.isClosed());
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolygon rotateAroundPoint(const B2DPolygon& rCandidate, const B2DPoint& rCenter, double fAngle)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ B2DPolygon aRetval(rCandidate);
+
+ if(nPointCount)
+ {
+ const B2DHomMatrix aMatrix(basegfx::tools::createRotateAroundPoint(rCenter, fAngle));
+
+ aRetval.transform(aMatrix);
+ }
+
+ return aRetval;
+ }
+
+ B2DPolygon expandToCurve(const B2DPolygon& rCandidate)
+ {
+ B2DPolygon aRetval(rCandidate);
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ expandToCurveInPoint(aRetval, a);
+ }
+
+ return aRetval;
+ }
+
+ bool expandToCurveInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "expandToCurveInPoint: Access to polygon out of range (!)");
+ bool bRetval(false);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ // predecessor
+ if(!rCandidate.isPrevControlPointUsed(nIndex))
+ {
+ 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))
+ {
+ 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;
+ }
+ }
+ }
+
+ return bRetval;
+ }
+
+ B2DPolygon setContinuity(const B2DPolygon& rCandidate, B2VectorContinuity eContinuity)
+ {
+ B2DPolygon aRetval(rCandidate);
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ setContinuityInPoint(aRetval, a, eContinuity);
+ }
+
+ return aRetval;
+ }
+
+ bool setContinuityInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "setContinuityInPoint: Access to polygon out of range (!)");
+ bool bRetval(false);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(nIndex));
+
+ switch(eContinuity)
+ {
+ case CONTINUITY_NONE :
+ {
+ if(rCandidate.isPrevControlPointUsed(nIndex))
+ {
+ 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))
+ {
+ 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;
+ }
+
+ break;
+ }
+ case CONTINUITY_C1 :
+ {
+ 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());
+ const double fLenNext(aVectorNext.getLength());
+ aVectorPrev.normalize();
+ aVectorNext.normalize();
+ const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
+
+ if(ORIENTATION_NEUTRAL == aOrientation && aVectorPrev.scalar(aVectorNext) < 0.0)
+ {
+ // 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.
+ const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
+ const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+ const double fLenPrevEdge(B2DVector(rCandidate.getB2DPoint(nPrevIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
+ const double fLenNextEdge(B2DVector(rCandidate.getB2DPoint(nNextIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
+
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint + (aVectorPrev * fLenPrevEdge),
+ aCurrentPoint + (aVectorNext * fLenNextEdge));
+ bRetval = true;
+ }
+ }
+ else
+ {
+ // not parallel or same direction, set vectors and length
+ const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
+
+ if(ORIENTATION_POSITIVE == aOrientation)
+ {
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint - (aNormalizedPerpendicular * fLenPrev),
+ aCurrentPoint + (aNormalizedPerpendicular * fLenNext));
+ }
+ else
+ {
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint + (aNormalizedPerpendicular * fLenPrev),
+ aCurrentPoint - (aNormalizedPerpendicular * fLenNext));
+ }
+
+ bRetval = true;
+ }
+ }
+ break;
+ }
+ case CONTINUITY_C2 :
+ {
+ 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);
+ aVectorPrev.normalize();
+ aVectorNext.normalize();
+ const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
+
+ if(ORIENTATION_NEUTRAL == aOrientation && aVectorPrev.scalar(aVectorNext) < 0.0)
+ {
+ // parallel and opposite direction; set length. Use one direction for better numerical correctness
+ const B2DVector aScaledDirection(aVectorPrev * fCommonLength);
+
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint + aScaledDirection,
+ aCurrentPoint - aScaledDirection);
+ }
+ else
+ {
+ // not parallel or same direction, set vectors and length
+ const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
+ const B2DVector aPerpendicular(aNormalizedPerpendicular * fCommonLength);
+
+ if(ORIENTATION_POSITIVE == aOrientation)
+ {
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint - aPerpendicular,
+ aCurrentPoint + aPerpendicular);
+ }
+ else
+ {
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint + aPerpendicular,
+ aCurrentPoint - aPerpendicular);
+ }
+ }
+
+ bRetval = true;
+ }
+ break;
+ }
+ }
+ }
+
+ return bRetval;
+ }
+
+ 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 B2DPoint aPoints[] =
+ {
+ B2DPoint(rRect.getMinX(),rRect.getMinY()),
+ B2DPoint(rRect.getMaxX(),rRect.getMinY()),
+ B2DPoint(rRect.getMaxX(),rRect.getMaxY()),
+ B2DPoint(rRect.getMinX(),rRect.getMaxY())
+ };
+
+ 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 B2DPoint &p1 = aPoints[j];
+ const B2DPoint &p2 = aPoints[(j+1)%4];
+ bool bPointOnBoundary = false;
+ for( sal_uInt32 i=0; i<nCount; ++i )
+ {
+ const B2DPoint p(rPoly.getB2DPoint(i));
+
+ // 1 | x0 y0 1 |
+ // A = - | x1 y1 1 |
+ // 2 | x2 y2 1 |
+ double fDoubleArea = p2.getX()*p.getY() -
+ p2.getY()*p.getX() -
+ p1.getX()*p.getY() +
+ p1.getY()*p.getX() +
+ p1.getX()*p2.getY() -
+ p1.getY()*p2.getX();
+
+ if(fDoubleArea < epsilon)
+ {
+ bPointOnBoundary=true;
+ break;
+ }
+ }
+ if(!(bPointOnBoundary))
+ return false;
+ }
+
+ 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)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount && rCandidate.areControlPointsUsed())
+ {
+ // prepare loop
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DPolygon aRetval;
+ B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rCandidate.getB2DPoint(0));
+
+ // try to avoid costly reallocations
+ aRetval.reserve( nEdgeCount+1);
+
+ // add start point
+ aRetval.append(aBezier.getStartPoint());
+
+ for(sal_uInt32 a(0L); a < nEdgeCount; a++)
+ {
+ // get values for edge
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+ aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
+ aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aBezier.testAndSolveTrivialBezier();
+
+ // still bezier?
+ if(aBezier.isBezier())
+ {
+ // add edge with control vectors
+ aRetval.appendBezierSegment(aBezier.getControlPointA(), aBezier.getControlPointB(), aBezier.getEndPoint());
+ }
+ else
+ {
+ // add edge
+ aRetval.append(aBezier.getEndPoint());
+ }
+
+ // next point
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(rCandidate.isClosed())
+ {
+ // set closed flag, rescue control point and correct last double point
+ closeWithGeometryChange(aRetval);
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ // makes the given indexed point the new polygon start point. To do that, the points in the
+ // polygon will be rotated. This is only valid for closed polygons, for non-closed ones
+ // an assertion will be triggered
+ B2DPolygon makeStartPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndexOfNewStatPoint)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 2 && nIndexOfNewStatPoint != 0 && nIndexOfNewStatPoint < nPointCount)
+ {
+ OSL_ENSURE(rCandidate.isClosed(), "makeStartPoint: only valid for closed polygons (!)");
+ B2DPolygon aRetval;
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const sal_uInt32 nSourceIndex((a + nIndexOfNewStatPoint) % nPointCount);
+ aRetval.append(rCandidate.getB2DPoint(nSourceIndex));
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ aRetval.setPrevControlPoint(a, rCandidate.getPrevControlPoint(nSourceIndex));
+ aRetval.setNextControlPoint(a, rCandidate.getNextControlPoint(nSourceIndex));
+ }
+ }
+
+ return aRetval;
+ }
+
+ 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
+
+ bool equal(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, const double& rfSmallValue)
+ {
+ const sal_uInt32 nPointCount(rCandidateA.count());
+
+ if(nPointCount != rCandidateB.count())
+ return false;
+
+ const bool bClosed(rCandidateA.isClosed());
+
+ if(bClosed != rCandidateB.isClosed())
+ return false;
+
+ const bool bAreControlPointsUsed(rCandidateA.areControlPointsUsed());
+
+ if(bAreControlPointsUsed != rCandidateB.areControlPointsUsed())
+ return false;
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B2DPoint aPoint(rCandidateA.getB2DPoint(a));
+
+ if(!aPoint.equal(rCandidateB.getB2DPoint(a), rfSmallValue))
+ return false;
+
+ if(bAreControlPointsUsed)
+ {
+ const basegfx::B2DPoint aPrev(rCandidateA.getPrevControlPoint(a));
+
+ if(!aPrev.equal(rCandidateB.getPrevControlPoint(a), rfSmallValue))
+ return false;
+
+ const basegfx::B2DPoint aNext(rCandidateA.getNextControlPoint(a));
+
+ if(!aNext.equal(rCandidateB.getNextControlPoint(a), rfSmallValue))
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool equal(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB)
+ {
+ const double fSmallValue(fTools::getSmallValue());
+
+ return equal(rCandidateA, rCandidateB, fSmallValue);
+ }
+
+ // snap points of horizontal or vertical edges to discrete values
+ B2DPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon& rCandidate)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1)
+ {
+ // Start by copying the source polygon to get a writeable copy. The closed state is
+ // copied by aRetval's initialisation, too, so no need to copy it in this method
+ B2DPolygon aRetval(rCandidate);
+
+ // prepare geometry data. Get rounded from original
+ B2ITuple aPrevTuple(basegfx::fround(rCandidate.getB2DPoint(nPointCount - 1)));
+ B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
+ B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
+
+ // loop over all points. This will also snap the implicit closing edge
+ // even when not closed, but that's no problem here
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ // get next point. Get rounded from original
+ const bool bLastRun(a + 1 == nPointCount);
+ const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
+ const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
+ const B2ITuple aNextTuple(basegfx::fround(aNextPoint));
+
+ // get the states
+ const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
+ const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
+ const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
+ const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
+ const bool bSnapX(bPrevVertical || bNextVertical);
+ const bool bSnapY(bPrevHorizontal || bNextHorizontal);
+
+ if(bSnapX || bSnapY)
+ {
+ const B2DPoint aSnappedPoint(
+ bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
+ bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
+
+ aRetval.setB2DPoint(a, aSnappedPoint);
+ }
+
+ // prepare next point
+ if(!bLastRun)
+ {
+ aPrevTuple = aCurrTuple;
+ aCurrPoint = aNextPoint;
+ aCurrTuple = aNextTuple;
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ } // end of namespace tools
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+// eof
diff --git a/basegfx/source/polygon/b2dpolygontriangulator.cxx b/basegfx/source/polygon/b2dpolygontriangulator.cxx
new file mode 100644
index 000000000000..83fcc036c996
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolygontriangulator.cxx
@@ -0,0 +1,466 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <basegfx/polygon/b2dpolygontriangulator.hxx>
+#include <osl/diagnose.h>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/numeric/ftools.hxx>
+
+#include <algorithm>
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace
+ {
+ class EdgeEntry
+ {
+ EdgeEntry* mpNext;
+ B2DPoint maStart;
+ B2DPoint maEnd;
+ double mfAtan2;
+
+ public:
+ EdgeEntry(const B2DPoint& rStart, const B2DPoint& rEnd)
+ : mpNext(0L),
+ maStart(rStart),
+ maEnd(rEnd),
+ mfAtan2(0.0)
+ {
+ // make sure edge goes down. If horizontal, let it go to the right (left-handed).
+ bool bSwap(false);
+
+ if(::basegfx::fTools::equal(maStart.getY(), maEnd.getY()))
+ {
+ if(maStart.getX() > maEnd.getX())
+ {
+ bSwap = true;
+ }
+ }
+ else if(maStart.getY() > maEnd.getY())
+ {
+ bSwap = true;
+ }
+
+ if(bSwap)
+ {
+ maStart = rEnd;
+ maEnd = rStart;
+ }
+
+ mfAtan2 = atan2(maEnd.getY() - maStart.getY(), maEnd.getX() - maStart.getX());
+ }
+
+ ~EdgeEntry()
+ {
+ }
+
+ bool operator<(const EdgeEntry& rComp) const
+ {
+ if(::basegfx::fTools::equal(maStart.getY(), rComp.maStart.getY()))
+ {
+ if(::basegfx::fTools::equal(maStart.getX(), rComp.maStart.getX()))
+ {
+ // same in x and y -> same start point. Sort emitting vectors from left to right.
+ return (mfAtan2 > rComp.mfAtan2);
+ }
+
+ return (maStart.getX() < rComp.maStart.getX());
+ }
+
+ return (maStart.getY() < rComp.maStart.getY());
+ }
+
+ bool operator==(const EdgeEntry& rComp) const
+ {
+ return (maStart.equal(rComp.maStart) && maEnd.equal(rComp.maEnd));
+ }
+
+ bool operator!=(const EdgeEntry& rComp) const
+ {
+ return !(*this == rComp);
+ }
+
+ const B2DPoint& getStart() const { return maStart; }
+ const B2DPoint& getEnd() const { return maEnd; }
+
+ EdgeEntry* getNext() const { return mpNext; }
+ void setNext(EdgeEntry* pNext) { mpNext = pNext; }
+ };
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ typedef ::std::vector< EdgeEntry > EdgeEntries;
+ typedef ::std::vector< EdgeEntry* > EdgeEntryPointers;
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ class Triangulator
+ {
+ EdgeEntry* mpList;
+ EdgeEntries maStartEntries;
+ EdgeEntryPointers maNewEdgeEntries;
+ B2DPolygon maResult;
+
+ void handleClosingEdge(const B2DPoint& rStart, const B2DPoint& rEnd);
+ bool CheckPointInTriangle(EdgeEntry* pEdgeA, EdgeEntry* pEdgeB, const B2DPoint& rTestPoint);
+ void createTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC);
+
+ public:
+ Triangulator(const B2DPolyPolygon& rCandidate);
+ ~Triangulator();
+
+ const B2DPolygon getResult() const { return maResult; }
+ };
+
+ void Triangulator::handleClosingEdge(const B2DPoint& rStart, const B2DPoint& rEnd)
+ {
+ // create an entry, else the comparison might use the wrong edges
+ EdgeEntry aNew(rStart, rEnd);
+ EdgeEntry* pCurr = mpList;
+ EdgeEntry* pPrev = 0L;
+
+ while(pCurr
+ && pCurr->getStart().getY() <= aNew.getStart().getY()
+ && *pCurr != aNew)
+ {
+ pPrev = pCurr;
+ pCurr = pCurr->getNext();
+ }
+
+ if(pCurr && *pCurr == aNew)
+ {
+ // found closing edge, remove
+ if(pPrev)
+ {
+ pPrev->setNext(pCurr->getNext());
+ }
+ else
+ {
+ mpList = pCurr->getNext();
+ }
+ }
+ else
+ {
+ // insert closing edge
+ EdgeEntry* pNew = new EdgeEntry(aNew);
+ maNewEdgeEntries.push_back(pNew);
+ pCurr = mpList;
+ pPrev = 0L;
+
+ while(pCurr && *pCurr < *pNew)
+ {
+ pPrev = pCurr;
+ pCurr = pCurr->getNext();
+ }
+
+ if(pPrev)
+ {
+ pNew->setNext(pPrev->getNext());
+ pPrev->setNext(pNew);
+ }
+ else
+ {
+ pNew->setNext(mpList);
+ mpList = pNew;
+ }
+ }
+ }
+
+ bool Triangulator::CheckPointInTriangle(EdgeEntry* pEdgeA, EdgeEntry* pEdgeB, const B2DPoint& rTestPoint)
+ {
+ // inside triangle or on edge?
+ if(tools::isPointInTriangle(pEdgeA->getStart(), pEdgeA->getEnd(), pEdgeB->getEnd(), rTestPoint, true))
+ {
+ // but not on point
+ if(!rTestPoint.equal(pEdgeA->getEnd()) && !rTestPoint.equal(pEdgeB->getEnd()))
+ {
+ // found point in triangle -> split triangle inserting two edges
+ EdgeEntry* pStart = new EdgeEntry(pEdgeA->getStart(), rTestPoint);
+ EdgeEntry* pEnd = new EdgeEntry(*pStart);
+ maNewEdgeEntries.push_back(pStart);
+ maNewEdgeEntries.push_back(pEnd);
+
+ pStart->setNext(pEnd);
+ pEnd->setNext(pEdgeA->getNext());
+ pEdgeA->setNext(pStart);
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void Triangulator::createTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC)
+ {
+ maResult.append(rA);
+ maResult.append(rB);
+ maResult.append(rC);
+ }
+
+ // consume as long as there are edges
+ Triangulator::Triangulator(const B2DPolyPolygon& rCandidate)
+ : mpList(0L)
+ {
+ // add all available edges to the single linked local list which will be sorted
+ // by Y,X,atan2 when adding nodes
+ if(rCandidate.count())
+ {
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ const B2DPolygon aPolygonCandidate(rCandidate.getB2DPolygon(a));
+ const sal_uInt32 nCount(aPolygonCandidate.count());
+
+ if(nCount > 2L)
+ {
+ B2DPoint aPrevPnt(aPolygonCandidate.getB2DPoint(nCount - 1L));
+
+ for(sal_uInt32 b(0L); b < nCount; b++)
+ {
+ B2DPoint aNextPnt(aPolygonCandidate.getB2DPoint(b));
+
+ if( !aPrevPnt.equal(aNextPnt) )
+ {
+ maStartEntries.push_back(EdgeEntry(aPrevPnt, aNextPnt));
+ }
+
+ aPrevPnt = aNextPnt;
+ }
+ }
+ }
+
+ if(maStartEntries.size())
+ {
+ // sort initial list
+ ::std::sort(maStartEntries.begin(), maStartEntries.end());
+
+ // insert to own simply linked list
+ EdgeEntries::iterator aPos(maStartEntries.begin());
+ mpList = &(*aPos++);
+ EdgeEntry* pLast = mpList;
+
+ while(aPos != maStartEntries.end())
+ {
+ EdgeEntry* pEntry = &(*aPos++);
+ pLast->setNext(pEntry);
+ pLast = pEntry;
+ }
+ }
+ }
+
+ while(mpList)
+ {
+ if(mpList->getNext() && mpList->getNext()->getStart().equal(mpList->getStart()))
+ {
+ // next candidate. There are two edges and start point is equal.
+ // Length is not zero.
+ EdgeEntry* pEdgeA = mpList;
+ EdgeEntry* pEdgeB = pEdgeA->getNext();
+
+ if( pEdgeA->getEnd().equal(pEdgeB->getEnd()) )
+ {
+ // start and end equal -> neutral triangle, delete both
+ mpList = pEdgeB->getNext();
+ }
+ else
+ {
+ const B2DVector aLeft(pEdgeA->getEnd() - pEdgeA->getStart());
+ const B2DVector aRight(pEdgeB->getEnd() - pEdgeA->getStart());
+
+ if(ORIENTATION_NEUTRAL == getOrientation(aLeft, aRight))
+ {
+ // edges are parallel and have different length -> neutral triangle,
+ // delete both edges and handle closing edge
+ mpList = pEdgeB->getNext();
+ handleClosingEdge(pEdgeA->getEnd(), pEdgeB->getEnd());
+ }
+ else
+ {
+ // not parallel, look for points inside
+ B2DRange aRange(pEdgeA->getStart(), pEdgeA->getEnd());
+ aRange.expand(pEdgeB->getEnd());
+ EdgeEntry* pTestEdge = pEdgeB->getNext();
+ bool bNoPointInTriangle(true);
+
+ // look for start point in triangle
+ while(bNoPointInTriangle && pTestEdge)
+ {
+ if(aRange.getMaxY() < pTestEdge->getStart().getY())
+ {
+ // edge is below test range and edges are sorted -> stop looking
+ break;
+ }
+ else
+ {
+ // do not look for edges with same start point, they are sorted and cannot end inside.
+ if(!pTestEdge->getStart().equal(pEdgeA->getStart()))
+ {
+ if(aRange.isInside(pTestEdge->getStart()))
+ {
+ bNoPointInTriangle = CheckPointInTriangle(pEdgeA, pEdgeB, pTestEdge->getStart());
+ }
+ }
+ }
+
+ // next candidate
+ pTestEdge = pTestEdge->getNext();
+ }
+
+ if(bNoPointInTriangle)
+ {
+ // look for end point in triange
+ pTestEdge = pEdgeB->getNext();
+
+ while(bNoPointInTriangle && pTestEdge)
+ {
+ if(aRange.getMaxY() < pTestEdge->getStart().getY())
+ {
+ // edge is below test range and edges are sorted -> stop looking
+ break;
+ }
+ else
+ {
+ // do not look for edges with same end point, they are sorted and cannot end inside.
+ if(!pTestEdge->getEnd().equal(pEdgeA->getStart()))
+ {
+ if(aRange.isInside(pTestEdge->getEnd()))
+ {
+ bNoPointInTriangle = CheckPointInTriangle(pEdgeA, pEdgeB, pTestEdge->getEnd());
+ }
+ }
+ }
+
+ // next candidate
+ pTestEdge = pTestEdge->getNext();
+ }
+ }
+
+ if(bNoPointInTriangle)
+ {
+ // create triangle, remove edges, handle closing edge
+ mpList = pEdgeB->getNext();
+ createTriangle(pEdgeA->getStart(), pEdgeB->getEnd(), pEdgeA->getEnd());
+ handleClosingEdge(pEdgeA->getEnd(), pEdgeB->getEnd());
+ }
+ }
+ }
+ }
+ else
+ {
+ // only one entry at start point, delete it
+ mpList = mpList->getNext();
+ }
+ }
+ }
+
+ Triangulator::~Triangulator()
+ {
+ EdgeEntryPointers::iterator aIter(maNewEdgeEntries.begin());
+
+ while(aIter != maNewEdgeEntries.end())
+ {
+ delete (*aIter++);
+ }
+ }
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace triangulator
+ {
+ B2DPolygon triangulate(const B2DPolygon& rCandidate)
+ {
+ B2DPolygon aRetval;
+
+ // subdivide locally (triangulate does not work with beziers), remove double and neutral points
+ B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? tools::adaptiveSubdivideByAngle(rCandidate) : rCandidate);
+ aCandidate.removeDoublePoints();
+ aCandidate = tools::removeNeutralPoints(aCandidate);
+
+ if(2L == aCandidate.count())
+ {
+ // candidate IS a triangle, just append
+ aRetval.append(aCandidate);
+ }
+ else if(aCandidate.count() > 2L)
+ {
+ if(tools::isConvex(aCandidate))
+ {
+ // polygon is convex, just use a triangle fan
+ tools::addTriangleFan(aCandidate, aRetval);
+ }
+ else
+ {
+ // polygon is concave.
+ const B2DPolyPolygon aCandPolyPoly(aCandidate);
+ Triangulator aTriangulator(aCandPolyPoly);
+ aRetval = aTriangulator.getResult();
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolygon triangulate(const B2DPolyPolygon& rCandidate)
+ {
+ B2DPolygon aRetval;
+
+ // subdivide locally (triangulate does not work with beziers)
+ B2DPolyPolygon aCandidate(rCandidate.areControlPointsUsed() ? tools::adaptiveSubdivideByAngle(rCandidate) : rCandidate);
+
+ if(1L == aCandidate.count())
+ {
+ // single polygon -> single polygon triangulation
+ const B2DPolygon aSinglePolygon(aCandidate.getB2DPolygon(0L));
+ aRetval = triangulate(aSinglePolygon);
+ }
+ else
+ {
+ Triangulator aTriangulator(aCandidate);
+ aRetval = aTriangulator.getResult();
+ }
+
+ return aRetval;
+ }
+ } // end of namespace triangulator
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+// eof
diff --git a/basegfx/source/polygon/b2dpolypolygon.cxx b/basegfx/source/polygon/b2dpolypolygon.cxx
new file mode 100644
index 000000000000..767d2b25ced5
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolypolygon.cxx
@@ -0,0 +1,380 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <osl/diagnose.h>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <rtl/instance.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <functional>
+#include <vector>
+#include <algorithm>
+
+//////////////////////////////////////////////////////////////////////////////
+
+class ImplB2DPolyPolygon
+{
+ typedef ::std::vector< basegfx::B2DPolygon > PolygonVector;
+
+ PolygonVector maPolygons;
+
+public:
+ ImplB2DPolyPolygon() : maPolygons()
+ {
+ }
+
+ ImplB2DPolyPolygon(const basegfx::B2DPolygon& rToBeCopied) :
+ maPolygons(1,rToBeCopied)
+ {
+ }
+
+ bool operator==(const ImplB2DPolyPolygon& rPolygonList) const
+ {
+ // same polygon count?
+ if(maPolygons.size() != rPolygonList.maPolygons.size())
+ return false;
+
+ // compare polygon content
+ if(!(maPolygons == rPolygonList.maPolygons))
+ return false;
+
+ return true;
+ }
+
+ const basegfx::B2DPolygon& getB2DPolygon(sal_uInt32 nIndex) const
+ {
+ return maPolygons[nIndex];
+ }
+
+ void setB2DPolygon(sal_uInt32 nIndex, const basegfx::B2DPolygon& rPolygon)
+ {
+ maPolygons[nIndex] = rPolygon;
+ }
+
+ void insert(sal_uInt32 nIndex, const basegfx::B2DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rPolygon
+ PolygonVector::iterator aIndex(maPolygons.begin());
+ aIndex += nIndex;
+ maPolygons.insert(aIndex, nCount, rPolygon);
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const basegfx::B2DPolyPolygon& rPolyPolygon)
+ {
+ const sal_uInt32 nCount = rPolyPolygon.count();
+
+ if(nCount)
+ {
+ // add nCount polygons from rPolyPolygon
+ maPolygons.reserve(maPolygons.size() + nCount);
+ PolygonVector::iterator aIndex(maPolygons.begin());
+ aIndex += nIndex;
+
+ for(sal_uInt32 a(0L); a < nCount; a++)
+ {
+ aIndex = maPolygons.insert(aIndex, rPolyPolygon.getB2DPolygon(a));
+ aIndex++;
+ }
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // remove polygon data
+ PolygonVector::iterator aStart(maPolygons.begin());
+ aStart += nIndex;
+ const PolygonVector::iterator aEnd(aStart + nCount);
+
+ maPolygons.erase(aStart, aEnd);
+ }
+ }
+
+ sal_uInt32 count() const
+ {
+ return maPolygons.size();
+ }
+
+ void setClosed(bool bNew)
+ {
+ for(sal_uInt32 a(0L); a < maPolygons.size(); a++)
+ {
+ maPolygons[a].setClosed(bNew);
+ }
+ }
+
+ void flip()
+ {
+ std::for_each( maPolygons.begin(),
+ maPolygons.end(),
+ std::mem_fun_ref( &basegfx::B2DPolygon::flip ));
+ }
+
+ void removeDoublePoints()
+ {
+ std::for_each( maPolygons.begin(),
+ maPolygons.end(),
+ std::mem_fun_ref( &basegfx::B2DPolygon::removeDoublePoints ));
+ }
+
+ void transform(const basegfx::B2DHomMatrix& rMatrix)
+ {
+ for(sal_uInt32 a(0L); a < maPolygons.size(); a++)
+ {
+ maPolygons[a].transform(rMatrix);
+ }
+ }
+
+ void makeUnique()
+ {
+ std::for_each( maPolygons.begin(),
+ maPolygons.end(),
+ std::mem_fun_ref( &basegfx::B2DPolygon::makeUnique ));
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace { struct DefaultPolyPolygon: public rtl::Static<B2DPolyPolygon::ImplType,
+ DefaultPolyPolygon> {}; }
+
+ B2DPolyPolygon::B2DPolyPolygon() :
+ mpPolyPolygon(DefaultPolyPolygon::get())
+ {
+ }
+
+ B2DPolyPolygon::B2DPolyPolygon(const B2DPolyPolygon& rPolyPolygon) :
+ mpPolyPolygon(rPolyPolygon.mpPolyPolygon)
+ {
+ }
+
+ B2DPolyPolygon::B2DPolyPolygon(const B2DPolygon& rPolygon) :
+ mpPolyPolygon( ImplB2DPolyPolygon(rPolygon) )
+ {
+ }
+
+ B2DPolyPolygon::~B2DPolyPolygon()
+ {
+ }
+
+ B2DPolyPolygon& B2DPolyPolygon::operator=(const B2DPolyPolygon& rPolyPolygon)
+ {
+ mpPolyPolygon = rPolyPolygon.mpPolyPolygon;
+ return *this;
+ }
+
+ void B2DPolyPolygon::makeUnique()
+ {
+ mpPolyPolygon.make_unique();
+ mpPolyPolygon->makeUnique();
+ }
+
+ bool B2DPolyPolygon::operator==(const B2DPolyPolygon& rPolyPolygon) const
+ {
+ if(mpPolyPolygon.same_object(rPolyPolygon.mpPolyPolygon))
+ return true;
+
+ return ((*mpPolyPolygon) == (*rPolyPolygon.mpPolyPolygon));
+ }
+
+ bool B2DPolyPolygon::operator!=(const B2DPolyPolygon& rPolyPolygon) const
+ {
+ return !((*this) == rPolyPolygon);
+ }
+
+ sal_uInt32 B2DPolyPolygon::count() const
+ {
+ return mpPolyPolygon->count();
+ }
+
+ B2DPolygon B2DPolyPolygon::getB2DPolygon(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolyPolygon->count(), "B2DPolyPolygon access outside range (!)");
+
+ return mpPolyPolygon->getB2DPolygon(nIndex);
+ }
+
+ void B2DPolyPolygon::setB2DPolygon(sal_uInt32 nIndex, const B2DPolygon& rPolygon)
+ {
+ OSL_ENSURE(nIndex < mpPolyPolygon->count(), "B2DPolyPolygon access outside range (!)");
+
+ if(getB2DPolygon(nIndex) != rPolygon)
+ mpPolyPolygon->setB2DPolygon(nIndex, rPolygon);
+ }
+
+ bool B2DPolyPolygon::areControlPointsUsed() const
+ {
+ for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++)
+ {
+ const B2DPolygon& rPolygon = mpPolyPolygon->getB2DPolygon(a);
+
+ if(rPolygon.areControlPointsUsed())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void B2DPolyPolygon::insert(sal_uInt32 nIndex, const B2DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex <= mpPolyPolygon->count(), "B2DPolyPolygon Insert outside range (!)");
+
+ if(nCount)
+ mpPolyPolygon->insert(nIndex, rPolygon, nCount);
+ }
+
+ void B2DPolyPolygon::append(const B2DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ if(nCount)
+ mpPolyPolygon->insert(mpPolyPolygon->count(), rPolygon, nCount);
+ }
+
+ B2DPolyPolygon B2DPolyPolygon::getDefaultAdaptiveSubdivision() const
+ {
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++)
+ {
+ aRetval.append(mpPolyPolygon->getB2DPolygon(a).getDefaultAdaptiveSubdivision());
+ }
+
+ return aRetval;
+ }
+
+ B2DRange B2DPolyPolygon::getB2DRange() const
+ {
+ B2DRange aRetval;
+
+ for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++)
+ {
+ aRetval.expand(mpPolyPolygon->getB2DPolygon(a).getB2DRange());
+ }
+
+ return aRetval;
+ }
+
+ void B2DPolyPolygon::insert(sal_uInt32 nIndex, const B2DPolyPolygon& rPolyPolygon)
+ {
+ OSL_ENSURE(nIndex <= mpPolyPolygon->count(), "B2DPolyPolygon Insert outside range (!)");
+
+ if(rPolyPolygon.count())
+ mpPolyPolygon->insert(nIndex, rPolyPolygon);
+ }
+
+ void B2DPolyPolygon::append(const B2DPolyPolygon& rPolyPolygon)
+ {
+ if(rPolyPolygon.count())
+ mpPolyPolygon->insert(mpPolyPolygon->count(), rPolyPolygon);
+ }
+
+ void B2DPolyPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex + nCount <= mpPolyPolygon->count(), "B2DPolyPolygon Remove outside range (!)");
+
+ if(nCount)
+ mpPolyPolygon->remove(nIndex, nCount);
+ }
+
+ void B2DPolyPolygon::clear()
+ {
+ mpPolyPolygon = DefaultPolyPolygon::get();
+ }
+
+ bool B2DPolyPolygon::isClosed() const
+ {
+ bool bRetval(true);
+
+ // PolyPOlygon is closed when all contained Polygons are closed or
+ // no Polygon exists.
+ for(sal_uInt32 a(0L); bRetval && a < mpPolyPolygon->count(); a++)
+ {
+ if(!(mpPolyPolygon->getB2DPolygon(a)).isClosed())
+ {
+ bRetval = false;
+ }
+ }
+
+ return bRetval;
+ }
+
+ void B2DPolyPolygon::setClosed(bool bNew)
+ {
+ if(bNew != isClosed())
+ mpPolyPolygon->setClosed(bNew);
+ }
+
+ void B2DPolyPolygon::flip()
+ {
+ if(mpPolyPolygon->count())
+ {
+ mpPolyPolygon->flip();
+ }
+ }
+
+ bool B2DPolyPolygon::hasDoublePoints() const
+ {
+ bool bRetval(false);
+
+ for(sal_uInt32 a(0L); !bRetval && a < mpPolyPolygon->count(); a++)
+ {
+ if((mpPolyPolygon->getB2DPolygon(a)).hasDoublePoints())
+ {
+ bRetval = true;
+ }
+ }
+
+ return bRetval;
+ }
+
+ void B2DPolyPolygon::removeDoublePoints()
+ {
+ if(hasDoublePoints())
+ mpPolyPolygon->removeDoublePoints();
+ }
+
+ void B2DPolyPolygon::transform(const B2DHomMatrix& rMatrix)
+ {
+ if(mpPolyPolygon->count() && !rMatrix.isIdentity())
+ {
+ mpPolyPolygon->transform(rMatrix);
+ }
+ }
+} // end of namespace basegfx
+
+// eof
diff --git a/basegfx/source/polygon/b2dpolypolygoncutter.cxx b/basegfx/source/polygon/b2dpolypolygoncutter.cxx
new file mode 100644
index 000000000000..4f9cf3a75f72
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolypolygoncutter.cxx
@@ -0,0 +1,1014 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <osl/diagnose.h>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolygoncutandtouch.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <vector>
+#include <algorithm>
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace
+ {
+ //////////////////////////////////////////////////////////////////////////////
+
+ struct StripHelper
+ {
+ B2DRange maRange;
+ sal_Int32 mnDepth;
+ B2VectorOrientation meOrinetation;
+ };
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ struct PN
+ {
+ public:
+ B2DPoint maPoint;
+ sal_uInt32 mnI;
+ sal_uInt32 mnIP;
+ sal_uInt32 mnIN;
+ };
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ struct VN
+ {
+ public:
+ B2DVector maPrev;
+ B2DVector maNext;
+
+ // to have the correct curve segments in the crossover checks,
+ // it is necessary to keep the original next vectors, too. Else,
+ // it may happen to use a already switched next vector which
+ // would interpolate the wrong comparison point
+ B2DVector maOriginalNext;
+ };
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ struct SN
+ {
+ public:
+ PN* mpPN;
+
+ bool operator<(const SN& rComp) const
+ {
+ if(fTools::equal(mpPN->maPoint.getX(), rComp.mpPN->maPoint.getX()))
+ {
+ if(fTools::equal(mpPN->maPoint.getY(), rComp.mpPN->maPoint.getY()))
+ {
+ return (mpPN->mnI < rComp.mpPN->mnI);
+ }
+ else
+ {
+ return fTools::less(mpPN->maPoint.getY(), rComp.mpPN->maPoint.getY());
+ }
+ }
+ else
+ {
+ return fTools::less(mpPN->maPoint.getX(), rComp.mpPN->maPoint.getX());
+ }
+ }
+ };
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ typedef ::std::vector< PN > PNV;
+ typedef ::std::vector< VN > VNV;
+ typedef ::std::vector< SN > SNV;
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ class solver
+ {
+ private:
+ const B2DPolyPolygon maOriginal;
+ PNV maPNV;
+ VNV maVNV;
+ SNV maSNV;
+
+ unsigned mbIsCurve : 1;
+ unsigned mbChanged : 1;
+
+ void impAddPolygon(const sal_uInt32 aPos, const B2DPolygon& rGeometry)
+ {
+ const sal_uInt32 nCount(rGeometry.count());
+ PN aNewPN;
+ VN aNewVN;
+ SN aNewSN;
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ const B2DPoint aPoint(rGeometry.getB2DPoint(a));
+ aNewPN.maPoint = aPoint;
+ aNewPN.mnI = aPos + a;
+ aNewPN.mnIP = aPos + ((a != 0) ? a - 1 : nCount - 1);
+ aNewPN.mnIN = aPos + ((a + 1 == nCount) ? 0 : a + 1);
+ maPNV.push_back(aNewPN);
+
+ if(mbIsCurve)
+ {
+ aNewVN.maPrev = rGeometry.getPrevControlPoint(a) - aPoint;
+ aNewVN.maNext = rGeometry.getNextControlPoint(a) - aPoint;
+ aNewVN.maOriginalNext = aNewVN.maNext;
+ maVNV.push_back(aNewVN);
+ }
+
+ aNewSN.mpPN = &maPNV[maPNV.size() - 1];
+ maSNV.push_back(aNewSN);
+ }
+ }
+
+ bool impLeftOfEdges(const B2DVector& rVecA, const B2DVector& rVecB, const B2DVector& rTest)
+ {
+ // tests if rTest is left of both directed line segments along the line -rVecA, rVecB. Test is
+ // with border.
+ if(rVecA.cross(rVecB) > 0.0)
+ {
+ // b is left turn seen from a, test if Test is left of both and so inside (left is seeen as inside)
+ const bool bBoolA(fTools::moreOrEqual(rVecA.cross(rTest), 0.0));
+ const bool bBoolB(fTools::lessOrEqual(rVecB.cross(rTest), 0.0));
+
+ return (bBoolA && bBoolB);
+ }
+ else
+ {
+ // b is right turn seen from a, test if Test is right of both and so outside (left is seeen as inside)
+ const bool bBoolA(fTools::lessOrEqual(rVecA.cross(rTest), 0.0));
+ const bool bBoolB(fTools::moreOrEqual(rVecB.cross(rTest), 0.0));
+
+ return (!(bBoolA && bBoolB));
+ }
+ }
+
+ void impSwitchNext(PN& rPNa, PN& rPNb)
+ {
+ ::std::swap(rPNa.mnIN, rPNb.mnIN);
+
+ if(mbIsCurve)
+ {
+ VN& rVNa = maVNV[rPNa.mnI];
+ VN& rVNb = maVNV[rPNb.mnI];
+
+ ::std::swap(rVNa.maNext, rVNb.maNext);
+ }
+
+ if(!mbChanged)
+ {
+ mbChanged = true;
+ }
+ }
+
+ B2DCubicBezier createSegment(const PN& rPN, bool bPrev) const
+ {
+ const B2DPoint& rStart(rPN.maPoint);
+ const B2DPoint& rEnd(maPNV[bPrev ? rPN.mnIP : rPN.mnIN].maPoint);
+ const B2DVector& rCPA(bPrev ? maVNV[rPN.mnI].maPrev : maVNV[rPN.mnI].maNext);
+ // Use maOriginalNext, not maNext to create the original (yet unchanged)
+ // curve segment. Otherwise, this segment would NOT ne correct.
+ const B2DVector& rCPB(bPrev ? maVNV[maPNV[rPN.mnIP].mnI].maOriginalNext : maVNV[maPNV[rPN.mnIN].mnI].maPrev);
+
+ return B2DCubicBezier(rStart, rStart + rCPA, rEnd + rCPB, rEnd);
+ }
+
+ void impHandleCommon(PN& rPNa, PN& rPNb)
+ {
+ if(mbIsCurve)
+ {
+ const B2DCubicBezier aNextA(createSegment(rPNa, false));
+ const B2DCubicBezier aPrevA(createSegment(rPNa, true));
+
+ if(aNextA.equal(aPrevA))
+ {
+ // deadend on A (identical edge)
+ return;
+ }
+
+ const B2DCubicBezier aNextB(createSegment(rPNb, false));
+ const B2DCubicBezier aPrevB(createSegment(rPNb, true));
+
+ if(aNextB.equal(aPrevB))
+ {
+ // deadend on B (identical edge)
+ return;
+ }
+
+ if(aPrevA.equal(aPrevB))
+ {
+ // common edge in same direction
+ if(aNextA.equal(aNextB))
+ {
+ // common edge in same direction continues
+ return;
+ }
+ else
+ {
+ // common edge in same direction leave
+ // action is done on enter
+ return;
+ }
+ }
+ else if(aPrevA.equal(aNextB))
+ {
+ // common edge in opposite direction
+ if(aNextA.equal(aPrevB))
+ {
+ // common edge in opposite direction continues
+ return;
+ }
+ else
+ {
+ // common edge in opposite direction leave
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ else if(aNextA.equal(aNextB))
+ {
+ // common edge in same direction enter
+ // search leave edge
+ PN* pPNa2 = &maPNV[rPNa.mnIN];
+ PN* pPNb2 = &maPNV[rPNb.mnIN];
+ bool bOnEdge(true);
+
+ do
+ {
+ const B2DCubicBezier aNextA2(createSegment(*pPNa2, false));
+ const B2DCubicBezier aNextB2(createSegment(*pPNb2, false));
+
+ if(aNextA2.equal(aNextB2))
+ {
+ pPNa2 = &maPNV[pPNa2->mnIN];
+ pPNb2 = &maPNV[pPNb2->mnIN];
+ }
+ else
+ {
+ bOnEdge = false;
+ }
+ }
+ while(bOnEdge && pPNa2 != &rPNa && pPNa2 != &rPNa);
+
+ if(bOnEdge)
+ {
+ // loop over two identical polygon paths
+ return;
+ }
+ else
+ {
+ // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges
+ // at enter/leave. Check for crossover.
+ const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint());
+ const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint());
+ const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint());
+ const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB));
+
+ const B2DCubicBezier aNextA2(createSegment(*pPNa2, false));
+ const B2DCubicBezier aPrevA2(createSegment(*pPNa2, true));
+ const B2DCubicBezier aNextB2(createSegment(*pPNb2, false));
+ const B2DVector aPrevCA2(aPrevA2.interpolatePoint(0.5) - aPrevA2.getStartPoint());
+ const B2DVector aNextCA2(aNextA2.interpolatePoint(0.5) - aNextA2.getStartPoint());
+ const B2DVector aNextCB2(aNextB2.interpolatePoint(0.5) - aNextB2.getStartPoint());
+ const bool bLeave(impLeftOfEdges(aPrevCA2, aNextCA2, aNextCB2));
+
+ if(bEnter != bLeave)
+ {
+ // crossover
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ }
+ else if(aNextA.equal(aPrevB))
+ {
+ // common edge in opposite direction enter
+ impSwitchNext(rPNa, rPNb);
+ }
+ else
+ {
+ // no common edges, check for crossover
+ const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint());
+ const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint());
+ const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint());
+ const B2DVector aNextCB(aNextB.interpolatePoint(0.5) - aNextB.getStartPoint());
+
+ const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB));
+ const bool bLeave(impLeftOfEdges(aPrevCA, aNextCA, aNextCB));
+
+ if(bEnter != bLeave)
+ {
+ // crossover
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ }
+ else
+ {
+ const B2DPoint& rNextA(maPNV[rPNa.mnIN].maPoint);
+ const B2DPoint& rPrevA(maPNV[rPNa.mnIP].maPoint);
+
+ if(rNextA.equal(rPrevA))
+ {
+ // deadend on A
+ return;
+ }
+
+ const B2DPoint& rNextB(maPNV[rPNb.mnIN].maPoint);
+ const B2DPoint& rPrevB(maPNV[rPNb.mnIP].maPoint);
+
+ if(rNextB.equal(rPrevB))
+ {
+ // deadend on B
+ return;
+ }
+
+ if(rPrevA.equal(rPrevB))
+ {
+ // common edge in same direction
+ if(rNextA.equal(rNextB))
+ {
+ // common edge in same direction continues
+ return;
+ }
+ else
+ {
+ // common edge in same direction leave
+ // action is done on enter
+ return;
+ }
+ }
+ else if(rPrevA.equal(rNextB))
+ {
+ // common edge in opposite direction
+ if(rNextA.equal(rPrevB))
+ {
+ // common edge in opposite direction continues
+ return;
+ }
+ else
+ {
+ // common edge in opposite direction leave
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ else if(rNextA.equal(rNextB))
+ {
+ // common edge in same direction enter
+ // search leave edge
+ PN* pPNa2 = &maPNV[rPNa.mnIN];
+ PN* pPNb2 = &maPNV[rPNb.mnIN];
+ bool bOnEdge(true);
+
+ do
+ {
+ const B2DPoint& rNextA2(maPNV[pPNa2->mnIN].maPoint);
+ const B2DPoint& rNextB2(maPNV[pPNb2->mnIN].maPoint);
+
+ if(rNextA2.equal(rNextB2))
+ {
+ pPNa2 = &maPNV[pPNa2->mnIN];
+ pPNb2 = &maPNV[pPNb2->mnIN];
+ }
+ else
+ {
+ bOnEdge = false;
+ }
+ }
+ while(bOnEdge && pPNa2 != &rPNa && pPNa2 != &rPNa);
+
+ if(bOnEdge)
+ {
+ // loop over two identical polygon paths
+ return;
+ }
+ else
+ {
+ // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges
+ // at enter/leave. Check for crossover.
+ const B2DPoint& aPointE(rPNa.maPoint);
+ const B2DVector aPrevAE(rPrevA - aPointE);
+ const B2DVector aNextAE(rNextA - aPointE);
+ const B2DVector aPrevBE(rPrevB - aPointE);
+
+ const B2DPoint& aPointL(pPNa2->maPoint);
+ const B2DVector aPrevAL(maPNV[pPNa2->mnIP].maPoint - aPointL);
+ const B2DVector aNextAL(maPNV[pPNa2->mnIN].maPoint - aPointL);
+ const B2DVector aNextBL(maPNV[pPNb2->mnIN].maPoint - aPointL);
+
+ const bool bEnter(impLeftOfEdges(aPrevAE, aNextAE, aPrevBE));
+ const bool bLeave(impLeftOfEdges(aPrevAL, aNextAL, aNextBL));
+
+ if(bEnter != bLeave)
+ {
+ // crossover; switch start or end
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ }
+ else if(rNextA.equal(rPrevB))
+ {
+ // common edge in opposite direction enter
+ impSwitchNext(rPNa, rPNb);
+ }
+ else
+ {
+ // no common edges, check for crossover
+ const B2DPoint& aPoint(rPNa.maPoint);
+ const B2DVector aPrevA(rPrevA - aPoint);
+ const B2DVector aNextA(rNextA - aPoint);
+ const B2DVector aPrevB(rPrevB - aPoint);
+ const B2DVector aNextB(rNextB - aPoint);
+
+ const bool bEnter(impLeftOfEdges(aPrevA, aNextA, aPrevB));
+ const bool bLeave(impLeftOfEdges(aPrevA, aNextA, aNextB));
+
+ if(bEnter != bLeave)
+ {
+ // crossover
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ }
+ }
+
+ void impSolve()
+ {
+ // sort by point to identify common nodes
+ ::std::sort(maSNV.begin(), maSNV.end());
+
+ // handle common nodes
+ const sal_uInt32 nNodeCount(maSNV.size());
+
+ for(sal_uInt32 a(0); a < nNodeCount - 1; a++)
+ {
+ // test a before using it, not after. Also use nPointCount instead of aSortNodes.size()
+ PN& rPNb = *(maSNV[a].mpPN);
+
+ for(sal_uInt32 b(a + 1); b < nNodeCount && rPNb.maPoint.equal(maSNV[b].mpPN->maPoint); b++)
+ {
+ impHandleCommon(rPNb, *maSNV[b].mpPN);
+ }
+ }
+ }
+
+ public:
+ solver(const B2DPolygon& rOriginal)
+ : maOriginal(B2DPolyPolygon(rOriginal)),
+ mbIsCurve(false),
+ mbChanged(false)
+ {
+ const sal_uInt32 nOriginalCount(rOriginal.count());
+
+ if(nOriginalCount)
+ {
+ B2DPolygon aGeometry(tools::addPointsAtCutsAndTouches(rOriginal));
+ aGeometry.removeDoublePoints();
+ aGeometry = tools::simplifyCurveSegments(aGeometry);
+ mbIsCurve = aGeometry.areControlPointsUsed();
+
+ const sal_uInt32 nPointCount(aGeometry.count());
+
+ // If it's not a pezier polygon, at least four points are needed to create
+ // a self-intersection. If it's a bezier polygon, the minimum point number
+ // is two, since with a single point You get a curve, but no self-intersection
+ if(nPointCount > 3 || (nPointCount > 1 && mbIsCurve))
+ {
+ // reserve space in point, control and sort vector.
+ maSNV.reserve(nPointCount);
+ maPNV.reserve(nPointCount);
+ maVNV.reserve(mbIsCurve ? nPointCount : 0);
+
+ // fill data
+ impAddPolygon(0, aGeometry);
+
+ // solve common nodes
+ impSolve();
+ }
+ }
+ }
+
+ solver(const B2DPolyPolygon& rOriginal)
+ : maOriginal(rOriginal),
+ mbIsCurve(false),
+ mbChanged(false)
+ {
+ sal_uInt32 nOriginalCount(maOriginal.count());
+
+ if(nOriginalCount)
+ {
+ B2DPolyPolygon aGeometry(tools::addPointsAtCutsAndTouches(maOriginal, true));
+ aGeometry.removeDoublePoints();
+ aGeometry = tools::simplifyCurveSegments(aGeometry);
+ mbIsCurve = aGeometry.areControlPointsUsed();
+ nOriginalCount = aGeometry.count();
+
+ if(nOriginalCount)
+ {
+ sal_uInt32 nPointCount(0);
+ sal_uInt32 a(0);
+
+ // count points
+ for(a = 0; a < nOriginalCount; a++)
+ {
+ const B2DPolygon aCandidate(aGeometry.getB2DPolygon(a));
+ const sal_uInt32 nCandCount(aCandidate.count());
+
+ // If it's not a bezier curve, at least three points would be needed to have a
+ // topological relevant (not empty) polygon. Since its not known here if trivial
+ // edges (dead ends) will be kept or sorted out, add non-bezier polygons with
+ // more than one point.
+ // For bezier curves, the minimum for defining an area is also one.
+ if(nCandCount)
+ {
+ nPointCount += nCandCount;
+ }
+ }
+
+ if(nPointCount)
+ {
+ // reserve space in point, control and sort vector.
+ maSNV.reserve(nPointCount);
+ maPNV.reserve(nPointCount);
+ maVNV.reserve(mbIsCurve ? nPointCount : 0);
+
+ // fill data
+ sal_uInt32 nInsertIndex(0);
+
+ for(a = 0; a < nOriginalCount; a++)
+ {
+ const B2DPolygon aCandidate(aGeometry.getB2DPolygon(a));
+ const sal_uInt32 nCandCount(aCandidate.count());
+
+ // use same condition as above, the data vector is
+ // pre-allocated
+ if(nCandCount)
+ {
+ impAddPolygon(nInsertIndex, aCandidate);
+ nInsertIndex += nCandCount;
+ }
+ }
+
+ // solve common nodes
+ impSolve();
+ }
+ }
+ }
+ }
+
+ B2DPolyPolygon getB2DPolyPolygon()
+ {
+ if(mbChanged)
+ {
+ B2DPolyPolygon aRetval;
+ const sal_uInt32 nCount(maPNV.size());
+ sal_uInt32 nCountdown(nCount);
+
+ for(sal_uInt32 a(0); nCountdown && a < nCount; a++)
+ {
+ PN& rPN = maPNV[a];
+
+ if(SAL_MAX_UINT32 != rPN.mnI)
+ {
+ // unused node, start new part polygon
+ B2DPolygon aNewPart;
+ PN* pPNCurr = &rPN;
+
+ do
+ {
+ const B2DPoint& rPoint = pPNCurr->maPoint;
+ aNewPart.append(rPoint);
+
+ if(mbIsCurve)
+ {
+ const VN& rVNCurr = maVNV[pPNCurr->mnI];
+
+ if(!rVNCurr.maPrev.equalZero())
+ {
+ aNewPart.setPrevControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maPrev);
+ }
+
+ if(!rVNCurr.maNext.equalZero())
+ {
+ aNewPart.setNextControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maNext);
+ }
+ }
+
+ pPNCurr->mnI = SAL_MAX_UINT32;
+ nCountdown--;
+ pPNCurr = &(maPNV[pPNCurr->mnIN]);
+ }
+ while(pPNCurr != &rPN && SAL_MAX_UINT32 != pPNCurr->mnI);
+
+ // close and add
+ aNewPart.setClosed(true);
+ aRetval.append(aNewPart);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ // no change, return original
+ return maOriginal;
+ }
+ }
+ };
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace tools
+ {
+ //////////////////////////////////////////////////////////////////////////////
+
+ B2DPolyPolygon solveCrossovers(const B2DPolyPolygon& rCandidate)
+ {
+ if(rCandidate.count() > 1L)
+ {
+ solver aSolver(rCandidate);
+ return aSolver.getB2DPolyPolygon();
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ B2DPolyPolygon solveCrossovers(const B2DPolygon& rCandidate)
+ {
+ solver aSolver(rCandidate);
+ return aSolver.getB2DPolyPolygon();
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ B2DPolyPolygon stripNeutralPolygons(const B2DPolyPolygon& rCandidate)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ if(ORIENTATION_NEUTRAL != tools::getOrientation(aCandidate))
+ {
+ aRetval.append(aCandidate);
+ }
+ }
+
+ return aRetval;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ B2DPolyPolygon stripDispensablePolygons(const B2DPolyPolygon& rCandidate, bool bKeepAboveZero)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ if(nCount)
+ {
+ if(nCount == 1L)
+ {
+ if(!bKeepAboveZero && ORIENTATION_POSITIVE == tools::getOrientation(rCandidate.getB2DPolygon(0L)))
+ {
+ aRetval = rCandidate;
+ }
+ }
+ else
+ {
+ sal_uInt32 a, b;
+ ::std::vector< StripHelper > aHelpers;
+ aHelpers.resize(nCount);
+
+ for(a = 0L; a < nCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+ StripHelper* pNewHelper = &(aHelpers[a]);
+ pNewHelper->maRange = tools::getRange(aCandidate);
+ pNewHelper->meOrinetation = tools::getOrientation(aCandidate);
+ pNewHelper->mnDepth = (ORIENTATION_NEGATIVE == pNewHelper->meOrinetation ? -1L : 0L);
+ }
+
+ for(a = 0L; a < nCount - 1L; a++)
+ {
+ const B2DPolygon aCandA(rCandidate.getB2DPolygon(a));
+ StripHelper& rHelperA = aHelpers[a];
+
+ for(b = a + 1L; b < nCount; b++)
+ {
+ const B2DPolygon aCandB(rCandidate.getB2DPolygon(b));
+ StripHelper& rHelperB = aHelpers[b];
+ const bool bAInB(rHelperB.maRange.isInside(rHelperA.maRange) && tools::isInside(aCandB, aCandA, true));
+ const bool bBInA(rHelperA.maRange.isInside(rHelperB.maRange) && tools::isInside(aCandA, aCandB, true));
+
+ if(bAInB && bBInA)
+ {
+ // congruent
+ if(rHelperA.meOrinetation == rHelperB.meOrinetation)
+ {
+ // two polys or two holes. Lower one of them to get one of them out of the way.
+ // Since each will be contained in the other one, both will be increased, too.
+ // So, for lowering, increase only one of them
+ rHelperA.mnDepth++;
+ }
+ else
+ {
+ // poly and hole. They neutralize, so get rid of both. Move securely below zero.
+ rHelperA.mnDepth = -((sal_Int32)nCount);
+ rHelperB.mnDepth = -((sal_Int32)nCount);
+ }
+ }
+ else
+ {
+ if(bAInB)
+ {
+ if(ORIENTATION_NEGATIVE == rHelperB.meOrinetation)
+ {
+ rHelperA.mnDepth--;
+ }
+ else
+ {
+ rHelperA.mnDepth++;
+ }
+ }
+ else if(bBInA)
+ {
+ if(ORIENTATION_NEGATIVE == rHelperA.meOrinetation)
+ {
+ rHelperB.mnDepth--;
+ }
+ else
+ {
+ rHelperB.mnDepth++;
+ }
+ }
+ }
+ }
+ }
+
+ for(a = 0L; a < nCount; a++)
+ {
+ const StripHelper& rHelper = aHelpers[a];
+ bool bAcceptEntry(bKeepAboveZero ? 1L <= rHelper.mnDepth : 0L == rHelper.mnDepth);
+
+ if(bAcceptEntry)
+ {
+ aRetval.append(rCandidate.getB2DPolygon(a));
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ B2DPolyPolygon prepareForPolygonOperation(const B2DPolygon& rCandidate)
+ {
+ solver aSolver(rCandidate);
+ B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon()));
+
+ return correctOrientations(aRetval);
+ }
+
+ B2DPolyPolygon prepareForPolygonOperation(const B2DPolyPolygon& rCandidate)
+ {
+ solver aSolver(rCandidate);
+ B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon()));
+
+ return correctOrientations(aRetval);
+ }
+
+ B2DPolyPolygon solvePolygonOperationOr(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
+ {
+ if(!rCandidateA.count())
+ {
+ return rCandidateB;
+ }
+ else if(!rCandidateB.count())
+ {
+ return rCandidateA;
+ }
+ else
+ {
+ // concatenate polygons, solve crossovers and throw away all sub-polygons
+ // which have a depth other than 0.
+ B2DPolyPolygon aRetval(rCandidateA);
+
+ aRetval.append(rCandidateB);
+ aRetval = solveCrossovers(aRetval);
+ aRetval = stripNeutralPolygons(aRetval);
+
+ return stripDispensablePolygons(aRetval, false);
+ }
+ }
+
+ B2DPolyPolygon solvePolygonOperationXor(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
+ {
+ if(!rCandidateA.count())
+ {
+ return rCandidateB;
+ }
+ else if(!rCandidateB.count())
+ {
+ return rCandidateA;
+ }
+ else
+ {
+ // XOR is pretty simple: By definition it is the simple concatenation of
+ // the single polygons since we imply XOR fill rule. Make it intersection-free
+ // and correct orientations
+ B2DPolyPolygon aRetval(rCandidateA);
+
+ aRetval.append(rCandidateB);
+ aRetval = solveCrossovers(aRetval);
+ aRetval = stripNeutralPolygons(aRetval);
+
+ return correctOrientations(aRetval);
+ }
+ }
+
+ B2DPolyPolygon solvePolygonOperationAnd(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
+ {
+ if(!rCandidateA.count())
+ {
+ return B2DPolyPolygon();
+ }
+ else if(!rCandidateB.count())
+ {
+ return B2DPolyPolygon();
+ }
+ else
+ {
+ // concatenate polygons, solve crossovers and throw away all sub-polygons
+ // with a depth of < 1. This means to keep all polygons where at least two
+ // polygons do overlap.
+ B2DPolyPolygon aRetval(rCandidateA);
+
+ aRetval.append(rCandidateB);
+ aRetval = solveCrossovers(aRetval);
+ aRetval = stripNeutralPolygons(aRetval);
+
+ return stripDispensablePolygons(aRetval, true);
+ }
+ }
+
+ B2DPolyPolygon solvePolygonOperationDiff(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
+ {
+ if(!rCandidateA.count())
+ {
+ return B2DPolyPolygon();
+ }
+ else if(!rCandidateB.count())
+ {
+ return rCandidateA;
+ }
+ else
+ {
+ // Make B topologically to holes and append to A
+ B2DPolyPolygon aRetval(rCandidateB);
+
+ aRetval.flip();
+ aRetval.append(rCandidateA);
+
+ // solve crossovers and throw away all sub-polygons which have a
+ // depth other than 0.
+ aRetval = basegfx::tools::solveCrossovers(aRetval);
+ aRetval = basegfx::tools::stripNeutralPolygons(aRetval);
+
+ return basegfx::tools::stripDispensablePolygons(aRetval, false);
+ }
+ }
+
+ B2DPolyPolygon mergeToSinglePolyPolygon(const std::vector< basegfx::B2DPolyPolygon >& rInput)
+ {
+ std::vector< basegfx::B2DPolyPolygon > aInput(rInput);
+
+ // first step: prepareForPolygonOperation and simple merge of non-overlapping
+ // PolyPolygons for speedup; this is possible for the wanted OR-operation
+ if(aInput.size())
+ {
+ std::vector< basegfx::B2DPolyPolygon > aResult;
+ aResult.reserve(aInput.size());
+
+ for(sal_uInt32 a(0); a < aInput.size(); a++)
+ {
+ const basegfx::B2DPolyPolygon aCandidate(prepareForPolygonOperation(aInput[a]));
+
+ if(aResult.size())
+ {
+ const B2DRange aCandidateRange(aCandidate.getB2DRange());
+ bool bCouldMergeSimple(false);
+
+ for(sal_uInt32 b(0); !bCouldMergeSimple && b < aResult.size(); b++)
+ {
+ basegfx::B2DPolyPolygon aTarget(aResult[b]);
+ const B2DRange aTargetRange(aTarget.getB2DRange());
+
+ if(!aCandidateRange.overlaps(aTargetRange))
+ {
+ aTarget.append(aCandidate);
+ aResult[b] = aTarget;
+ bCouldMergeSimple = true;
+ }
+ }
+
+ if(!bCouldMergeSimple)
+ {
+ aResult.push_back(aCandidate);
+ }
+ }
+ else
+ {
+ aResult.push_back(aCandidate);
+ }
+ }
+
+ aInput = aResult;
+ }
+
+ // second step: melt pairwise to a single PolyPolygon
+ while(aInput.size() > 1)
+ {
+ std::vector< basegfx::B2DPolyPolygon > aResult;
+ aResult.reserve((aInput.size() / 2) + 1);
+
+ for(sal_uInt32 a(0); a < aInput.size(); a += 2)
+ {
+ if(a + 1 < aInput.size())
+ {
+ // a pair for processing
+ aResult.push_back(solvePolygonOperationOr(aInput[a], aInput[a + 1]));
+ }
+ else
+ {
+ // last single PolyPolygon; copy to target to not lose it
+ aResult.push_back(aInput[a]);
+ }
+ }
+
+ aInput = aResult;
+ }
+
+ // third step: get result
+ if(1 == aInput.size())
+ {
+ return aInput[0];
+ }
+
+ return B2DPolyPolygon();
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ } // end of namespace tools
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+// eof
diff --git a/basegfx/source/polygon/b2dpolypolygonrasterconverter.cxx b/basegfx/source/polygon/b2dpolypolygonrasterconverter.cxx
new file mode 100644
index 000000000000..b795c04e158e
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolypolygonrasterconverter.cxx
@@ -0,0 +1,702 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+
+#include <basegfx/polygon/b2dpolypolygonrasterconverter.hxx>
+
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+
+#include <boost/mem_fn.hpp>
+
+#include <algorithm>
+
+namespace basegfx
+{
+ class radixSort {
+
+ //! public interface
+ public:
+
+ //! default constructor
+ radixSort( void );
+
+ //! destructor
+ ~radixSort( void );
+
+ bool sort( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride );
+
+ inline sal_uInt32 *indices( void ) const { return m_indices1; }
+
+ //! private attributes
+ private:
+
+ // current size of index list
+ sal_uInt32 m_current_size;
+
+ // last known size of index list
+ sal_uInt32 m_previous_size;
+
+ // index lists
+ sal_uInt32 *m_indices1;
+ sal_uInt32 *m_indices2;
+
+ sal_uInt32 m_counter[256*4];
+ sal_uInt32 m_offset[256];
+
+ //! private methods
+ private:
+
+ bool resize( sal_uInt32 nNumElements );
+ inline void reset_indices( void );
+ bool prepareCounters( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride );
+ };
+
+ inline radixSort::radixSort( void ) {
+
+ m_indices1 = NULL;
+ m_indices2 = NULL;
+ m_current_size = 0;
+ m_previous_size = 0;
+
+ reset_indices();
+ }
+
+ inline radixSort::~radixSort( void ) {
+
+ delete [] m_indices2;
+ delete [] m_indices1;
+ }
+
+ bool radixSort::resize( sal_uInt32 nNumElements ) {
+
+ if(nNumElements==m_previous_size)
+ return true;
+
+ if(nNumElements > m_current_size) {
+
+ // release index lists
+ if(m_indices2)
+ delete [] m_indices2;
+ if(m_indices1)
+ delete [] m_indices1;
+
+ // allocate new index lists
+ m_indices1 = new sal_uInt32[nNumElements];
+ m_indices2 = new sal_uInt32[nNumElements];
+
+ // check for out of memory situation
+ if(!m_indices1 || !m_indices2) {
+ delete [] m_indices1;
+ delete [] m_indices2;
+ m_indices1 = NULL;
+ m_indices2 = NULL;
+ m_current_size = 0;
+ return false;
+ }
+
+ m_current_size = nNumElements;
+ }
+
+ m_previous_size = nNumElements;
+
+ // initialize indices
+ reset_indices();
+
+ return true;
+ }
+
+ inline void radixSort::reset_indices( void ) {
+
+ for(sal_uInt32 i=0;i<m_current_size;i++)
+ m_indices1[i] = i;
+ }
+
+ bool radixSort::prepareCounters( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride ) {
+
+ // clear counters
+ sal_uInt32 *ptr = m_counter;
+ for(int i=0; i<64; ++i)
+ {
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ }
+
+ // prepare pointers to relevant memory addresses
+ sal_uInt8 *p = (sal_uInt8*)pInput;
+ sal_uInt8 *pe = p+(nNumElements*dwStride);
+ sal_uInt32 *h0= &m_counter[0];
+ sal_uInt32 *h1= &m_counter[256];
+ sal_uInt32 *h2= &m_counter[512];
+ sal_uInt32 *h3= &m_counter[768];
+
+ sal_uInt32 *Indices = m_indices1;
+ float previous_value = *(float *)(((sal_uInt8 *)pInput)+(m_indices1[0]*dwStride));
+ bool bSorted = true;
+ while(p!=pe) {
+ float value = *(float *)(((sal_uInt8 *)pInput)+((*Indices++)*dwStride));
+ if(value<previous_value) {
+ bSorted = false;
+ break;
+ }
+ previous_value = value;
+ h0[*p++]++;
+ h1[*p++]++;
+ h2[*p++]++;
+ h3[*p++]++;
+ p += dwStride-4;
+ }
+ if(bSorted)
+ return true;
+ while(p!=pe) {
+ h0[*p++]++;
+ h1[*p++]++;
+ h2[*p++]++;
+ h3[*p++]++;
+ p += dwStride-4;
+ }
+ return false;
+ }
+
+ bool radixSort::sort( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride ) {
+
+ if(!(pInput))
+ return false;
+ if(!(nNumElements))
+ return false;
+ if(!(resize(nNumElements)))
+ return false;
+
+ // prepare radix counters, return if already sorted
+ if(prepareCounters(pInput,nNumElements,dwStride))
+ return true;
+
+ // count number of negative values
+ sal_uInt32 num_negatives = 0;
+ sal_uInt32 *h3= &m_counter[768];
+ for(sal_uInt32 i=128;i<256;i++)
+ num_negatives += h3[i];
+
+ // perform passes, one for each byte
+ for(sal_uInt32 j=0;j<4;j++) {
+
+ // ignore this pass if all values have the same byte
+ bool bRun = true;
+ sal_uInt32 *current_counter = &m_counter[j<<8];
+ sal_uInt8 unique_value = *(((sal_uInt8*)pInput)+j);
+ if(current_counter[unique_value]==nNumElements)
+ bRun=false;
+
+ // does the incoming byte contain the sign bit?
+ sal_uInt32 i;
+ if(j!=3) {
+ if(bRun) {
+ m_offset[0] = 0;
+ for(i=1;i<256;i++)
+ m_offset[i] = m_offset[i-1] + current_counter[i-1];
+ sal_uInt8 *InputBytes = (sal_uInt8 *)pInput;
+ sal_uInt32 *Indices = m_indices1;
+ sal_uInt32 *IndicesEnd = &m_indices1[nNumElements];
+ InputBytes += j;
+ while(Indices!=IndicesEnd) {
+ sal_uInt32 id = *Indices++;
+ m_indices2[m_offset[InputBytes[id*dwStride]]++] = id;
+ }
+ sal_uInt32 *Tmp = m_indices1;
+ m_indices1 = m_indices2;
+ m_indices2 = Tmp;
+ }
+ }
+ else {
+ if(bRun) {
+ m_offset[0] = num_negatives;
+ for(i=1;i<128;i++)
+ m_offset[i] = m_offset[i-1] + current_counter[i-1];
+ m_offset[255] = 0;
+ for(i=0;i<127;i++)
+ m_offset[254-i] = m_offset[255-i] + current_counter[255-i];
+ for(i=128;i<256;i++)
+ m_offset[i] += current_counter[i];
+ for(i=0;i<nNumElements;i++) {
+ sal_uInt32 Radix = (*(sal_uInt32 *)(((sal_uInt8 *)pInput)+(m_indices1[i]*dwStride)))>>24;
+ if(Radix<128) m_indices2[m_offset[Radix]++] = m_indices1[i];
+ else m_indices2[--m_offset[Radix]] = m_indices1[i];
+ }
+ sal_uInt32 *Tmp = m_indices1;
+ m_indices1 = m_indices2;
+ m_indices2 = Tmp;
+ }
+ else {
+ if(unique_value>=128) {
+ for(i=0;i<nNumElements;i++)
+ m_indices2[i] = m_indices1[nNumElements-i-1];
+ sal_uInt32 *Tmp = m_indices1;
+ m_indices1 = m_indices2;
+ m_indices2 = Tmp;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ //************************************************************
+ // Internal vertex storage of B2DPolyPolygonRasterConverter
+ //************************************************************
+
+ inline B2DPolyPolygonRasterConverter::Vertex::Vertex() :
+ aP1(),
+ aP2(),
+ bDownwards( true )
+ {
+ }
+
+ inline B2DPolyPolygonRasterConverter::Vertex::Vertex( const B2DPoint& rP1, const B2DPoint& rP2, bool bDown ) :
+ aP1( rP1 ),
+ aP2( rP2 ),
+ bDownwards( bDown )
+ {
+ }
+
+
+ //************************************************************
+ // Helper class for holding horizontal line segments during raster
+ // conversion
+ //************************************************************
+
+ namespace
+ {
+ class ImplLineNode
+ {
+ public:
+ sal_Int32 mnYCounter;
+ float mfXPos;
+ float mfXDelta;
+ bool mbDownwards;
+
+ public:
+ /**rP1 and rP2 must not have equal y values, when rounded
+ to integer!
+ */
+ ImplLineNode(const B2DPoint& rP1, const B2DPoint& rP2, bool bDown) :
+ mnYCounter( fround(rP2.getY()) - fround(rP1.getY()) ),
+ mfXPos( (float)(rP1.getX()) ),
+ mfXDelta((float) ((rP2.getX() - rP1.getX()) / mnYCounter) ),
+ mbDownwards( bDown )
+ {
+ }
+
+ /// get current x position
+ const float& getXPos() const
+ {
+ return mfXPos;
+ }
+
+ /// returns true, if line ends on this Y value
+ float nextLine()
+ {
+ if(mnYCounter>=0)
+ {
+ // go one step in Y
+ mfXPos += mfXDelta;
+ --mnYCounter;
+ return mfXDelta;
+ }
+
+ return 0.0f;
+ }
+
+ bool isEnded()
+ {
+ return mnYCounter<=0;
+ }
+
+ bool isDownwards()
+ {
+ return mbDownwards;
+ }
+ };
+ }
+
+ typedef ::std::vector<ImplLineNode> VectorOfLineNodes;
+
+
+ //************************************************************
+ // Base2D PolyPolygon Raster Converter (Rasterizer)
+ //************************************************************
+
+ namespace
+ {
+ struct VertexComparator
+ {
+ bool operator()( const B2DPolyPolygonRasterConverter::Vertex& rLHS,
+ const B2DPolyPolygonRasterConverter::Vertex& rRHS )
+ {
+ return rLHS.aP1.getX() < rRHS.aP1.getX();
+ }
+ };
+ }
+
+ void B2DPolyPolygonRasterConverter::init()
+ {
+ if(!maPolyPolyRectangle.isEmpty())
+ {
+ const sal_Int32 nMinY( fround(maPolyPolyRectangle.getMinY()) );
+ const sal_Int32 nScanlines(fround(maPolyPolyRectangle.getMaxY()) - nMinY);
+
+ maScanlines.resize( nScanlines+1 );
+
+ // add all polygons
+ for( sal_uInt32 i(0), nCount(maPolyPolygon.count());
+ i < nCount;
+ ++i )
+ {
+ // add all vertices
+ const B2DPolygon& rPoly( maPolyPolygon.getB2DPolygon(i) );
+ for( sal_uInt32 k(0), nVertices(rPoly.count());
+ k<nVertices;
+ ++k )
+ {
+ const B2DPoint& rP1( rPoly.getB2DPoint(k) );
+ const B2DPoint& rP2( rPoly.getB2DPoint( (k + 1) % nVertices ) );
+
+ const sal_Int32 nVertexYP1( fround(rP1.getY()) );
+ const sal_Int32 nVertexYP2( fround(rP2.getY()) );
+
+ // insert only vertices which are not strictly
+ // horizontal. Note that the ImplLineNode relies on
+ // this.
+ if(nVertexYP1 != nVertexYP2)
+ {
+ if( nVertexYP2 < nVertexYP1 )
+ {
+ const sal_Int32 nStartScanline(nVertexYP2 - nMinY);
+
+ // swap edges
+ maScanlines[ nStartScanline ].push_back( Vertex(rP2, rP1, false) );
+ }
+ else
+ {
+ const sal_Int32 nStartScanline(nVertexYP1 - nMinY);
+
+ maScanlines[ nStartScanline ].push_back( Vertex(rP1, rP2, true) );
+ }
+ }
+ }
+ }
+
+ // now sort all scanlines, with increasing x coordinates
+ VectorOfVertexVectors::iterator aIter( maScanlines.begin() );
+ VectorOfVertexVectors::iterator aEnd( maScanlines.end() );
+ while( aIter != aEnd )
+ {
+ ::std::sort( aIter->begin(),
+ aIter->end(),
+ VertexComparator() );
+ ++aIter;
+ }
+ }
+ }
+
+ B2DPolyPolygonRasterConverter::B2DPolyPolygonRasterConverter( const B2DPolyPolygon& rPolyPoly ) :
+ maPolyPolygon( rPolyPoly ),
+ maPolyPolyRectangle( tools::getRange( rPolyPoly ) ),
+ maScanlines()
+ {
+ init();
+ }
+
+ namespace
+ {
+ B2DRectangle getCombinedBounds( const B2DPolyPolygon& rPolyPolyRaster,
+ const B2DRectangle& rRasterArea )
+ {
+ B2DRectangle aRect( tools::getRange( rPolyPolyRaster ) );
+ aRect.expand( rRasterArea );
+
+ return aRect;
+ }
+ }
+
+ B2DPolyPolygonRasterConverter::B2DPolyPolygonRasterConverter( const B2DPolyPolygon& rPolyPolyRaster,
+ const B2DRectangle& rRasterArea ) :
+ maPolyPolygon( rPolyPolyRaster ),
+ maPolyPolyRectangle(
+ getCombinedBounds( rPolyPolyRaster,
+ rRasterArea ) ),
+ maScanlines()
+ {
+ init();
+ }
+
+ B2DPolyPolygonRasterConverter::~B2DPolyPolygonRasterConverter()
+ {
+ }
+
+ namespace
+ {
+ class LineNodeGenerator
+ {
+ public:
+ LineNodeGenerator( VectorOfLineNodes& rActiveVertices ) :
+ mrActiveVertices( rActiveVertices )
+ {
+ }
+
+ void operator()( const B2DPolyPolygonRasterConverter::Vertex& rVertex )
+ {
+ mrActiveVertices.push_back( ImplLineNode(rVertex.aP1,
+ rVertex.aP2,
+ rVertex.bDownwards) );
+ }
+
+ private:
+ VectorOfLineNodes& mrActiveVertices;
+ };
+
+ struct LineNodeComparator
+ {
+ bool operator()( const ImplLineNode& rLHS, const ImplLineNode& rRHS )
+ {
+ return rLHS.getXPos() < rRHS.getXPos();
+ }
+ };
+ }
+
+ void B2DPolyPolygonRasterConverter::rasterConvert( FillRule eFillRule )
+ {
+ if( maScanlines.empty() )
+ return; // no scanlines at all -> bail out
+
+ const sal_Int32 nMinY( fround(maPolyPolyRectangle.getMinY()) );
+ const sal_Int32 nScanlines(fround(maPolyPolyRectangle.getMaxY()) - nMinY);
+
+ // Vector of currently active vertices. A vertex is active, if
+ // it crosses or touches the current scanline.
+ VectorOfLineNodes aActiveVertices;
+
+ // mickey's optimized version...
+ radixSort rs;
+ std::size_t nb(0);
+ std::size_t nb_previous(0);
+ bool bSort(false);
+
+ // process each scanline
+ for( sal_Int32 y(0); y <= nScanlines; ++y )
+ {
+ // add vertices which start at current scanline into
+ // active vertex vector
+ ::std::for_each( maScanlines[y].begin(),
+ maScanlines[y].end(),
+ LineNodeGenerator( aActiveVertices ) );
+ nb = aActiveVertices.size();
+ if(nb != nb_previous)
+ {
+ nb_previous = nb;
+ bSort = true;
+ }
+
+ // sort with increasing X
+ if(bSort)
+ {
+ bSort = false;
+
+ if( nb )
+ {
+ rs.sort(&aActiveVertices[0].mfXPos,
+ nb,
+ sizeof(ImplLineNode));
+ }
+ }
+
+ const std::size_t nLen( nb );
+ if( !nLen )
+ {
+ // empty scanline - call derived with an 'off' span
+ // for the full width
+ span( maPolyPolyRectangle.getMinX(),
+ maPolyPolyRectangle.getMaxX(),
+ nMinY + y,
+ false );
+ }
+ else
+ {
+ const sal_Int32 nCurrY( nMinY + y );
+
+ // scanline not empty - forward all scans to derived,
+ // according to selected fill rule
+
+ // TODO(P1): Maybe allow these 'off' span calls to be
+ // switched off (or all 'on' span calls, depending on
+ // use case scenario)
+
+ // sorting didn't change the order of the elements
+ // in memory but prepared a list of indices in sorted order.
+ // thus we now process the nodes with an additional indirection.
+ sal_uInt32 *sorted = rs.indices();
+
+ // call derived with 'off' span for everything left of first active span
+ if( aActiveVertices[sorted[0]].getXPos() > maPolyPolyRectangle.getMinX() )
+ {
+ span( maPolyPolyRectangle.getMinX(),
+ aActiveVertices[sorted[0]].getXPos(),
+ nCurrY,
+ false );
+ }
+
+ switch( eFillRule )
+ {
+ default:
+ OSL_ENSURE(false,
+ "B2DPolyPolygonRasterConverter::rasterConvert(): Unexpected fill rule");
+ return;
+
+ case FillRule_EVEN_ODD:
+ // process each span in current scanline, with
+ // even-odd fill rule
+ for( ::std::size_t i(0), nLength(aActiveVertices.size());
+ i+1 < nLength;
+ ++i )
+ {
+ sal_uInt32 nIndex = sorted[i];
+ sal_uInt32 nNextIndex = sorted[i+1];
+ span( aActiveVertices[nIndex].getXPos(),
+ aActiveVertices[nNextIndex].getXPos(),
+ nCurrY,
+ i % 2 == 0 );
+
+ float delta = aActiveVertices[nIndex].nextLine();
+ if(delta > 0.0f)
+ {
+ if(aActiveVertices[nIndex].getXPos() > aActiveVertices[nNextIndex].getXPos())
+ bSort = true;
+ }
+ else if(delta < 0.0f)
+ {
+ if(i)
+ {
+ sal_uInt32 nPrevIndex = sorted[i-1];
+ if(aActiveVertices[nIndex].getXPos() < aActiveVertices[nPrevIndex].getXPos())
+ bSort = true;
+ }
+ }
+ }
+ break;
+
+ case FillRule_NONZERO_WINDING_NUMBER:
+ // process each span in current scanline, with
+ // non-zero winding numbe fill rule
+ sal_Int32 nWindingNumber(0);
+ for( ::std::size_t i(0), nLength(aActiveVertices.size());
+ i+1 < nLength;
+ ++i )
+ {
+ sal_uInt32 nIndex = sorted[i];
+ sal_uInt32 nNextIndex = sorted[i+1];
+ nWindingNumber += -1 + 2*aActiveVertices[nIndex].isDownwards();
+
+ span( aActiveVertices[nIndex].getXPos(),
+ aActiveVertices[nNextIndex].getXPos(),
+ nCurrY,
+ nWindingNumber != 0 );
+
+ float delta = aActiveVertices[nIndex].nextLine();
+ if(delta > 0.0f)
+ {
+ if(aActiveVertices[nIndex].getXPos() > aActiveVertices[nNextIndex].getXPos())
+ bSort = true;
+ }
+ else if(delta < 0.0f)
+ {
+ if(i)
+ {
+ sal_uInt32 nPrevIndex = sorted[i-1];
+ if(aActiveVertices[nIndex].getXPos() < aActiveVertices[nPrevIndex].getXPos())
+ bSort = true;
+ }
+ }
+ }
+ break;
+ }
+
+ // call derived with 'off' span for everything right of last active span
+ if( aActiveVertices[sorted[nb-1]].getXPos()+1.0 < maPolyPolyRectangle.getMaxX() )
+ {
+ span( aActiveVertices[sorted[nb-1]].getXPos()+1.0,
+ maPolyPolyRectangle.getMaxX(),
+ nCurrY,
+ false );
+ }
+
+ // also call nextLine on very last line node
+ sal_uInt32 nIndex = sorted[nb-1];
+ float delta = aActiveVertices[nIndex].nextLine();
+ if(delta < 0.0f)
+ {
+ if(nb)
+ {
+ sal_uInt32 nPrevIndex = sorted[nb-2];
+ if(aActiveVertices[nIndex].getXPos() < aActiveVertices[nPrevIndex].getXPos())
+ bSort = true;
+ }
+ }
+ }
+
+ // remove line nodes which have ended on the current scanline
+ aActiveVertices.erase( ::std::remove_if( aActiveVertices.begin(),
+ aActiveVertices.end(),
+ ::boost::mem_fn( &ImplLineNode::isEnded ) ),
+ aActiveVertices.end() );
+ nb = aActiveVertices.size();
+ if(nb != nb_previous)
+ {
+ nb_previous = nb;
+ bSort = true;
+ }
+ }
+ }
+}
+// eof
diff --git a/basegfx/source/polygon/b2dpolypolygontools.cxx b/basegfx/source/polygon/b2dpolypolygontools.cxx
new file mode 100644
index 000000000000..dcfa34f93c02
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolypolygontools.cxx
@@ -0,0 +1,585 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <osl/diagnose.h>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+
+#include <numeric>
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace tools
+ {
+ B2DPolyPolygon correctOrientations(const B2DPolyPolygon& rCandidate)
+ {
+ B2DPolyPolygon aRetval(rCandidate);
+ const sal_uInt32 nCount(aRetval.count());
+
+ for(sal_uInt32 a(0L); a < nCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+ const B2VectorOrientation aOrientation(tools::getOrientation(aCandidate));
+ sal_uInt32 nDepth(0L);
+
+ for(sal_uInt32 b(0L); b < nCount; b++)
+ {
+ if(b != a)
+ {
+ const B2DPolygon aCompare(rCandidate.getB2DPolygon(b));
+
+ if(tools::isInside(aCompare, aCandidate, true))
+ {
+ nDepth++;
+ }
+ }
+ }
+
+ const bool bShallBeHole(1L == (nDepth & 0x00000001));
+ const bool bIsHole(ORIENTATION_NEGATIVE == aOrientation);
+
+ if(bShallBeHole != bIsHole && ORIENTATION_NEUTRAL != aOrientation)
+ {
+ B2DPolygon aFlipped(aCandidate);
+ aFlipped.flip();
+ aRetval.setB2DPolygon(a, aFlipped);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon correctOutmostPolygon(const B2DPolyPolygon& rCandidate)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nCount > 1L)
+ {
+ for(sal_uInt32 a(0L); a < nCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+ sal_uInt32 nDepth(0L);
+
+ for(sal_uInt32 b(0L); b < nCount; b++)
+ {
+ if(b != a)
+ {
+ const B2DPolygon aCompare(rCandidate.getB2DPolygon(b));
+
+ if(tools::isInside(aCompare, aCandidate, true))
+ {
+ nDepth++;
+ }
+ }
+ }
+
+ if(!nDepth)
+ {
+ B2DPolyPolygon aRetval(rCandidate);
+
+ if(a != 0L)
+ {
+ // exchange polygon a and polygon 0L
+ aRetval.setB2DPolygon(0L, aCandidate);
+ aRetval.setB2DPolygon(a, rCandidate.getB2DPolygon(0L));
+ }
+
+ // exit
+ return aRetval;
+ }
+ }
+ }
+
+ return rCandidate;
+ }
+
+ B2DPolyPolygon adaptiveSubdivideByDistance(const B2DPolyPolygon& rCandidate, double fDistanceBound)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ if(aCandidate.areControlPointsUsed())
+ {
+ aRetval.append(tools::adaptiveSubdivideByDistance(aCandidate, fDistanceBound));
+ }
+ else
+ {
+ aRetval.append(aCandidate);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolyPolygon adaptiveSubdivideByAngle(const B2DPolyPolygon& rCandidate, double fAngleBound)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ if(aCandidate.areControlPointsUsed())
+ {
+ aRetval.append(tools::adaptiveSubdivideByAngle(aCandidate, fAngleBound));
+ }
+ else
+ {
+ aRetval.append(aCandidate);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolyPolygon adaptiveSubdivideByCount(const B2DPolyPolygon& rCandidate, sal_uInt32 nCount)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ if(aCandidate.areControlPointsUsed())
+ {
+ aRetval.append(tools::adaptiveSubdivideByCount(aCandidate, nCount));
+ }
+ else
+ {
+ aRetval.append(aCandidate);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ bool isInside(const B2DPolyPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+
+ if(1L == nPolygonCount)
+ {
+ return isInside(rCandidate.getB2DPolygon(0L), rPoint, bWithBorder);
+ }
+ else
+ {
+ sal_Int32 nInsideCount(0L);
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolygon aPolygon(rCandidate.getB2DPolygon(a));
+ const bool bInside(isInside(aPolygon, rPoint, bWithBorder));
+
+ if(bInside)
+ {
+ nInsideCount++;
+ }
+ }
+
+ return (nInsideCount % 2L);
+ }
+ }
+
+ B2DRange getRangeWithControlPoints(const B2DPolyPolygon& rCandidate)
+ {
+ B2DRange aRetval;
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ B2DPolygon aCandidate = rCandidate.getB2DPolygon(a);
+ aRetval.expand(tools::getRangeWithControlPoints(aCandidate));
+ }
+
+ return aRetval;
+ }
+
+ B2DRange getRange(const B2DPolyPolygon& rCandidate)
+ {
+ B2DRange aRetval;
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ B2DPolygon aCandidate = rCandidate.getB2DPolygon(a);
+ aRetval.expand(tools::getRange(aCandidate));
+ }
+
+ return aRetval;
+ }
+
+ void applyLineDashing(const B2DPolyPolygon& rCandidate, const ::std::vector<double>& rDotDashArray, B2DPolyPolygon* pLineTarget, B2DPolyPolygon* pGapTarget, double fFullDashDotLen)
+ {
+ if(0.0 == fFullDashDotLen && rDotDashArray.size())
+ {
+ // calculate fFullDashDotLen from rDotDashArray
+ fFullDashDotLen = ::std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
+ }
+
+ if(rCandidate.count() && fFullDashDotLen > 0.0)
+ {
+ B2DPolyPolygon aLineTarget, aGapTarget;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ applyLineDashing(
+ aCandidate,
+ rDotDashArray,
+ pLineTarget ? &aLineTarget : 0,
+ pGapTarget ? &aGapTarget : 0,
+ fFullDashDotLen);
+
+ if(pLineTarget)
+ {
+ pLineTarget->append(aLineTarget);
+ }
+
+ if(pGapTarget)
+ {
+ pGapTarget->append(aGapTarget);
+ }
+ }
+ }
+ }
+
+ bool isInEpsilonRange(const B2DPolyPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ if(isInEpsilonRange(aCandidate, rTestPosition, fDistance))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ B3DPolyPolygon createB3DPolyPolygonFromB2DPolyPolygon(const B2DPolyPolygon& rCandidate, double fZCoordinate)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B3DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ aRetval.append(createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate));
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon createB2DPolyPolygonFromB3DPolyPolygon(const B3DPolyPolygon& rCandidate, const B3DHomMatrix& rMat)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ B3DPolygon aCandidate(rCandidate.getB3DPolygon(a));
+
+ aRetval.append(createB2DPolygonFromB3DPolygon(aCandidate, rMat));
+ }
+
+ return aRetval;
+ }
+
+ double getSmallestDistancePointToPolyPolygon(const B2DPolyPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rPolygonIndex, sal_uInt32& rEdgeIndex, double& rCut)
+ {
+ double fRetval(DBL_MAX);
+ const double fZero(0.0);
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+ sal_uInt32 nNewEdgeIndex;
+ double fNewCut;
+ const double fNewDistance(getSmallestDistancePointToPolygon(aCandidate, rTestPoint, nNewEdgeIndex, fNewCut));
+
+ if(DBL_MAX == fRetval || fNewDistance < fRetval)
+ {
+ fRetval = fNewDistance;
+ rPolygonIndex = a;
+ rEdgeIndex = nNewEdgeIndex;
+ rCut = fNewCut;
+
+ if(fTools::equal(fRetval, fZero))
+ {
+ // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
+ fRetval = 0.0;
+ break;
+ }
+ }
+ }
+
+ return fRetval;
+ }
+
+ B2DPolyPolygon distort(const B2DPolyPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ aRetval.append(distort(aCandidate, rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon rotateAroundPoint(const B2DPolyPolygon& rCandidate, const B2DPoint& rCenter, double fAngle)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ aRetval.append(rotateAroundPoint(aCandidate, rCenter, fAngle));
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon expandToCurve(const B2DPolyPolygon& rCandidate)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ aRetval.append(expandToCurve(aCandidate));
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon setContinuity(const B2DPolyPolygon& rCandidate, B2VectorContinuity eContinuity)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a));
+
+ aRetval.append(setContinuity(aCandidate, eContinuity));
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolyPolygon growInNormalDirection(const B2DPolyPolygon& rCandidate, double fValue)
+ {
+ if(0.0 != fValue)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(growInNormalDirection(rCandidate.getB2DPolygon(a), fValue));
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ void correctGrowShrinkPolygonPair(B2DPolyPolygon& /*rOriginal*/, B2DPolyPolygon& /*rGrown*/)
+ {
+ }
+
+ B2DPolyPolygon reSegmentPolyPolygon(const B2DPolyPolygon& rCandidate, sal_uInt32 nSegments)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(reSegmentPolygon(rCandidate.getB2DPolygon(a), nSegments));
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon interpolate(const B2DPolyPolygon& rOld1, const B2DPolyPolygon& rOld2, double t)
+ {
+ OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolyPolygon interpolate: Different geometry (!)");
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rOld1.count(); a++)
+ {
+ aRetval.append(interpolate(rOld1.getB2DPolygon(a), rOld2.getB2DPolygon(a), t));
+ }
+
+ return aRetval;
+ }
+
+ bool isRectangle( const B2DPolyPolygon& rPoly )
+ {
+ // exclude some cheap cases first
+ if( rPoly.count() != 1 )
+ return false;
+
+ return isRectangle( rPoly.getB2DPolygon(0) );
+ }
+
+ // #i76891#
+ B2DPolyPolygon simplifyCurveSegments(const B2DPolyPolygon& rCandidate)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(simplifyCurveSegments(rCandidate.getB2DPolygon(a)));
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolyPolygon reSegmentPolyPolygonEdges(const B2DPolyPolygon& rCandidate, sal_uInt32 nSubEdges, bool bHandleCurvedEdges, bool bHandleStraightEdges)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(reSegmentPolygonEdges(rCandidate.getB2DPolygon(a), nSubEdges, bHandleCurvedEdges, bHandleStraightEdges));
+ }
+
+ return aRetval;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ // comparators with tolerance for 2D PolyPolygons
+
+ bool equal(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB, const double& rfSmallValue)
+ {
+ const sal_uInt32 nPolygonCount(rCandidateA.count());
+
+ if(nPolygonCount != rCandidateB.count())
+ return false;
+
+ for(sal_uInt32 a(0); a < nPolygonCount; a++)
+ {
+ const B2DPolygon aCandidate(rCandidateA.getB2DPolygon(a));
+
+ if(!equal(aCandidate, rCandidateB.getB2DPolygon(a), rfSmallValue))
+ return false;
+ }
+
+ return true;
+ }
+
+ bool equal(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
+ {
+ const double fSmallValue(fTools::getSmallValue());
+
+ return equal(rCandidateA, rCandidateB, fSmallValue);
+ }
+
+ B2DPolyPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolyPolygon& rCandidate)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(snapPointsOfHorizontalOrVerticalEdges(rCandidate.getB2DPolygon(a)));
+ }
+
+ return aRetval;
+ }
+
+ } // end of namespace tools
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+// eof
diff --git a/basegfx/source/polygon/b2dsvgpolypolygon.cxx b/basegfx/source/polygon/b2dsvgpolypolygon.cxx
new file mode 100644
index 000000000000..bbb6db4c064a
--- /dev/null
+++ b/basegfx/source/polygon/b2dsvgpolypolygon.cxx
@@ -0,0 +1,1105 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/math.hxx>
+
+namespace basegfx
+{
+ namespace tools
+ {
+ namespace
+ {
+ void lcl_skipSpaces(sal_Int32& io_rPos,
+ const ::rtl::OUString& rStr,
+ const sal_Int32 nLen)
+ {
+ while( io_rPos < nLen &&
+ sal_Unicode(' ') == rStr[io_rPos] )
+ {
+ ++io_rPos;
+ }
+ }
+
+ void lcl_skipSpacesAndCommas(sal_Int32& io_rPos,
+ const ::rtl::OUString& rStr,
+ const sal_Int32 nLen)
+ {
+ while(io_rPos < nLen
+ && (sal_Unicode(' ') == rStr[io_rPos] || sal_Unicode(',') == rStr[io_rPos]))
+ {
+ ++io_rPos;
+ }
+ }
+
+ inline bool lcl_isOnNumberChar(const ::rtl::OUString& rStr, const sal_Int32 nPos, bool bSignAllowed = true)
+ {
+ const sal_Unicode aChar(rStr[nPos]);
+
+ const bool bPredicate( (sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
+ || (bSignAllowed && sal_Unicode('+') == aChar)
+ || (bSignAllowed && sal_Unicode('-') == aChar) );
+
+ return bPredicate;
+ }
+
+ bool lcl_getDoubleChar(double& o_fRetval,
+ sal_Int32& io_rPos,
+ const ::rtl::OUString& rStr,
+ const sal_Int32 /*nLen*/)
+ {
+ sal_Unicode aChar( rStr[io_rPos] );
+ ::rtl::OUStringBuffer sNumberString;
+
+ if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
+ {
+ sNumberString.append(rStr[io_rPos]);
+ aChar = rStr[++io_rPos];
+ }
+
+ while((sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
+ || sal_Unicode('.') == aChar)
+ {
+ sNumberString.append(rStr[io_rPos]);
+ aChar = rStr[++io_rPos];
+ }
+
+ if(sal_Unicode('e') == aChar || sal_Unicode('E') == aChar)
+ {
+ sNumberString.append(rStr[io_rPos]);
+ aChar = rStr[++io_rPos];
+
+ if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
+ {
+ sNumberString.append(rStr[io_rPos]);
+ aChar = rStr[++io_rPos];
+ }
+
+ while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
+ {
+ sNumberString.append(rStr[io_rPos]);
+ aChar = rStr[++io_rPos];
+ }
+ }
+
+ if(sNumberString.getLength())
+ {
+ rtl_math_ConversionStatus eStatus;
+ o_fRetval = ::rtl::math::stringToDouble( sNumberString.makeStringAndClear(),
+ (sal_Unicode)('.'),
+ (sal_Unicode)(','),
+ &eStatus,
+ NULL );
+ return ( eStatus == rtl_math_ConversionStatus_Ok );
+ }
+
+ return false;
+ }
+
+ bool lcl_importDoubleAndSpaces( double& o_fRetval,
+ sal_Int32& io_rPos,
+ const ::rtl::OUString& rStr,
+ const sal_Int32 nLen )
+ {
+ if( !lcl_getDoubleChar(o_fRetval, io_rPos, rStr, nLen) )
+ return false;
+
+ lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
+
+ return true;
+ }
+
+ bool lcl_importNumberAndSpaces(sal_Int32& o_nRetval,
+ sal_Int32& io_rPos,
+ const ::rtl::OUString& rStr,
+ const sal_Int32 nLen)
+ {
+ sal_Unicode aChar( rStr[io_rPos] );
+ ::rtl::OUStringBuffer sNumberString;
+
+ if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
+ {
+ sNumberString.append(rStr[io_rPos]);
+ aChar = rStr[++io_rPos];
+ }
+
+ while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
+ {
+ sNumberString.append(rStr[io_rPos]);
+ aChar = rStr[++io_rPos];
+ }
+
+ if(sNumberString.getLength())
+ {
+ o_nRetval = sNumberString.makeStringAndClear().toInt32();
+ lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ void lcl_skipNumber(sal_Int32& io_rPos,
+ const ::rtl::OUString& rStr,
+ const sal_Int32 nLen)
+ {
+ bool bSignAllowed(true);
+
+ while(io_rPos < nLen && lcl_isOnNumberChar(rStr, io_rPos, bSignAllowed))
+ {
+ bSignAllowed = false;
+ ++io_rPos;
+ }
+ }
+
+ void lcl_skipDouble(sal_Int32& io_rPos,
+ const ::rtl::OUString& rStr,
+ const sal_Int32 /*nLen*/)
+ {
+ sal_Unicode aChar( rStr[io_rPos] );
+
+ if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
+ aChar = rStr[++io_rPos];
+
+ while((sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
+ || sal_Unicode('.') == aChar)
+ {
+ aChar = rStr[++io_rPos];
+ }
+
+ if(sal_Unicode('e') == aChar || sal_Unicode('E') == aChar)
+ {
+ aChar = rStr[++io_rPos];
+
+ if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar)
+ aChar = rStr[++io_rPos];
+
+ while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar)
+ {
+ aChar = rStr[++io_rPos];
+ }
+ }
+ }
+ void lcl_skipNumberAndSpacesAndCommas(sal_Int32& io_rPos,
+ const ::rtl::OUString& rStr,
+ const sal_Int32 nLen)
+ {
+ lcl_skipNumber(io_rPos, rStr, nLen);
+ lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
+ }
+
+ // #100617# Allow to skip doubles, too.
+ void lcl_skipDoubleAndSpacesAndCommas(sal_Int32& io_rPos,
+ const ::rtl::OUString& rStr,
+ const sal_Int32 nLen)
+ {
+ lcl_skipDouble(io_rPos, rStr, nLen);
+ lcl_skipSpacesAndCommas(io_rPos, rStr, nLen);
+ }
+
+ void lcl_putNumberChar( ::rtl::OUString& rStr,
+ double fValue )
+ {
+ rStr += ::rtl::OUString::valueOf( fValue );
+ }
+
+ void lcl_putNumberCharWithSpace( ::rtl::OUString& rStr,
+ double fValue,
+ double fOldValue,
+ bool bUseRelativeCoordinates )
+ {
+ if( bUseRelativeCoordinates )
+ fValue -= fOldValue;
+
+ const sal_Int32 aLen( rStr.getLength() );
+ if(aLen)
+ {
+ if( lcl_isOnNumberChar(rStr, aLen - 1, false) &&
+ fValue >= 0.0 )
+ {
+ rStr += ::rtl::OUString::valueOf(
+ sal_Unicode(' ') );
+ }
+ }
+
+ lcl_putNumberChar(rStr, fValue);
+ }
+
+ inline sal_Unicode lcl_getCommand( sal_Char cUpperCaseCommand,
+ sal_Char cLowerCaseCommand,
+ bool bUseRelativeCoordinates )
+ {
+ return bUseRelativeCoordinates ? cLowerCaseCommand : cUpperCaseCommand;
+ }
+ }
+
+ bool importFromSvgD(B2DPolyPolygon& o_rPolyPolygon, const ::rtl::OUString& rSvgDStatement)
+ {
+ o_rPolyPolygon.clear();
+ const sal_Int32 nLen(rSvgDStatement.getLength());
+ sal_Int32 nPos(0);
+ bool bIsClosed(false);
+ double nLastX( 0.0 );
+ double nLastY( 0.0 );
+ B2DPolygon aCurrPoly;
+
+ // skip initial whitespace
+ lcl_skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen)
+ {
+ bool bRelative(false);
+ bool bMoveTo(false);
+ const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
+
+ switch(aCurrChar)
+ {
+ case 'z' :
+ case 'Z' :
+ {
+ nPos++;
+ lcl_skipSpaces(nPos, rSvgDStatement, nLen);
+
+ // remember closed state of current polygon
+ bIsClosed = true;
+ break;
+ }
+
+ case 'm' :
+ case 'M' :
+ {
+ bMoveTo = true;
+ // FALLTHROUGH intended
+ }
+ case 'l' :
+ case 'L' :
+ {
+ if('m' == aCurrChar || 'l' == aCurrChar)
+ {
+ bRelative = true;
+ }
+
+ if(bMoveTo)
+ {
+ // new polygon start, finish old one
+ if(aCurrPoly.count())
+ {
+ // add current polygon
+ if(bIsClosed)
+ {
+ closeWithGeometryChange(aCurrPoly);
+ }
+
+ o_rPolyPolygon.append(aCurrPoly);
+
+ // reset import values
+ bIsClosed = false;
+ aCurrPoly.clear();
+ }
+ }
+
+ nPos++;
+ lcl_skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+
+ if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+
+ // add point
+ aCurrPoly.append(B2DPoint(nX, nY));
+ }
+ break;
+ }
+
+ case 'h' :
+ {
+ bRelative = true;
+ // FALLTHROUGH intended
+ }
+ case 'H' :
+ {
+ nPos++;
+ lcl_skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY(nLastY);
+
+ if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX += nLastX;
+ }
+
+ // set last position
+ nLastX = nX;
+
+ // add point
+ aCurrPoly.append(B2DPoint(nX, nY));
+ }
+ break;
+ }
+
+ case 'v' :
+ {
+ bRelative = true;
+ // FALLTHROUGH intended
+ }
+ case 'V' :
+ {
+ nPos++;
+ lcl_skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX(nLastX), nY;
+
+ if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nY += nLastY;
+ }
+
+ // set last position
+ nLastY = nY;
+
+ // add point
+ aCurrPoly.append(B2DPoint(nX, nY));
+ }
+ break;
+ }
+
+ case 's' :
+ {
+ bRelative = true;
+ // FALLTHROUGH intended
+ }
+ case 'S' :
+ {
+ nPos++;
+ lcl_skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+ double nX2, nY2;
+
+ if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX2 += nLastX;
+ nY2 += nLastY;
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ // ensure existance of start point
+ if(!aCurrPoly.count())
+ {
+ aCurrPoly.append(B2DPoint(nLastX, nLastY));
+ }
+
+ // get first control point. It's the reflection of the PrevControlPoint
+ // of the last point. If not existent, use current point (see SVG)
+ B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
+ const sal_uInt32 nIndex(aCurrPoly.count() - 1);
+
+ if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
+ {
+ const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
+ const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
+
+ // use mirrored previous control point
+ aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
+ aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
+ }
+
+ // append curved edge
+ aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+ }
+ break;
+ }
+
+ case 'c' :
+ {
+ bRelative = true;
+ // FALLTHROUGH intended
+ }
+ case 'C' :
+ {
+ nPos++;
+ lcl_skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+ double nX1, nY1;
+ double nX2, nY2;
+
+ if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX1 += nLastX;
+ nY1 += nLastY;
+ nX2 += nLastX;
+ nY2 += nLastY;
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ // ensure existance of start point
+ if(!aCurrPoly.count())
+ {
+ aCurrPoly.append(B2DPoint(nLastX, nLastY));
+ }
+
+ // append curved edge
+ aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+ }
+ break;
+ }
+
+ // #100617# quadratic beziers are imported as cubic ones
+ case 'q' :
+ {
+ bRelative = true;
+ // FALLTHROUGH intended
+ }
+ case 'Q' :
+ {
+ nPos++;
+ lcl_skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+ double nX1, nY1;
+
+ if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX1 += nLastX;
+ nY1 += nLastY;
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ // calculate the cubic bezier coefficients from the quadratic ones
+ const double nX1Prime((nX1 * 2.0 + nLastX) / 3.0);
+ const double nY1Prime((nY1 * 2.0 + nLastY) / 3.0);
+ const double nX2Prime((nX1 * 2.0 + nX) / 3.0);
+ const double nY2Prime((nY1 * 2.0 + nY) / 3.0);
+
+ // ensure existance of start point
+ if(!aCurrPoly.count())
+ {
+ aCurrPoly.append(B2DPoint(nLastX, nLastY));
+ }
+
+ // append curved edge
+ aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+ }
+ break;
+ }
+
+ // #100617# relative quadratic beziers are imported as cubic
+ case 't' :
+ {
+ bRelative = true;
+ // FALLTHROUGH intended
+ }
+ case 'T' :
+ {
+ nPos++;
+ lcl_skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+
+ if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ // ensure existance of start point
+ if(!aCurrPoly.count())
+ {
+ aCurrPoly.append(B2DPoint(nLastX, nLastY));
+ }
+
+ // get first control point. It's the reflection of the PrevControlPoint
+ // of the last point. If not existent, use current point (see SVG)
+ B2DPoint aPrevControl(B2DPoint(nLastX, nLastY));
+ const sal_uInt32 nIndex(aCurrPoly.count() - 1);
+ const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
+
+ if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
+ {
+ const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
+
+ // use mirrored previous control point
+ aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
+ aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
+ }
+
+ if(!aPrevControl.equal(aPrevPoint))
+ {
+ // there is a prev control point, and we have the already mirrored one
+ // in aPrevControl. We also need the quadratic control point for this
+ // new quadratic segment to calculate the 2nd cubic control point
+ const B2DPoint aQuadControlPoint(
+ ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0,
+ ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0);
+
+ // calculate the cubic bezier coefficients from the quadratic ones.
+ const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0);
+ const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0);
+
+ // append curved edge, use mirrored cubic control point directly
+ aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
+ }
+ else
+ {
+ // when no previous control, SVG says to use current point -> straight line.
+ // Just add end point
+ aCurrPoly.append(B2DPoint(nX, nY));
+ }
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+ }
+ break;
+ }
+
+ case 'a' :
+ {
+ bRelative = true;
+ // FALLTHROUGH intended
+ }
+ case 'A' :
+ {
+ nPos++;
+ lcl_skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+ double fRX, fRY, fPhi;
+ sal_Int32 bLargeArcFlag, bSweepFlag;
+
+ if(!lcl_importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importNumberAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importNumberAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(aCurrPoly.count() - 1));
+
+ if( nX == nLastX && nY == nLastY )
+ continue; // start==end -> skip according to SVG spec
+
+ if( fRX == 0.0 || fRY == 0.0 )
+ {
+ // straight line segment according to SVG spec
+ aCurrPoly.append(B2DPoint(nX, nY));
+ }
+ else
+ {
+ // normalize according to SVG spec
+ fRX=fabs(fRX); fRY=fabs(fRY);
+
+ // from the SVG spec, appendix F.6.4
+
+ // |x1'| |cos phi sin phi| |(x1 - x2)/2|
+ // |y1'| = |-sin phi cos phi| |(y1 - y2)/2|
+ const B2DPoint p1(nLastX, nLastY);
+ const B2DPoint p2(nX, nY);
+ B2DHomMatrix aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi*M_PI/180));
+
+ const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
+
+ // ______________________________________ rx y1'
+ // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry
+ // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1'
+ // rx
+ // chose + if f_A != f_S
+ // chose - if f_A = f_S
+ B2DPoint aCenter_prime;
+ const double fRadicant(
+ (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/
+ (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX()));
+ if( fRadicant < 0.0 )
+ {
+ // no solution - according to SVG
+ // spec, scale up ellipse
+ // uniformly such that it passes
+ // through end points (denominator
+ // of radicant solved for fRY,
+ // with s=fRX/fRY)
+ const double fRatio(fRX/fRY);
+ const double fRadicant2(
+ p1_prime.getY()*p1_prime.getY() +
+ p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio));
+ if( fRadicant2 < 0.0 )
+ {
+ // only trivial solution, one
+ // of the axes 0 -> straight
+ // line segment according to
+ // SVG spec
+ aCurrPoly.append(B2DPoint(nX, nY));
+ continue;
+ }
+
+ fRY=sqrt(fRadicant2);
+ fRX=fRatio*fRY;
+
+ // keep center_prime forced to (0,0)
+ }
+ else
+ {
+ const double fFactor(
+ (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
+ sqrt(fRadicant));
+
+ // actually calculate center_prime
+ aCenter_prime = B2DPoint(
+ fFactor*fRX*p1_prime.getY()/fRY,
+ -fFactor*fRY*p1_prime.getX()/fRX);
+ }
+
+ // + u - v
+ // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
+ // - ||u|| ||v||
+
+ // 1 | (x1' - cx')/rx |
+ // theta1 = angle(( ), | | )
+ // 0 | (y1' - cy')/ry |
+ const B2DPoint aRadii(fRX,fRY);
+ double fTheta1(
+ B2DVector(1.0,0.0).angle(
+ (p1_prime-aCenter_prime)/aRadii));
+
+ // |1| | (-x1' - cx')/rx |
+ // theta2 = angle( | | , | | )
+ // |0| | (-y1' - cy')/ry |
+ double fTheta2(
+ B2DVector(1.0,0.0).angle(
+ (-p1_prime-aCenter_prime)/aRadii));
+
+ // map both angles to [0,2pi)
+ fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI);
+ fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI);
+
+ // make sure the large arc is taken
+ // (since
+ // createPolygonFromEllipseSegment()
+ // normalizes to e.g. cw arc)
+ const bool bFlipSegment( (bLargeArcFlag!=0) ==
+ (fmod(fTheta2+2*M_PI-fTheta1,
+ 2*M_PI)<M_PI) );
+ if( bFlipSegment )
+ std::swap(fTheta1,fTheta2);
+
+ // finally, create bezier polygon from this
+ B2DPolygon aSegment(
+ tools::createPolygonFromUnitEllipseSegment(
+ fTheta1, fTheta2 ));
+
+ // transform ellipse by rotation & move to final center
+ aTransform = basegfx::tools::createScaleB2DHomMatrix(fRX, fRY);
+ aTransform.translate(aCenter_prime.getX(),
+ aCenter_prime.getY());
+ aTransform.rotate(fPhi*M_PI/180);
+ const B2DPoint aOffset((p1+p2)/2.0);
+ aTransform.translate(aOffset.getX(),
+ aOffset.getY());
+ aSegment.transform(aTransform);
+
+ // createPolygonFromEllipseSegment()
+ // always creates arcs that are
+ // positively oriented - flip polygon
+ // if we swapped angles above
+ if( bFlipSegment )
+ aSegment.flip();
+ aCurrPoly.append(aSegment);
+ }
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+ }
+ break;
+ }
+
+ default:
+ {
+ OSL_ENSURE(false, "importFromSvgD(): skipping tags in svg:d element (unknown)!");
+ OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar);
+ ++nPos;
+ break;
+ }
+ }
+ }
+
+ if(aCurrPoly.count())
+ {
+ // end-process last poly
+ if(bIsClosed)
+ {
+ closeWithGeometryChange(aCurrPoly);
+ }
+
+ o_rPolyPolygon.append(aCurrPoly);
+ }
+
+ return true;
+ }
+
+ bool importFromSvgPoints( B2DPolygon& o_rPoly,
+ const ::rtl::OUString& rSvgPointsAttribute )
+ {
+ o_rPoly.clear();
+ const sal_Int32 nLen(rSvgPointsAttribute.getLength());
+ sal_Int32 nPos(0);
+ double nX, nY;
+
+ // skip initial whitespace
+ lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
+
+ while(nPos < nLen)
+ {
+ if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
+ if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
+
+ // add point
+ o_rPoly.append(B2DPoint(nX, nY));
+
+ // skip to next number, or finish
+ lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen);
+ }
+
+ return true;
+ }
+
+ ::rtl::OUString exportToSvgD(
+ const B2DPolyPolygon& rPolyPolygon,
+ bool bUseRelativeCoordinates,
+ bool bDetectQuadraticBeziers)
+ {
+ const sal_uInt32 nCount(rPolyPolygon.count());
+ ::rtl::OUString aResult;
+ B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
+
+ for(sal_uInt32 i(0); i < nCount; i++)
+ {
+ const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i));
+ const sal_uInt32 nPointCount(aPolygon.count());
+
+ if(nPointCount)
+ {
+ const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
+ const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
+ sal_Unicode aLastSVGCommand(' '); // last SVG command char
+ B2DPoint aLeft, aRight; // for quadratic bezier test
+
+ // handle polygon start point
+ B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
+ aResult += ::rtl::OUString::valueOf(lcl_getCommand('M', 'm', bUseRelativeCoordinates));
+ lcl_putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ aLastSVGCommand = lcl_getCommand('L', 'l', bUseRelativeCoordinates);
+ aCurrentSVGPosition = aEdgeStart;
+
+ for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
+ {
+ // prepare access to next point
+ const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+ const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
+
+ // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
+ const bool bEdgeIsBezier(bPolyUsesControlPoints
+ && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
+
+ if(bEdgeIsBezier)
+ {
+ // handle bezier edge
+ const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
+ const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
+ bool bIsQuadraticBezier(false);
+
+ // check continuity at current edge's start point. For SVG, do NOT use an
+ // existing continuity since no 'S' or 's' statement should be written. At
+ // import, that 'previous' control vector is not available. SVG documentation
+ // says for interpretation:
+ //
+ // "(If there is no previous command or if the previous command was
+ // not an C, c, S or s, assume the first control point is coincident
+ // with the current point.)"
+ //
+ // That's what is done from our import, so avoid exporting it as first statement
+ // is necessary.
+ const bool bSymmetricAtEdgeStart(
+ 0 != nIndex
+ && CONTINUITY_C2 == aPolygon.getContinuityInPoint(nIndex));
+
+ if(bDetectQuadraticBeziers)
+ {
+ // check for quadratic beziers - that's
+ // the case if both control points are in
+ // the same place when they are prolonged
+ // to the common quadratic control point
+ //
+ // Left: P = (3P1 - P0) / 2
+ // Right: P = (3P2 - P3) / 2
+ aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
+ aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
+ bIsQuadraticBezier = aLeft.equal(aRight);
+ }
+
+ if(bIsQuadraticBezier)
+ {
+ // approximately equal, export as quadratic bezier
+ if(bSymmetricAtEdgeStart)
+ {
+ const sal_Unicode aCommand(lcl_getCommand('T', 't', bUseRelativeCoordinates));
+
+ if(aLastSVGCommand != aCommand)
+ {
+ aResult += ::rtl::OUString::valueOf(aCommand);
+ aLastSVGCommand = aCommand;
+ }
+
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ aLastSVGCommand = aCommand;
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ else
+ {
+ const sal_Unicode aCommand(lcl_getCommand('Q', 'q', bUseRelativeCoordinates));
+
+ if(aLastSVGCommand != aCommand)
+ {
+ aResult += ::rtl::OUString::valueOf(aCommand);
+ aLastSVGCommand = aCommand;
+ }
+
+ lcl_putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ aLastSVGCommand = aCommand;
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ }
+ else
+ {
+ // export as cubic bezier
+ if(bSymmetricAtEdgeStart)
+ {
+ const sal_Unicode aCommand(lcl_getCommand('S', 's', bUseRelativeCoordinates));
+
+ if(aLastSVGCommand != aCommand)
+ {
+ aResult += ::rtl::OUString::valueOf(aCommand);
+ aLastSVGCommand = aCommand;
+ }
+
+ lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ aLastSVGCommand = aCommand;
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ else
+ {
+ const sal_Unicode aCommand(lcl_getCommand('C', 'c', bUseRelativeCoordinates));
+
+ if(aLastSVGCommand != aCommand)
+ {
+ aResult += ::rtl::OUString::valueOf(aCommand);
+ aLastSVGCommand = aCommand;
+ }
+
+ lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ aLastSVGCommand = aCommand;
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ }
+ }
+ else
+ {
+ // straight edge
+ if(0 == nNextIndex)
+ {
+ // it's a closed polygon's last edge and it's not a bezier edge, so there is
+ // no need to write it
+ }
+ else
+ {
+ const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX());
+ const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY());
+
+ if(bXEqual && bYEqual)
+ {
+ // point is a double point; do not export at all
+ }
+ else if(bXEqual)
+ {
+ // export as vertical line
+ const sal_Unicode aCommand(lcl_getCommand('V', 'v', bUseRelativeCoordinates));
+
+ if(aLastSVGCommand != aCommand)
+ {
+ aResult += ::rtl::OUString::valueOf(aCommand);
+ aLastSVGCommand = aCommand;
+ }
+
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ else if(bYEqual)
+ {
+ // export as horizontal line
+ const sal_Unicode aCommand(lcl_getCommand('H', 'h', bUseRelativeCoordinates));
+
+ if(aLastSVGCommand != aCommand)
+ {
+ aResult += ::rtl::OUString::valueOf(aCommand);
+ aLastSVGCommand = aCommand;
+ }
+
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ else
+ {
+ // export as line
+ const sal_Unicode aCommand(lcl_getCommand('L', 'l', bUseRelativeCoordinates));
+
+ if(aLastSVGCommand != aCommand)
+ {
+ aResult += ::rtl::OUString::valueOf(aCommand);
+ aLastSVGCommand = aCommand;
+ }
+
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates);
+ lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates);
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ }
+ }
+
+ // prepare edge start for next loop step
+ aEdgeStart = aEdgeEnd;
+ }
+
+ // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
+ if(aPolygon.isClosed())
+ {
+ aResult += ::rtl::OUString::valueOf(lcl_getCommand('Z', 'z', bUseRelativeCoordinates));
+ }
+ }
+ }
+
+ return aResult;
+ }
+ }
+}
+
+// eof
diff --git a/basegfx/source/polygon/b3dgeometry.cxx b/basegfx/source/polygon/b3dgeometry.cxx
new file mode 100644
index 000000000000..6c1537bac0a6
--- /dev/null
+++ b/basegfx/source/polygon/b3dgeometry.cxx
@@ -0,0 +1,55 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+#include <osl/diagnose.h>
+#include <basegfx/polygon/b3dgeometry.hxx>
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ B3DGeometry::B3DGeometry()
+ : mbUnifiedVectorValid(false)
+ {
+ }
+
+ B3DGeometry::~B3DGeometry()
+ {
+ }
+
+ bool B3DGeometry::operator==(const B3DGeometry& rGeometry) const
+ {
+ return (maPolyPolygon == maPolyPolygon
+ && maPolygonTo3D == maPolygonTo3D
+ && maPolyNormal == maPolyNormal
+ && maPolyTexture == maPolyTexture
+ }
+
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+// eof
diff --git a/basegfx/source/polygon/b3dpolygon.cxx b/basegfx/source/polygon/b3dpolygon.cxx
new file mode 100644
index 000000000000..1985d3301d4b
--- /dev/null
+++ b/basegfx/source/polygon/b3dpolygon.cxx
@@ -0,0 +1,1816 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <osl/diagnose.h>
+#include <basegfx/polygon/b3dpolygon.hxx>
+#include <basegfx/point/b3dpoint.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <rtl/instance.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/color/bcolor.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <vector>
+#include <algorithm>
+
+//////////////////////////////////////////////////////////////////////////////
+
+class CoordinateData3D
+{
+ basegfx::B3DPoint maPoint;
+
+public:
+ CoordinateData3D()
+ : maPoint()
+ {
+ }
+
+ explicit CoordinateData3D(const basegfx::B3DPoint& rData)
+ : maPoint(rData)
+ {
+ }
+
+ const basegfx::B3DPoint& getCoordinate() const
+ {
+ return maPoint;
+ }
+
+ void setCoordinate(const basegfx::B3DPoint& rValue)
+ {
+ if(rValue != maPoint)
+ maPoint = rValue;
+ }
+
+ bool operator==(const CoordinateData3D& rData) const
+ {
+ return (maPoint == rData.getCoordinate());
+ }
+
+ void transform(const basegfx::B3DHomMatrix& rMatrix)
+ {
+ maPoint *= rMatrix;
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class CoordinateDataArray3D
+{
+ typedef ::std::vector< CoordinateData3D > CoordinateData3DVector;
+
+ CoordinateData3DVector maVector;
+
+public:
+ explicit CoordinateDataArray3D(sal_uInt32 nCount)
+ : maVector(nCount)
+ {
+ }
+
+ explicit CoordinateDataArray3D(const CoordinateDataArray3D& rOriginal)
+ : maVector(rOriginal.maVector)
+ {
+ }
+
+ CoordinateDataArray3D(const CoordinateDataArray3D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maVector(rOriginal.maVector.begin() + nIndex, rOriginal.maVector.begin() + (nIndex + nCount))
+ {
+ }
+
+ ~CoordinateDataArray3D()
+ {
+ }
+
+ ::basegfx::B3DVector getNormal() const
+ {
+ ::basegfx::B3DVector aRetval;
+ const sal_uInt32 nPointCount(maVector.size());
+
+ if(nPointCount > 2)
+ {
+ sal_uInt32 nISmallest(0);
+ sal_uInt32 a(0);
+ const basegfx::B3DPoint* pSmallest(&maVector[0].getCoordinate());
+ const basegfx::B3DPoint* pNext(0);
+ const basegfx::B3DPoint* pPrev(0);
+
+ // To guarantee a correctly oriented point, choose an outmost one
+ // which then cannot be concave
+ for(a = 1; a < nPointCount; a++)
+ {
+ const basegfx::B3DPoint& rCandidate = maVector[a].getCoordinate();
+
+ if((rCandidate.getX() < pSmallest->getX())
+ || (rCandidate.getX() == pSmallest->getX() && rCandidate.getY() < pSmallest->getY())
+ || (rCandidate.getX() == pSmallest->getX() && rCandidate.getY() == pSmallest->getY() && rCandidate.getZ() < pSmallest->getZ()))
+ {
+ nISmallest = a;
+ pSmallest = &rCandidate;
+ }
+ }
+
+ // look for a next point different from minimal one
+ for(a = (nISmallest + 1) % nPointCount; a != nISmallest; a = (a + 1) % nPointCount)
+ {
+ const basegfx::B3DPoint& rCandidate = maVector[a].getCoordinate();
+
+ if(!rCandidate.equal(*pSmallest))
+ {
+ pNext = &rCandidate;
+ break;
+ }
+ }
+
+ // look for a previous point different from minimal one
+ for(a = (nISmallest + nPointCount - 1) % nPointCount; a != nISmallest; a = (a + nPointCount - 1) % nPointCount)
+ {
+ const basegfx::B3DPoint& rCandidate = maVector[a].getCoordinate();
+
+ if(!rCandidate.equal(*pSmallest))
+ {
+ pPrev = &rCandidate;
+ break;
+ }
+ }
+
+ // we always have a minimal point. If we also have a different next and previous,
+ // we can calculate the normal
+ if(pNext && pPrev)
+ {
+ const basegfx::B3DVector aPrev(*pPrev - *pSmallest);
+ const basegfx::B3DVector aNext(*pNext - *pSmallest);
+
+ aRetval = cross(aPrev, aNext);
+ aRetval.normalize();
+ }
+ }
+
+ return aRetval;
+ }
+
+ sal_uInt32 count() const
+ {
+ return maVector.size();
+ }
+
+ bool operator==(const CoordinateDataArray3D& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ const basegfx::B3DPoint& getCoordinate(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex].getCoordinate();
+ }
+
+ void setCoordinate(sal_uInt32 nIndex, const basegfx::B3DPoint& rValue)
+ {
+ maVector[nIndex].setCoordinate(rValue);
+ }
+
+ void insert(sal_uInt32 nIndex, const CoordinateData3D& rValue, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rValue
+ CoordinateData3DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ maVector.insert(aIndex, nCount, rValue);
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const CoordinateDataArray3D& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maVector.size());
+
+ if(nCount)
+ {
+ // insert data
+ CoordinateData3DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ CoordinateData3DVector::const_iterator aStart(rSource.maVector.begin());
+ CoordinateData3DVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // remove point data
+ CoordinateData3DVector::iterator aStart(maVector.begin());
+ aStart += nIndex;
+ const CoordinateData3DVector::iterator aEnd(aStart + nCount);
+ maVector.erase(aStart, aEnd);
+ }
+ }
+
+ void flip()
+ {
+ if(maVector.size() > 1)
+ {
+ const sal_uInt32 nHalfSize(maVector.size() >> 1L);
+ CoordinateData3DVector::iterator aStart(maVector.begin());
+ CoordinateData3DVector::iterator aEnd(maVector.end() - 1L);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ ::std::swap(*aStart, *aEnd);
+ aStart++;
+ aEnd--;
+ }
+ }
+ }
+
+ void transform(const ::basegfx::B3DHomMatrix& rMatrix)
+ {
+ CoordinateData3DVector::iterator aStart(maVector.begin());
+ CoordinateData3DVector::iterator aEnd(maVector.end());
+
+ for(; aStart != aEnd; aStart++)
+ {
+ aStart->transform(rMatrix);
+ }
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class BColorArray
+{
+ typedef ::std::vector< ::basegfx::BColor > BColorDataVector;
+
+ BColorDataVector maVector;
+ sal_uInt32 mnUsedEntries;
+
+public:
+ explicit BColorArray(sal_uInt32 nCount)
+ : maVector(nCount),
+ mnUsedEntries(0L)
+ {
+ }
+
+ explicit BColorArray(const BColorArray& rOriginal)
+ : maVector(rOriginal.maVector),
+ mnUsedEntries(rOriginal.mnUsedEntries)
+ {
+ }
+
+ BColorArray(const BColorArray& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maVector(),
+ mnUsedEntries(0L)
+ {
+ BColorDataVector::const_iterator aStart(rOriginal.maVector.begin());
+ aStart += nIndex;
+ BColorDataVector::const_iterator aEnd(aStart);
+ aEnd += nCount;
+ maVector.reserve(nCount);
+
+ for(; aStart != aEnd; aStart++)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries++;
+
+ maVector.push_back(*aStart);
+ }
+ }
+
+ ~BColorArray()
+ {
+ }
+
+ sal_uInt32 count() const
+ {
+ return maVector.size();
+ }
+
+ bool operator==(const BColorArray& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ bool isUsed() const
+ {
+ return (0L != mnUsedEntries);
+ }
+
+ const ::basegfx::BColor& getBColor(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex];
+ }
+
+ void setBColor(sal_uInt32 nIndex, const ::basegfx::BColor& rValue)
+ {
+ bool bWasUsed(mnUsedEntries && !maVector[nIndex].equalZero());
+ bool bIsUsed(!rValue.equalZero());
+
+ if(bWasUsed)
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ }
+ else
+ {
+ maVector[nIndex] = ::basegfx::BColor::getEmptyBColor();
+ mnUsedEntries--;
+ }
+ }
+ else
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ mnUsedEntries++;
+ }
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::BColor& rValue, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rValue
+ BColorDataVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ maVector.insert(aIndex, nCount, rValue);
+
+ if(!rValue.equalZero())
+ mnUsedEntries += nCount;
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const BColorArray& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maVector.size());
+
+ if(nCount)
+ {
+ // insert data
+ BColorDataVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ BColorDataVector::const_iterator aStart(rSource.maVector.begin());
+ BColorDataVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+
+ for(; aStart != aEnd; aStart++)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries++;
+ }
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ const BColorDataVector::iterator aDeleteStart(maVector.begin() + nIndex);
+ const BColorDataVector::iterator aDeleteEnd(aDeleteStart + nCount);
+ BColorDataVector::const_iterator aStart(aDeleteStart);
+
+ for(; mnUsedEntries && aStart != aDeleteEnd; aStart++)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries--;
+ }
+
+ // remove point data
+ maVector.erase(aDeleteStart, aDeleteEnd);
+ }
+ }
+
+ void flip()
+ {
+ if(maVector.size() > 1)
+ {
+ const sal_uInt32 nHalfSize(maVector.size() >> 1L);
+ BColorDataVector::iterator aStart(maVector.begin());
+ BColorDataVector::iterator aEnd(maVector.end() - 1L);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ ::std::swap(*aStart, *aEnd);
+ aStart++;
+ aEnd--;
+ }
+ }
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class NormalsArray3D
+{
+ typedef ::std::vector< ::basegfx::B3DVector > NormalsData3DVector;
+
+ NormalsData3DVector maVector;
+ sal_uInt32 mnUsedEntries;
+
+public:
+ explicit NormalsArray3D(sal_uInt32 nCount)
+ : maVector(nCount),
+ mnUsedEntries(0L)
+ {
+ }
+
+ explicit NormalsArray3D(const NormalsArray3D& rOriginal)
+ : maVector(rOriginal.maVector),
+ mnUsedEntries(rOriginal.mnUsedEntries)
+ {
+ }
+
+ NormalsArray3D(const NormalsArray3D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maVector(),
+ mnUsedEntries(0L)
+ {
+ NormalsData3DVector::const_iterator aStart(rOriginal.maVector.begin());
+ aStart += nIndex;
+ NormalsData3DVector::const_iterator aEnd(aStart);
+ aEnd += nCount;
+ maVector.reserve(nCount);
+
+ for(; aStart != aEnd; aStart++)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries++;
+
+ maVector.push_back(*aStart);
+ }
+ }
+
+ ~NormalsArray3D()
+ {
+ }
+
+ sal_uInt32 count() const
+ {
+ return maVector.size();
+ }
+
+ bool operator==(const NormalsArray3D& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ bool isUsed() const
+ {
+ return (0L != mnUsedEntries);
+ }
+
+ const ::basegfx::B3DVector& getNormal(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex];
+ }
+
+ void setNormal(sal_uInt32 nIndex, const ::basegfx::B3DVector& rValue)
+ {
+ bool bWasUsed(mnUsedEntries && !maVector[nIndex].equalZero());
+ bool bIsUsed(!rValue.equalZero());
+
+ if(bWasUsed)
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ }
+ else
+ {
+ maVector[nIndex] = ::basegfx::B3DVector::getEmptyVector();
+ mnUsedEntries--;
+ }
+ }
+ else
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ mnUsedEntries++;
+ }
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::B3DVector& rValue, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rValue
+ NormalsData3DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ maVector.insert(aIndex, nCount, rValue);
+
+ if(!rValue.equalZero())
+ mnUsedEntries += nCount;
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const NormalsArray3D& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maVector.size());
+
+ if(nCount)
+ {
+ // insert data
+ NormalsData3DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ NormalsData3DVector::const_iterator aStart(rSource.maVector.begin());
+ NormalsData3DVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+
+ for(; aStart != aEnd; aStart++)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries++;
+ }
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ const NormalsData3DVector::iterator aDeleteStart(maVector.begin() + nIndex);
+ const NormalsData3DVector::iterator aDeleteEnd(aDeleteStart + nCount);
+ NormalsData3DVector::const_iterator aStart(aDeleteStart);
+
+ for(; mnUsedEntries && aStart != aDeleteEnd; aStart++)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries--;
+ }
+
+ // remove point data
+ maVector.erase(aDeleteStart, aDeleteEnd);
+ }
+ }
+
+ void flip()
+ {
+ if(maVector.size() > 1)
+ {
+ const sal_uInt32 nHalfSize(maVector.size() >> 1L);
+ NormalsData3DVector::iterator aStart(maVector.begin());
+ NormalsData3DVector::iterator aEnd(maVector.end() - 1L);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ ::std::swap(*aStart, *aEnd);
+ aStart++;
+ aEnd--;
+ }
+ }
+ }
+
+ void transform(const basegfx::B3DHomMatrix& rMatrix)
+ {
+ NormalsData3DVector::iterator aStart(maVector.begin());
+ NormalsData3DVector::iterator aEnd(maVector.end());
+
+ for(; aStart != aEnd; aStart++)
+ {
+ (*aStart) *= rMatrix;
+ }
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class TextureCoordinate2D
+{
+ typedef ::std::vector< ::basegfx::B2DPoint > TextureData2DVector;
+
+ TextureData2DVector maVector;
+ sal_uInt32 mnUsedEntries;
+
+public:
+ explicit TextureCoordinate2D(sal_uInt32 nCount)
+ : maVector(nCount),
+ mnUsedEntries(0L)
+ {
+ }
+
+ explicit TextureCoordinate2D(const TextureCoordinate2D& rOriginal)
+ : maVector(rOriginal.maVector),
+ mnUsedEntries(rOriginal.mnUsedEntries)
+ {
+ }
+
+ TextureCoordinate2D(const TextureCoordinate2D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maVector(),
+ mnUsedEntries(0L)
+ {
+ TextureData2DVector::const_iterator aStart(rOriginal.maVector.begin());
+ aStart += nIndex;
+ TextureData2DVector::const_iterator aEnd(aStart);
+ aEnd += nCount;
+ maVector.reserve(nCount);
+
+ for(; aStart != aEnd; aStart++)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries++;
+
+ maVector.push_back(*aStart);
+ }
+ }
+
+ ~TextureCoordinate2D()
+ {
+ }
+
+ sal_uInt32 count() const
+ {
+ return maVector.size();
+ }
+
+ bool operator==(const TextureCoordinate2D& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ bool isUsed() const
+ {
+ return (0L != mnUsedEntries);
+ }
+
+ const ::basegfx::B2DPoint& getTextureCoordinate(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex];
+ }
+
+ void setTextureCoordinate(sal_uInt32 nIndex, const ::basegfx::B2DPoint& rValue)
+ {
+ bool bWasUsed(mnUsedEntries && !maVector[nIndex].equalZero());
+ bool bIsUsed(!rValue.equalZero());
+
+ if(bWasUsed)
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ }
+ else
+ {
+ maVector[nIndex] = ::basegfx::B2DPoint::getEmptyPoint();
+ mnUsedEntries--;
+ }
+ }
+ else
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ mnUsedEntries++;
+ }
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::B2DPoint& rValue, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rValue
+ TextureData2DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ maVector.insert(aIndex, nCount, rValue);
+
+ if(!rValue.equalZero())
+ mnUsedEntries += nCount;
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const TextureCoordinate2D& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maVector.size());
+
+ if(nCount)
+ {
+ // insert data
+ TextureData2DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ TextureData2DVector::const_iterator aStart(rSource.maVector.begin());
+ TextureData2DVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+
+ for(; aStart != aEnd; aStart++)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries++;
+ }
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ const TextureData2DVector::iterator aDeleteStart(maVector.begin() + nIndex);
+ const TextureData2DVector::iterator aDeleteEnd(aDeleteStart + nCount);
+ TextureData2DVector::const_iterator aStart(aDeleteStart);
+
+ for(; mnUsedEntries && aStart != aDeleteEnd; aStart++)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries--;
+ }
+
+ // remove point data
+ maVector.erase(aDeleteStart, aDeleteEnd);
+ }
+ }
+
+ void flip()
+ {
+ if(maVector.size() > 1)
+ {
+ const sal_uInt32 nHalfSize(maVector.size() >> 1L);
+ TextureData2DVector::iterator aStart(maVector.begin());
+ TextureData2DVector::iterator aEnd(maVector.end() - 1L);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ ::std::swap(*aStart, *aEnd);
+ aStart++;
+ aEnd--;
+ }
+ }
+ }
+
+ void transform(const ::basegfx::B2DHomMatrix& rMatrix)
+ {
+ TextureData2DVector::iterator aStart(maVector.begin());
+ TextureData2DVector::iterator aEnd(maVector.end());
+
+ for(; aStart != aEnd; aStart++)
+ {
+ (*aStart) *= rMatrix;
+ }
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class ImplB3DPolygon
+{
+ // The point vector. This vector exists always and defines the
+ // count of members.
+ CoordinateDataArray3D maPoints;
+
+ // The BColor vector. This vectors are created on demand
+ // and may be zero.
+ BColorArray* mpBColors;
+
+ // The Normals vector. This vectors are created on demand
+ // and may be zero.
+ NormalsArray3D* mpNormals;
+
+ // The TextureCoordinates vector. This vectors are created on demand
+ // and may be zero.
+ TextureCoordinate2D* mpTextureCoordiantes;
+
+ // The calculated plane normal. mbPlaneNormalValid says if it's valid.
+ ::basegfx::B3DVector maPlaneNormal;
+
+ // bitfield
+ // flag which decides if this polygon is opened or closed
+ unsigned mbIsClosed : 1;
+
+ // flag which says if maPlaneNormal is up-to-date
+ unsigned mbPlaneNormalValid : 1;
+
+protected:
+ void invalidatePlaneNormal()
+ {
+ if(mbPlaneNormalValid)
+ {
+ mbPlaneNormalValid = false;
+ }
+ }
+
+public:
+ // This constructor is only used from the static identity polygon, thus
+ // the RefCount is set to 1 to never 'delete' this static incarnation.
+ ImplB3DPolygon()
+ : maPoints(0L),
+ mpBColors(0L),
+ mpNormals(0L),
+ mpTextureCoordiantes(0L),
+ maPlaneNormal(::basegfx::B3DVector::getEmptyVector()),
+ mbIsClosed(false),
+ mbPlaneNormalValid(true)
+ {
+ // complete initialization with defaults
+ }
+
+ ImplB3DPolygon(const ImplB3DPolygon& rToBeCopied)
+ : maPoints(rToBeCopied.maPoints),
+ mpBColors(0L),
+ mpNormals(0L),
+ mpTextureCoordiantes(0L),
+ maPlaneNormal(rToBeCopied.maPlaneNormal),
+ mbIsClosed(rToBeCopied.mbIsClosed),
+ mbPlaneNormalValid(rToBeCopied.mbPlaneNormalValid)
+ {
+ // complete initialization using copy
+ if(rToBeCopied.mpBColors && rToBeCopied.mpBColors->isUsed())
+ {
+ mpBColors = new BColorArray(*rToBeCopied.mpBColors);
+ }
+
+ if(rToBeCopied.mpNormals && rToBeCopied.mpNormals->isUsed())
+ {
+ mpNormals = new NormalsArray3D(*rToBeCopied.mpNormals);
+ }
+
+ if(rToBeCopied.mpTextureCoordiantes && rToBeCopied.mpTextureCoordiantes->isUsed())
+ {
+ mpTextureCoordiantes = new TextureCoordinate2D(*rToBeCopied.mpTextureCoordiantes);
+ }
+ }
+
+ ImplB3DPolygon(const ImplB3DPolygon& rToBeCopied, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maPoints(rToBeCopied.maPoints, nIndex, nCount),
+ mpBColors(0L),
+ mpNormals(0L),
+ mpTextureCoordiantes(0L),
+ maPlaneNormal(::basegfx::B3DVector::getEmptyVector()),
+ mbIsClosed(rToBeCopied.mbIsClosed),
+ mbPlaneNormalValid(false)
+ {
+ // complete initialization using partly copy
+ if(rToBeCopied.mpBColors && rToBeCopied.mpBColors->isUsed())
+ {
+ mpBColors = new BColorArray(*rToBeCopied.mpBColors, nIndex, nCount);
+
+ if(!mpBColors->isUsed())
+ {
+ delete mpBColors;
+ mpBColors = 0L;
+ }
+ }
+
+ if(rToBeCopied.mpNormals && rToBeCopied.mpNormals->isUsed())
+ {
+ mpNormals = new NormalsArray3D(*rToBeCopied.mpNormals, nIndex, nCount);
+
+ if(!mpNormals->isUsed())
+ {
+ delete mpNormals;
+ mpNormals = 0L;
+ }
+ }
+
+ if(rToBeCopied.mpTextureCoordiantes && rToBeCopied.mpTextureCoordiantes->isUsed())
+ {
+ mpTextureCoordiantes = new TextureCoordinate2D(*rToBeCopied.mpTextureCoordiantes, nIndex, nCount);
+
+ if(!mpTextureCoordiantes->isUsed())
+ {
+ delete mpTextureCoordiantes;
+ mpTextureCoordiantes = 0L;
+ }
+ }
+ }
+
+ ~ImplB3DPolygon()
+ {
+ if(mpBColors)
+ {
+ delete mpBColors;
+ mpBColors = 0L;
+ }
+
+ if(mpNormals)
+ {
+ delete mpNormals;
+ mpNormals = 0L;
+ }
+
+ if(mpTextureCoordiantes)
+ {
+ delete mpTextureCoordiantes;
+ mpTextureCoordiantes = 0L;
+ }
+ }
+
+ sal_uInt32 count() const
+ {
+ return maPoints.count();
+ }
+
+ bool isClosed() const
+ {
+ return mbIsClosed;
+ }
+
+ void setClosed(bool bNew)
+ {
+ if(bNew != (bool)mbIsClosed)
+ {
+ mbIsClosed = bNew;
+ }
+ }
+
+ inline bool impBColorsAreEqual(const ImplB3DPolygon& rCandidate) const
+ {
+ bool bBColorsAreEqual(true);
+
+ if(mpBColors)
+ {
+ if(rCandidate.mpBColors)
+ {
+ bBColorsAreEqual = (*mpBColors == *rCandidate.mpBColors);
+ }
+ else
+ {
+ // candidate has no BColors, so it's assumed all unused.
+ bBColorsAreEqual = !mpBColors->isUsed();
+ }
+ }
+ else
+ {
+ if(rCandidate.mpBColors)
+ {
+ // we have no TextureCoordiantes, so it's assumed all unused.
+ bBColorsAreEqual = !rCandidate.mpBColors->isUsed();
+ }
+ }
+
+ return bBColorsAreEqual;
+ }
+
+ inline bool impNormalsAreEqual(const ImplB3DPolygon& rCandidate) const
+ {
+ bool bNormalsAreEqual(true);
+
+ if(mpNormals)
+ {
+ if(rCandidate.mpNormals)
+ {
+ bNormalsAreEqual = (*mpNormals == *rCandidate.mpNormals);
+ }
+ else
+ {
+ // candidate has no normals, so it's assumed all unused.
+ bNormalsAreEqual = !mpNormals->isUsed();
+ }
+ }
+ else
+ {
+ if(rCandidate.mpNormals)
+ {
+ // we have no normals, so it's assumed all unused.
+ bNormalsAreEqual = !rCandidate.mpNormals->isUsed();
+ }
+ }
+
+ return bNormalsAreEqual;
+ }
+
+ inline bool impTextureCoordinatesAreEqual(const ImplB3DPolygon& rCandidate) const
+ {
+ bool bTextureCoordinatesAreEqual(true);
+
+ if(mpTextureCoordiantes)
+ {
+ if(rCandidate.mpTextureCoordiantes)
+ {
+ bTextureCoordinatesAreEqual = (*mpTextureCoordiantes == *rCandidate.mpTextureCoordiantes);
+ }
+ else
+ {
+ // candidate has no TextureCoordinates, so it's assumed all unused.
+ bTextureCoordinatesAreEqual = !mpTextureCoordiantes->isUsed();
+ }
+ }
+ else
+ {
+ if(rCandidate.mpTextureCoordiantes)
+ {
+ // we have no TextureCoordiantes, so it's assumed all unused.
+ bTextureCoordinatesAreEqual = !rCandidate.mpTextureCoordiantes->isUsed();
+ }
+ }
+
+ return bTextureCoordinatesAreEqual;
+ }
+
+ bool operator==(const ImplB3DPolygon& rCandidate) const
+ {
+ if(mbIsClosed == rCandidate.mbIsClosed)
+ {
+ if(maPoints == rCandidate.maPoints)
+ {
+ if(impBColorsAreEqual(rCandidate))
+ {
+ if(impNormalsAreEqual(rCandidate))
+ {
+ if(impTextureCoordinatesAreEqual(rCandidate))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ const ::basegfx::B3DPoint& getPoint(sal_uInt32 nIndex) const
+ {
+ return maPoints.getCoordinate(nIndex);
+ }
+
+ void setPoint(sal_uInt32 nIndex, const ::basegfx::B3DPoint& rValue)
+ {
+ maPoints.setCoordinate(nIndex, rValue);
+ invalidatePlaneNormal();
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::B3DPoint& rPoint, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ CoordinateData3D aCoordinate(rPoint);
+ maPoints.insert(nIndex, aCoordinate, nCount);
+ invalidatePlaneNormal();
+
+ if(mpBColors)
+ {
+ mpBColors->insert(nIndex, ::basegfx::BColor::getEmptyBColor(), nCount);
+ }
+
+ if(mpNormals)
+ {
+ mpNormals->insert(nIndex, ::basegfx::B3DVector::getEmptyVector(), nCount);
+ }
+
+ if(mpTextureCoordiantes)
+ {
+ mpTextureCoordiantes->insert(nIndex, ::basegfx::B2DPoint::getEmptyPoint(), nCount);
+ }
+ }
+ }
+
+ const ::basegfx::BColor& getBColor(sal_uInt32 nIndex) const
+ {
+ if(mpBColors)
+ {
+ return mpBColors->getBColor(nIndex);
+ }
+ else
+ {
+ return ::basegfx::BColor::getEmptyBColor();
+ }
+ }
+
+ void setBColor(sal_uInt32 nIndex, const ::basegfx::BColor& rValue)
+ {
+ if(!mpBColors)
+ {
+ if(!rValue.equalZero())
+ {
+ mpBColors = new BColorArray(maPoints.count());
+ mpBColors->setBColor(nIndex, rValue);
+ }
+ }
+ else
+ {
+ mpBColors->setBColor(nIndex, rValue);
+
+ if(!mpBColors->isUsed())
+ {
+ delete mpBColors;
+ mpBColors = 0L;
+ }
+ }
+ }
+
+ bool areBColorsUsed() const
+ {
+ return (mpBColors && mpBColors->isUsed());
+ }
+
+ void clearBColors()
+ {
+ if(mpBColors)
+ {
+ delete mpBColors;
+ mpBColors = 0L;
+ }
+ }
+
+ const ::basegfx::B3DVector& getNormal() const
+ {
+ if(!mbPlaneNormalValid)
+ {
+ const_cast< ImplB3DPolygon* >(this)->maPlaneNormal = maPoints.getNormal();
+ const_cast< ImplB3DPolygon* >(this)->mbPlaneNormalValid = true;
+ }
+
+ return maPlaneNormal;
+ }
+
+ const ::basegfx::B3DVector& getNormal(sal_uInt32 nIndex) const
+ {
+ if(mpNormals)
+ {
+ return mpNormals->getNormal(nIndex);
+ }
+ else
+ {
+ return ::basegfx::B3DVector::getEmptyVector();
+ }
+ }
+
+ void setNormal(sal_uInt32 nIndex, const ::basegfx::B3DVector& rValue)
+ {
+ if(!mpNormals)
+ {
+ if(!rValue.equalZero())
+ {
+ mpNormals = new NormalsArray3D(maPoints.count());
+ mpNormals->setNormal(nIndex, rValue);
+ }
+ }
+ else
+ {
+ mpNormals->setNormal(nIndex, rValue);
+
+ if(!mpNormals->isUsed())
+ {
+ delete mpNormals;
+ mpNormals = 0L;
+ }
+ }
+ }
+
+ void transformNormals(const ::basegfx::B3DHomMatrix& rMatrix)
+ {
+ if(mpNormals)
+ {
+ mpNormals->transform(rMatrix);
+ }
+ }
+
+ bool areNormalsUsed() const
+ {
+ return (mpNormals && mpNormals->isUsed());
+ }
+
+ void clearNormals()
+ {
+ if(mpNormals)
+ {
+ delete mpNormals;
+ mpNormals = 0L;
+ }
+ }
+
+ const ::basegfx::B2DPoint& getTextureCoordinate(sal_uInt32 nIndex) const
+ {
+ if(mpTextureCoordiantes)
+ {
+ return mpTextureCoordiantes->getTextureCoordinate(nIndex);
+ }
+ else
+ {
+ return ::basegfx::B2DPoint::getEmptyPoint();
+ }
+ }
+
+ void setTextureCoordinate(sal_uInt32 nIndex, const ::basegfx::B2DPoint& rValue)
+ {
+ if(!mpTextureCoordiantes)
+ {
+ if(!rValue.equalZero())
+ {
+ mpTextureCoordiantes = new TextureCoordinate2D(maPoints.count());
+ mpTextureCoordiantes->setTextureCoordinate(nIndex, rValue);
+ }
+ }
+ else
+ {
+ mpTextureCoordiantes->setTextureCoordinate(nIndex, rValue);
+
+ if(!mpTextureCoordiantes->isUsed())
+ {
+ delete mpTextureCoordiantes;
+ mpTextureCoordiantes = 0L;
+ }
+ }
+ }
+
+ bool areTextureCoordinatesUsed() const
+ {
+ return (mpTextureCoordiantes && mpTextureCoordiantes->isUsed());
+ }
+
+ void clearTextureCoordinates()
+ {
+ if(mpTextureCoordiantes)
+ {
+ delete mpTextureCoordiantes;
+ mpTextureCoordiantes = 0L;
+ }
+ }
+
+ void transformTextureCoordinates(const ::basegfx::B2DHomMatrix& rMatrix)
+ {
+ if(mpTextureCoordiantes)
+ {
+ mpTextureCoordiantes->transform(rMatrix);
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ImplB3DPolygon& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maPoints.count());
+
+ if(nCount)
+ {
+ maPoints.insert(nIndex, rSource.maPoints);
+ invalidatePlaneNormal();
+
+ if(rSource.mpBColors && rSource.mpBColors->isUsed())
+ {
+ if(!mpBColors)
+ {
+ mpBColors = new BColorArray(maPoints.count());
+ }
+
+ mpBColors->insert(nIndex, *rSource.mpBColors);
+ }
+ else
+ {
+ if(mpBColors)
+ {
+ mpBColors->insert(nIndex, ::basegfx::BColor::getEmptyBColor(), nCount);
+ }
+ }
+
+ if(rSource.mpNormals && rSource.mpNormals->isUsed())
+ {
+ if(!mpNormals)
+ {
+ mpNormals = new NormalsArray3D(maPoints.count());
+ }
+
+ mpNormals->insert(nIndex, *rSource.mpNormals);
+ }
+ else
+ {
+ if(mpNormals)
+ {
+ mpNormals->insert(nIndex, ::basegfx::B3DVector::getEmptyVector(), nCount);
+ }
+ }
+
+ if(rSource.mpTextureCoordiantes && rSource.mpTextureCoordiantes->isUsed())
+ {
+ if(!mpTextureCoordiantes)
+ {
+ mpTextureCoordiantes = new TextureCoordinate2D(maPoints.count());
+ }
+
+ mpTextureCoordiantes->insert(nIndex, *rSource.mpTextureCoordiantes);
+ }
+ else
+ {
+ if(mpTextureCoordiantes)
+ {
+ mpTextureCoordiantes->insert(nIndex, ::basegfx::B2DPoint::getEmptyPoint(), nCount);
+ }
+ }
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ maPoints.remove(nIndex, nCount);
+ invalidatePlaneNormal();
+
+ if(mpBColors)
+ {
+ mpBColors->remove(nIndex, nCount);
+
+ if(!mpBColors->isUsed())
+ {
+ delete mpBColors;
+ mpBColors = 0L;
+ }
+ }
+
+ if(mpNormals)
+ {
+ mpNormals->remove(nIndex, nCount);
+
+ if(!mpNormals->isUsed())
+ {
+ delete mpNormals;
+ mpNormals = 0L;
+ }
+ }
+
+ if(mpTextureCoordiantes)
+ {
+ mpTextureCoordiantes->remove(nIndex, nCount);
+
+ if(!mpTextureCoordiantes->isUsed())
+ {
+ delete mpTextureCoordiantes;
+ mpTextureCoordiantes = 0L;
+ }
+ }
+ }
+ }
+
+ void flip()
+ {
+ if(maPoints.count() > 1)
+ {
+ maPoints.flip();
+
+ if(mbPlaneNormalValid)
+ {
+ // mirror plane normal
+ maPlaneNormal = -maPlaneNormal;
+ }
+
+ if(mpBColors)
+ {
+ mpBColors->flip();
+ }
+
+ if(mpNormals)
+ {
+ mpNormals->flip();
+ }
+
+ if(mpTextureCoordiantes)
+ {
+ mpTextureCoordiantes->flip();
+ }
+ }
+ }
+
+ bool hasDoublePoints() const
+ {
+ if(mbIsClosed)
+ {
+ // check for same start and end point
+ const sal_uInt32 nIndex(maPoints.count() - 1L);
+
+ if(maPoints.getCoordinate(0L) == maPoints.getCoordinate(nIndex))
+ {
+ const bool bBColorEqual(!mpBColors || (mpBColors->getBColor(0L) == mpBColors->getBColor(nIndex)));
+
+ if(bBColorEqual)
+ {
+ const bool bNormalsEqual(!mpNormals || (mpNormals->getNormal(0L) == mpNormals->getNormal(nIndex)));
+
+ if(bNormalsEqual)
+ {
+ const bool bTextureCoordinatesEqual(!mpTextureCoordiantes || (mpTextureCoordiantes->getTextureCoordinate(0L) == mpTextureCoordiantes->getTextureCoordinate(nIndex)));
+
+ if(bTextureCoordinatesEqual)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ // test for range
+ for(sal_uInt32 a(0L); a < maPoints.count() - 1L; a++)
+ {
+ if(maPoints.getCoordinate(a) == maPoints.getCoordinate(a + 1L))
+ {
+ const bool bBColorEqual(!mpBColors || (mpBColors->getBColor(a) == mpBColors->getBColor(a + 1L)));
+
+ if(bBColorEqual)
+ {
+ const bool bNormalsEqual(!mpNormals || (mpNormals->getNormal(a) == mpNormals->getNormal(a + 1L)));
+
+ if(bNormalsEqual)
+ {
+ const bool bTextureCoordinatesEqual(!mpTextureCoordiantes || (mpTextureCoordiantes->getTextureCoordinate(a) == mpTextureCoordiantes->getTextureCoordinate(a + 1L)));
+
+ if(bTextureCoordinatesEqual)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ void removeDoublePointsAtBeginEnd()
+ {
+ // Only remove DoublePoints at Begin and End when poly is closed
+ if(mbIsClosed)
+ {
+ bool bRemove;
+
+ do
+ {
+ bRemove = false;
+
+ if(maPoints.count() > 1L)
+ {
+ const sal_uInt32 nIndex(maPoints.count() - 1L);
+ bRemove = (maPoints.getCoordinate(0L) == maPoints.getCoordinate(nIndex));
+
+ if(bRemove && mpBColors && !(mpBColors->getBColor(0L) == mpBColors->getBColor(nIndex)))
+ {
+ bRemove = false;
+ }
+
+ if(bRemove && mpNormals && !(mpNormals->getNormal(0L) == mpNormals->getNormal(nIndex)))
+ {
+ bRemove = false;
+ }
+
+ if(bRemove && mpTextureCoordiantes && !(mpTextureCoordiantes->getTextureCoordinate(0L) == mpTextureCoordiantes->getTextureCoordinate(nIndex)))
+ {
+ bRemove = false;
+ }
+ }
+
+ if(bRemove)
+ {
+ const sal_uInt32 nIndex(maPoints.count() - 1L);
+ remove(nIndex, 1L);
+ }
+ } while(bRemove);
+ }
+ }
+
+ void removeDoublePointsWholeTrack()
+ {
+ sal_uInt32 nIndex(0L);
+
+ // test as long as there are at least two points and as long as the index
+ // is smaller or equal second last point
+ while((maPoints.count() > 1L) && (nIndex <= maPoints.count() - 2L))
+ {
+ const sal_uInt32 nNextIndex(nIndex + 1L);
+ bool bRemove(maPoints.getCoordinate(nIndex) == maPoints.getCoordinate(nNextIndex));
+
+ if(bRemove && mpBColors && !(mpBColors->getBColor(nIndex) == mpBColors->getBColor(nNextIndex)))
+ {
+ bRemove = false;
+ }
+
+ if(bRemove && mpNormals && !(mpNormals->getNormal(nIndex) == mpNormals->getNormal(nNextIndex)))
+ {
+ bRemove = false;
+ }
+
+ if(bRemove && mpTextureCoordiantes && !(mpTextureCoordiantes->getTextureCoordinate(nIndex) == mpTextureCoordiantes->getTextureCoordinate(nNextIndex)))
+ {
+ bRemove = false;
+ }
+
+ if(bRemove)
+ {
+ // if next is same as index and the control vectors are unused, delete index
+ remove(nIndex, 1L);
+ }
+ else
+ {
+ // if different, step forward
+ nIndex++;
+ }
+ }
+ }
+
+ void transform(const ::basegfx::B3DHomMatrix& rMatrix)
+ {
+ maPoints.transform(rMatrix);
+
+ // Here, it seems to be possible to transform a valid plane normal and to avoid
+ // invalidation, but it's not true. If the transformation contains shears or e.g.
+ // perspective projection, the orthogonality to the transformed plane will not
+ // be preserved. It may be possible to test that at the matrix to not invalidate in
+ // all cases or to extract a matrix which does not 'shear' the vector which is
+ // a normal in this case. As long as this is not sure, i will just invalidate.
+ invalidatePlaneNormal();
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace { struct DefaultPolygon : public rtl::Static< B3DPolygon::ImplType,
+ DefaultPolygon > {}; }
+
+ B3DPolygon::B3DPolygon() :
+ mpPolygon(DefaultPolygon::get())
+ {
+ }
+
+ B3DPolygon::B3DPolygon(const B3DPolygon& rPolygon) :
+ mpPolygon(rPolygon.mpPolygon)
+ {
+ }
+
+ B3DPolygon::B3DPolygon(const B3DPolygon& rPolygon, sal_uInt32 nIndex, sal_uInt32 nCount) :
+ mpPolygon(ImplB3DPolygon(*rPolygon.mpPolygon, nIndex, nCount))
+ {
+ // TODO(P2): one extra temporary here (cow_wrapper copies
+ // given ImplB3DPolygon into its internal impl_t wrapper type)
+ OSL_ENSURE(nIndex + nCount > rPolygon.mpPolygon->count(), "B3DPolygon constructor outside range (!)");
+ }
+
+ B3DPolygon::~B3DPolygon()
+ {
+ }
+
+ B3DPolygon& B3DPolygon::operator=(const B3DPolygon& rPolygon)
+ {
+ mpPolygon = rPolygon.mpPolygon;
+ return *this;
+ }
+
+ void B3DPolygon::makeUnique()
+ {
+ mpPolygon.make_unique();
+ }
+
+ bool B3DPolygon::operator==(const B3DPolygon& rPolygon) const
+ {
+ if(mpPolygon.same_object(rPolygon.mpPolygon))
+ return true;
+
+ return (*mpPolygon == *rPolygon.mpPolygon);
+ }
+
+ bool B3DPolygon::operator!=(const B3DPolygon& rPolygon) const
+ {
+ return !(*this == rPolygon);
+ }
+
+ sal_uInt32 B3DPolygon::count() const
+ {
+ return mpPolygon->count();
+ }
+
+ basegfx::B3DPoint B3DPolygon::getB3DPoint(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ return mpPolygon->getPoint(nIndex);
+ }
+
+ void B3DPolygon::setB3DPoint(sal_uInt32 nIndex, const basegfx::B3DPoint& rValue)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ if(getB3DPoint(nIndex) != rValue)
+ mpPolygon->setPoint(nIndex, rValue);
+ }
+
+ BColor B3DPolygon::getBColor(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ return mpPolygon->getBColor(nIndex);
+ }
+
+ void B3DPolygon::setBColor(sal_uInt32 nIndex, const BColor& rValue)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ if(mpPolygon->getBColor(nIndex) != rValue)
+ mpPolygon->setBColor(nIndex, rValue);
+ }
+
+ bool B3DPolygon::areBColorsUsed() const
+ {
+ return mpPolygon->areBColorsUsed();
+ }
+
+ void B3DPolygon::clearBColors()
+ {
+ if(mpPolygon->areBColorsUsed())
+ mpPolygon->clearBColors();
+ }
+
+ B3DVector B3DPolygon::getNormal() const
+ {
+ return mpPolygon->getNormal();
+ }
+
+ B3DVector B3DPolygon::getNormal(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ return mpPolygon->getNormal(nIndex);
+ }
+
+ void B3DPolygon::setNormal(sal_uInt32 nIndex, const B3DVector& rValue)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ if(mpPolygon->getNormal(nIndex) != rValue)
+ mpPolygon->setNormal(nIndex, rValue);
+ }
+
+ void B3DPolygon::transformNormals(const B3DHomMatrix& rMatrix)
+ {
+ if(mpPolygon->areNormalsUsed() && !rMatrix.isIdentity())
+ mpPolygon->transformNormals(rMatrix);
+ }
+
+ bool B3DPolygon::areNormalsUsed() const
+ {
+ return mpPolygon->areNormalsUsed();
+ }
+
+ void B3DPolygon::clearNormals()
+ {
+ if(mpPolygon->areNormalsUsed())
+ mpPolygon->clearNormals();
+ }
+
+ B2DPoint B3DPolygon::getTextureCoordinate(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ return mpPolygon->getTextureCoordinate(nIndex);
+ }
+
+ void B3DPolygon::setTextureCoordinate(sal_uInt32 nIndex, const B2DPoint& rValue)
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ if(mpPolygon->getTextureCoordinate(nIndex) != rValue)
+ mpPolygon->setTextureCoordinate(nIndex, rValue);
+ }
+
+ void B3DPolygon::transformTextureCoordiantes(const B2DHomMatrix& rMatrix)
+ {
+ if(mpPolygon->areTextureCoordinatesUsed() && !rMatrix.isIdentity())
+ mpPolygon->transformTextureCoordinates(rMatrix);
+ }
+
+ bool B3DPolygon::areTextureCoordinatesUsed() const
+ {
+ return mpPolygon->areTextureCoordinatesUsed();
+ }
+
+ void B3DPolygon::clearTextureCoordinates()
+ {
+ if(mpPolygon->areTextureCoordinatesUsed())
+ mpPolygon->clearTextureCoordinates();
+ }
+
+ void B3DPolygon::insert(sal_uInt32 nIndex, const ::basegfx::B3DPoint& rPoint, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex <= mpPolygon->count(), "B3DPolygon Insert outside range (!)");
+
+ if(nCount)
+ mpPolygon->insert(nIndex, rPoint, nCount);
+ }
+
+ void B3DPolygon::append(const basegfx::B3DPoint& rPoint, sal_uInt32 nCount)
+ {
+ if(nCount)
+ mpPolygon->insert(mpPolygon->count(), rPoint, nCount);
+ }
+
+ void B3DPolygon::insert(sal_uInt32 nIndex, const B3DPolygon& rPoly, sal_uInt32 nIndex2, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex <= mpPolygon->count(), "B3DPolygon Insert outside range (!)");
+
+ if(rPoly.count())
+ {
+ if(!nCount)
+ {
+ nCount = rPoly.count();
+ }
+
+ if(0L == nIndex2 && nCount == rPoly.count())
+ {
+ mpPolygon->insert(nIndex, *rPoly.mpPolygon);
+ }
+ else
+ {
+ OSL_ENSURE(nIndex2 + nCount <= rPoly.mpPolygon->count(), "B3DPolygon Insert outside range (!)");
+ ImplB3DPolygon aTempPoly(*rPoly.mpPolygon, nIndex2, nCount);
+ mpPolygon->insert(nIndex, aTempPoly);
+ }
+ }
+ }
+
+ void B3DPolygon::append(const B3DPolygon& rPoly, sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(rPoly.count())
+ {
+ if(!nCount)
+ {
+ nCount = rPoly.count();
+ }
+
+ if(0L == nIndex && nCount == rPoly.count())
+ {
+ mpPolygon->insert(mpPolygon->count(), *rPoly.mpPolygon);
+ }
+ else
+ {
+ OSL_ENSURE(nIndex + nCount <= rPoly.mpPolygon->count(), "B3DPolygon Append outside range (!)");
+ ImplB3DPolygon aTempPoly(*rPoly.mpPolygon, nIndex, nCount);
+ mpPolygon->insert(mpPolygon->count(), aTempPoly);
+ }
+ }
+ }
+
+ void B3DPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex + nCount <= mpPolygon->count(), "B3DPolygon Remove outside range (!)");
+
+ if(nCount)
+ mpPolygon->remove(nIndex, nCount);
+ }
+
+ void B3DPolygon::clear()
+ {
+ mpPolygon = DefaultPolygon::get();
+ }
+
+ bool B3DPolygon::isClosed() const
+ {
+ return mpPolygon->isClosed();
+ }
+
+ void B3DPolygon::setClosed(bool bNew)
+ {
+ if(isClosed() != bNew)
+ mpPolygon->setClosed(bNew);
+ }
+
+ void B3DPolygon::flip()
+ {
+ if(count() > 1)
+ mpPolygon->flip();
+ }
+
+ bool B3DPolygon::hasDoublePoints() const
+ {
+ return (mpPolygon->count() > 1L && mpPolygon->hasDoublePoints());
+ }
+
+ void B3DPolygon::removeDoublePoints()
+ {
+ if(hasDoublePoints())
+ {
+ mpPolygon->removeDoublePointsAtBeginEnd();
+ mpPolygon->removeDoublePointsWholeTrack();
+ }
+ }
+
+ void B3DPolygon::transform(const basegfx::B3DHomMatrix& rMatrix)
+ {
+ if(mpPolygon->count() && !rMatrix.isIdentity())
+ {
+ mpPolygon->transform(rMatrix);
+ }
+ }
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+// eof
diff --git a/basegfx/source/polygon/b3dpolygonclipper.cxx b/basegfx/source/polygon/b3dpolygonclipper.cxx
new file mode 100644
index 000000000000..88ebf12dae7b
--- /dev/null
+++ b/basegfx/source/polygon/b3dpolygonclipper.cxx
@@ -0,0 +1,574 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+
+#include <basegfx/polygon/b3dpolygonclipper.hxx>
+#include <osl/diagnose.h>
+#include <basegfx/polygon/b3dpolygontools.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/polygon/b3dpolygontools.hxx>
+#include <basegfx/range/b3drange.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/color/bcolor.hxx>
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace
+ {
+ inline bool impIsInside(const B3DPoint& rCandidate, double fPlaneOffset, tools::B3DOrientation ePlaneOrthogonal)
+ {
+ if(tools::B3DORIENTATION_X == ePlaneOrthogonal)
+ {
+ return fTools::moreOrEqual(rCandidate.getX(), fPlaneOffset);
+ }
+ else if(tools::B3DORIENTATION_Y == ePlaneOrthogonal)
+ {
+ return fTools::moreOrEqual(rCandidate.getY(), fPlaneOffset);
+ }
+ else
+ {
+ return fTools::moreOrEqual(rCandidate.getZ(), fPlaneOffset);
+ }
+ }
+
+ inline double impGetCut(const B3DPoint& rCurrent, const B3DPoint& rNext, double fPlaneOffset, tools::B3DOrientation ePlaneOrthogonal)
+ {
+ if(tools::B3DORIENTATION_X == ePlaneOrthogonal)
+ {
+ return ((fPlaneOffset - rCurrent.getX())/(rNext.getX() - rCurrent.getX()));
+ }
+ else if(tools::B3DORIENTATION_Y == ePlaneOrthogonal)
+ {
+ return ((fPlaneOffset - rCurrent.getY())/(rNext.getY() - rCurrent.getY()));
+ }
+ else
+ {
+ return ((fPlaneOffset - rCurrent.getZ())/(rNext.getZ() - rCurrent.getZ()));
+ }
+ }
+
+ void impAppendCopy(B3DPolygon& rDest, const B3DPolygon& rSource, sal_uInt32 nIndex)
+ {
+ rDest.append(rSource.getB3DPoint(nIndex));
+
+ if(rSource.areBColorsUsed())
+ {
+ rDest.setBColor(rDest.count() - 1L, rSource.getBColor(nIndex));
+ }
+
+ if(rSource.areNormalsUsed())
+ {
+ rDest.setNormal(rDest.count() - 1L, rSource.getNormal(nIndex));
+ }
+
+ if(rSource.areTextureCoordinatesUsed())
+ {
+ rDest.setTextureCoordinate(rDest.count() - 1L, rSource.getTextureCoordinate(nIndex));
+ }
+ }
+
+ void impAppendInterpolate(B3DPolygon& rDest, const B3DPolygon& rSource, sal_uInt32 nIndA, sal_uInt32 nIndB, double fCut)
+ {
+ const B3DPoint aCurrPoint(rSource.getB3DPoint(nIndA));
+ const B3DPoint aNextPoint(rSource.getB3DPoint(nIndB));
+ rDest.append(interpolate(aCurrPoint, aNextPoint, fCut));
+
+ if(rSource.areBColorsUsed())
+ {
+ const BColor aCurrBColor(rSource.getBColor(nIndA));
+ const BColor aNextBColor(rSource.getBColor(nIndB));
+ rDest.setBColor(rDest.count() - 1L, interpolate(aCurrBColor, aNextBColor, fCut));
+ }
+
+ if(rSource.areNormalsUsed())
+ {
+ const B3DVector aCurrVector(rSource.getNormal(nIndA));
+ const B3DVector aNextVector(rSource.getNormal(nIndB));
+ rDest.setNormal(rDest.count() - 1L, interpolate(aCurrVector, aNextVector, fCut));
+ }
+
+ if(rSource.areTextureCoordinatesUsed())
+ {
+ const B2DPoint aCurrTxCo(rSource.getTextureCoordinate(nIndA));
+ const B2DPoint aNextTxCo(rSource.getTextureCoordinate(nIndB));
+ rDest.setTextureCoordinate(rDest.count() - 1L, interpolate(aCurrTxCo, aNextTxCo, fCut));
+ }
+ }
+ }
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace tools
+ {
+ B3DPolyPolygon clipPolygonOnOrthogonalPlane(const B3DPolygon& rCandidate, B3DOrientation ePlaneOrthogonal, bool bClipPositive, double fPlaneOffset, bool bStroke)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(rCandidate.count())
+ {
+ const B3DRange aCandidateRange(getRange(rCandidate));
+
+ if(B3DORIENTATION_X == ePlaneOrthogonal && fTools::moreOrEqual(aCandidateRange.getMinX(), fPlaneOffset))
+ {
+ // completely above and on the clip plane.
+ if(bClipPositive)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(B3DORIENTATION_X == ePlaneOrthogonal && fTools::lessOrEqual(aCandidateRange.getMaxX(), fPlaneOffset))
+ {
+ // completely below and on the clip plane.
+ if(!bClipPositive)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(B3DORIENTATION_Y == ePlaneOrthogonal && fTools::moreOrEqual(aCandidateRange.getMinY(), fPlaneOffset))
+ {
+ // completely above and on the clip plane.
+ if(bClipPositive)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(B3DORIENTATION_Y == ePlaneOrthogonal && fTools::lessOrEqual(aCandidateRange.getMaxY(), fPlaneOffset))
+ {
+ // completely below and on the clip plane.
+ if(!bClipPositive)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(B3DORIENTATION_Z == ePlaneOrthogonal && fTools::moreOrEqual(aCandidateRange.getMinZ(), fPlaneOffset))
+ {
+ // completely above and on the clip plane.
+ if(bClipPositive)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(B3DORIENTATION_Z == ePlaneOrthogonal && fTools::lessOrEqual(aCandidateRange.getMaxZ(), fPlaneOffset))
+ {
+ // completely below and on the clip plane.
+ if(!bClipPositive)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else
+ {
+ // prepare loop(s)
+ B3DPolygon aNewPolygon;
+ B3DPoint aCurrent(rCandidate.getB3DPoint(0L));
+ const sal_uInt32 nPointCount(rCandidate.count());
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+ bool bCurrentInside(impIsInside(aCurrent, fPlaneOffset, ePlaneOrthogonal) == bClipPositive);
+
+ if(bCurrentInside)
+ {
+ impAppendCopy(aNewPolygon, rCandidate, 0L);
+ }
+
+ if(bStroke)
+ {
+ // open polygon, create clipped line snippets.
+ for(sal_uInt32 a(0L); a < nEdgeCount; a++)
+ {
+ // get next point data
+ const sal_uInt32 nNextIndex((a + 1L == nPointCount) ? 0L : a + 1L);
+ const B3DPoint aNext(rCandidate.getB3DPoint(nNextIndex));
+ const bool bNextInside(impIsInside(aNext, fPlaneOffset, ePlaneOrthogonal) == bClipPositive);
+
+ if(bCurrentInside != bNextInside)
+ {
+ // change inside/outside
+ if(bNextInside)
+ {
+ // entering, finish existing and start new line polygon
+ if(aNewPolygon.count() > 1L)
+ {
+ aRetval.append(aNewPolygon);
+ }
+
+ aNewPolygon.clear();
+ }
+
+ // calculate and add cut point
+ const double fCut(impGetCut(aCurrent, aNext, fPlaneOffset, ePlaneOrthogonal));
+ impAppendInterpolate(aNewPolygon, rCandidate, a, nNextIndex, fCut);
+
+ // pepare next step
+ bCurrentInside = bNextInside;
+ }
+
+ if(bNextInside)
+ {
+ impAppendCopy(aNewPolygon, rCandidate, nNextIndex);
+ }
+
+ // pepare next step
+ aCurrent = aNext;
+ }
+
+ if(aNewPolygon.count() > 1L)
+ {
+ aRetval.append(aNewPolygon);
+ }
+ }
+ else
+ {
+ // closed polygon, create single clipped closed polygon
+ for(sal_uInt32 a(0L); a < nEdgeCount; a++)
+ {
+ // get next point data, use offset
+ const sal_uInt32 nNextIndex((a + 1L == nPointCount) ? 0L : a + 1L);
+ const B3DPoint aNext(rCandidate.getB3DPoint(nNextIndex));
+ const bool bNextInside(impIsInside(aNext, fPlaneOffset, ePlaneOrthogonal) == bClipPositive);
+
+ if(bCurrentInside != bNextInside)
+ {
+ // calculate and add cut point
+ const double fCut(impGetCut(aCurrent, aNext, fPlaneOffset, ePlaneOrthogonal));
+ impAppendInterpolate(aNewPolygon, rCandidate, a, nNextIndex, fCut);
+
+ // pepare next step
+ bCurrentInside = bNextInside;
+ }
+
+ if(bNextInside && nNextIndex)
+ {
+ impAppendCopy(aNewPolygon, rCandidate, nNextIndex);
+ }
+
+ // pepare next step
+ aCurrent = aNext;
+ }
+
+ if(aNewPolygon.count() > 2L)
+ {
+ aNewPolygon.setClosed(true);
+ aRetval.append(aNewPolygon);
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon clipPolyPolygonOnOrthogonalPlane(const B3DPolyPolygon& rCandidate, B3DOrientation ePlaneOrthogonal, bool bClipPositive, double fPlaneOffset, bool bStroke)
+ {
+ B3DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(clipPolygonOnOrthogonalPlane(rCandidate.getB3DPolygon(a), ePlaneOrthogonal, bClipPositive, fPlaneOffset, bStroke));
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon clipPolyPolygonOnRange(const B3DPolyPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke)
+ {
+ B3DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(clipPolygonOnRange(rCandidate.getB3DPolygon(a), rRange, bInside, bStroke));
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon clipPolygonOnRange(const B3DPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(rRange.isEmpty())
+ {
+ // clipping against an empty range. Nothing is inside an empty range, so the polygon
+ // is outside the range. So only return if not inside is wanted
+ if(!bInside && rCandidate.count())
+ {
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(rCandidate.count())
+ {
+ const B3DRange aCandidateRange3D(getRange(rCandidate));
+ const B2DRange aCandidateRange(
+ aCandidateRange3D.getMinX(), aCandidateRange3D.getMinY(),
+ aCandidateRange3D.getMaxX(), aCandidateRange3D.getMaxY());
+
+ if(rRange.isInside(aCandidateRange))
+ {
+ // candidate is completely inside given range, nothing to do. Is also true with curves.
+ if(bInside)
+ {
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(!rRange.overlaps(aCandidateRange))
+ {
+ // candidate is completely outside given range, nothing to do. Is also true with curves.
+ if(!bInside)
+ {
+ aRetval.append(rCandidate);
+ }
+ }
+ else
+ {
+ // clip against the six planes of the range
+ // against lower X
+ aRetval = clipPolygonOnOrthogonalPlane(rCandidate, tools::B3DORIENTATION_X, bInside, rRange.getMinX(), bStroke);
+
+ if(aRetval.count())
+ {
+ // against lower Y
+ if(1L == aRetval.count())
+ {
+ aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Y, bInside, rRange.getMinY(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Y, bInside, rRange.getMinY(), bStroke);
+ }
+
+ if(aRetval.count())
+ {
+ // against higher X
+ if(1L == aRetval.count())
+ {
+ aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_X, !bInside, rRange.getMaxX(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_X, !bInside, rRange.getMaxX(), bStroke);
+ }
+
+ if(aRetval.count())
+ {
+ // against higher Y
+ if(1L == aRetval.count())
+ {
+ aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Y, !bInside, rRange.getMaxY(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Y, !bInside, rRange.getMaxY(), bStroke);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon clipPolyPolygonOnRange(const B3DPolyPolygon& rCandidate, const B3DRange& rRange, bool bInside, bool bStroke)
+ {
+ B3DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(clipPolygonOnRange(rCandidate.getB3DPolygon(a), rRange, bInside, bStroke));
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon clipPolygonOnRange(const B3DPolygon& rCandidate, const B3DRange& rRange, bool bInside, bool bStroke)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(rRange.isEmpty())
+ {
+ // clipping against an empty range. Nothing is inside an empty range, so the polygon
+ // is outside the range. So only return if not inside is wanted
+ if(!bInside && rCandidate.count())
+ {
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(rCandidate.count())
+ {
+ const B3DRange aCandidateRange(getRange(rCandidate));
+
+ if(rRange.isInside(aCandidateRange))
+ {
+ // candidate is completely inside given range, nothing to do. Is also true with curves.
+ if(bInside)
+ {
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(!rRange.overlaps(aCandidateRange))
+ {
+ // candidate is completely outside given range, nothing to do. Is also true with curves.
+ if(!bInside)
+ {
+ aRetval.append(rCandidate);
+ }
+ }
+ else
+ {
+ // clip against X,Y first and see if there's something left
+ const B2DRange aCandidateRange2D(rRange.getMinX(), rRange.getMinY(), rRange.getMaxX(), rRange.getMaxY());
+ aRetval = clipPolygonOnRange(rCandidate, aCandidateRange2D, bInside, bStroke);
+
+ if(aRetval.count())
+ {
+ // against lower Z
+ if(1L == aRetval.count())
+ {
+ aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Z, bInside, rRange.getMinZ(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Z, bInside, rRange.getMinZ(), bStroke);
+ }
+
+ if(aRetval.count())
+ {
+ // against higher Z
+ if(1L == aRetval.count())
+ {
+ aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Z, !bInside, rRange.getMaxZ(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Z, !bInside, rRange.getMaxZ(), bStroke);
+ }
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon clipPolygonOnPlane(const B3DPolygon& rCandidate, const B3DPoint& rPointOnPlane, const B3DVector& rPlaneNormal, bool bClipPositive, bool bStroke)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(rPlaneNormal.equalZero())
+ {
+ // not really a plane definition, return polygon
+ aRetval.append(rCandidate);
+ }
+ else if(rCandidate.count())
+ {
+ // build transform to project planeNormal on X-Axis and pointOnPlane to null point
+ B3DHomMatrix aMatrixTransform;
+ aMatrixTransform.translate(-rPointOnPlane.getX(), -rPointOnPlane.getY(), -rPointOnPlane.getZ());
+ const double fRotInXY(atan2(rPlaneNormal.getY(), rPlaneNormal.getX()));
+ const double fRotInXZ(atan2(-rPlaneNormal.getZ(), rPlaneNormal.getXYLength()));
+ if(!fTools::equalZero(fRotInXY) || !fTools::equalZero(fRotInXZ))
+ {
+ aMatrixTransform.rotate(0.0, fRotInXZ, fRotInXY);
+ }
+
+ // transform polygon to clip scenario
+ B3DPolygon aCandidate(rCandidate);
+ aCandidate.transform(aMatrixTransform);
+
+ // clip on YZ plane
+ aRetval = clipPolygonOnOrthogonalPlane(aCandidate, tools::B3DORIENTATION_X, bClipPositive, 0.0, bStroke);
+
+ if(aRetval.count())
+ {
+ // if there is a result, it needs to be transformed back
+ aMatrixTransform.invert();
+ aRetval.transform(aMatrixTransform);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon clipPolyPolygonOnPlane(const B3DPolyPolygon& rCandidate, const B3DPoint& rPointOnPlane, const B3DVector& rPlaneNormal, bool bClipPositive, bool bStroke)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(rPlaneNormal.equalZero())
+ {
+ // not really a plane definition, return polygon
+ aRetval = rCandidate;
+ }
+ else if(rCandidate.count())
+ {
+ // build transform to project planeNormal on X-Axis and pointOnPlane to null point
+ B3DHomMatrix aMatrixTransform;
+ aMatrixTransform.translate(-rPointOnPlane.getX(), -rPointOnPlane.getY(), -rPointOnPlane.getZ());
+ const double fRotInXY(atan2(rPlaneNormal.getY(), rPlaneNormal.getX()));
+ const double fRotInXZ(atan2(-rPlaneNormal.getZ(), rPlaneNormal.getXYLength()));
+ if(!fTools::equalZero(fRotInXY) || !fTools::equalZero(fRotInXZ))
+ {
+ aMatrixTransform.rotate(0.0, fRotInXZ, fRotInXY);
+ }
+
+ // transform polygon to clip scenario
+ aRetval = rCandidate;
+ aRetval.transform(aMatrixTransform);
+
+ // clip on YZ plane
+ aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_X, bClipPositive, 0.0, bStroke);
+
+ if(aRetval.count())
+ {
+ // if there is a result, it needs to be transformed back
+ aMatrixTransform.invert();
+ aRetval.transform(aMatrixTransform);
+ }
+ }
+
+ return aRetval;
+ }
+
+ } // end of namespace tools
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+// eof
diff --git a/basegfx/source/polygon/b3dpolygontools.cxx b/basegfx/source/polygon/b3dpolygontools.cxx
new file mode 100644
index 000000000000..77bbbd379d3c
--- /dev/null
+++ b/basegfx/source/polygon/b3dpolygontools.cxx
@@ -0,0 +1,1263 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <osl/diagnose.h>
+#include <basegfx/polygon/b3dpolygontools.hxx>
+#include <basegfx/polygon/b3dpolygon.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/range/b3drange.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/tuple/b3ituple.hxx>
+#include <numeric>
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace tools
+ {
+ // B3DPolygon tools
+ void checkClosed(B3DPolygon& rCandidate)
+ {
+ while(rCandidate.count() > 1L
+ && rCandidate.getB3DPoint(0L).equal(rCandidate.getB3DPoint(rCandidate.count() - 1L)))
+ {
+ rCandidate.setClosed(true);
+ rCandidate.remove(rCandidate.count() - 1L);
+ }
+ }
+
+ // Get successor and predecessor indices. Returning the same index means there
+ // is none. Same for successor.
+ sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B3DPolygon& rCandidate)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
+
+ if(nIndex)
+ {
+ return nIndex - 1L;
+ }
+ else if(rCandidate.count())
+ {
+ return rCandidate.count() - 1L;
+ }
+ else
+ {
+ return nIndex;
+ }
+ }
+
+ sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B3DPolygon& rCandidate)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
+
+ if(nIndex + 1L < rCandidate.count())
+ {
+ return nIndex + 1L;
+ }
+ else
+ {
+ return 0L;
+ }
+ }
+
+ B3DRange getRange(const B3DPolygon& rCandidate)
+ {
+ B3DRange aRetval;
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ for(sal_uInt32 a(0L); a < nPointCount; a++)
+ {
+ const B3DPoint aTestPoint(rCandidate.getB3DPoint(a));
+ aRetval.expand(aTestPoint);
+ }
+
+ return aRetval;
+ }
+
+ B3DVector getNormal(const B3DPolygon& rCandidate)
+ {
+ return rCandidate.getNormal();
+ }
+
+ B3DVector getPositiveOrientedNormal(const B3DPolygon& rCandidate)
+ {
+ B3DVector aRetval(rCandidate.getNormal());
+
+ if(ORIENTATION_NEGATIVE == getOrientation(rCandidate))
+ {
+ aRetval = -aRetval;
+ }
+
+ return aRetval;
+ }
+
+ B2VectorOrientation getOrientation(const B3DPolygon& rCandidate)
+ {
+ B2VectorOrientation eRetval(ORIENTATION_NEUTRAL);
+
+ if(rCandidate.count() > 2L)
+ {
+ const double fSignedArea(getSignedArea(rCandidate));
+
+ if(fSignedArea > 0.0)
+ {
+ eRetval = ORIENTATION_POSITIVE;
+ }
+ else if(fSignedArea < 0.0)
+ {
+ eRetval = ORIENTATION_NEGATIVE;
+ }
+ }
+
+ return eRetval;
+ }
+
+ double getSignedArea(const B3DPolygon& rCandidate)
+ {
+ double fRetval(0.0);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 2)
+ {
+ const B3DVector aAbsNormal(absolute(getNormal(rCandidate)));
+ sal_uInt16 nCase(3); // default: ignore z
+
+ if(aAbsNormal.getX() > aAbsNormal.getY())
+ {
+ if(aAbsNormal.getX() > aAbsNormal.getZ())
+ {
+ nCase = 1; // ignore x
+ }
+ }
+ else if(aAbsNormal.getY() > aAbsNormal.getZ())
+ {
+ nCase = 2; // ignore y
+ }
+
+ B3DPoint aPreviousPoint(rCandidate.getB3DPoint(nPointCount - 1L));
+
+ for(sal_uInt32 a(0L); a < nPointCount; a++)
+ {
+ const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(a));
+
+ switch(nCase)
+ {
+ case 1: // ignore x
+ fRetval += aPreviousPoint.getZ() * aCurrentPoint.getY();
+ fRetval -= aPreviousPoint.getY() * aCurrentPoint.getZ();
+ break;
+ case 2: // ignore y
+ fRetval += aPreviousPoint.getX() * aCurrentPoint.getZ();
+ fRetval -= aPreviousPoint.getZ() * aCurrentPoint.getX();
+ break;
+ case 3: // ignore z
+ fRetval += aPreviousPoint.getX() * aCurrentPoint.getY();
+ fRetval -= aPreviousPoint.getY() * aCurrentPoint.getX();
+ break;
+ }
+
+ // prepare next step
+ aPreviousPoint = aCurrentPoint;
+ }
+
+ switch(nCase)
+ {
+ case 1: // ignore x
+ fRetval /= 2.0 * aAbsNormal.getX();
+ break;
+ case 2: // ignore y
+ fRetval /= 2.0 * aAbsNormal.getY();
+ break;
+ case 3: // ignore z
+ fRetval /= 2.0 * aAbsNormal.getZ();
+ break;
+ }
+ }
+
+ return fRetval;
+ }
+
+ double getArea(const B3DPolygon& rCandidate)
+ {
+ double fRetval(0.0);
+
+ if(rCandidate.count() > 2)
+ {
+ fRetval = getSignedArea(rCandidate);
+ const double fZero(0.0);
+
+ if(fTools::less(fRetval, fZero))
+ {
+ fRetval = -fRetval;
+ }
+ }
+
+ return fRetval;
+ }
+
+ double getEdgeLength(const B3DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "getEdgeLength: Access to polygon out of range (!)");
+ double fRetval(0.0);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nIndex < nPointCount)
+ {
+ if(rCandidate.isClosed() || ((nIndex + 1L) != nPointCount))
+ {
+ const sal_uInt32 nNextIndex(getIndexOfSuccessor(nIndex, rCandidate));
+ const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(nIndex));
+ const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
+ const B3DVector aVector(aNextPoint - aCurrentPoint);
+ fRetval = aVector.getLength();
+ }
+ }
+
+ return fRetval;
+ }
+
+ double getLength(const B3DPolygon& rCandidate)
+ {
+ double fRetval(0.0);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1L)
+ {
+ const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+
+ for(sal_uInt32 a(0L); a < nLoopCount; a++)
+ {
+ const sal_uInt32 nNextIndex(getIndexOfSuccessor(a, rCandidate));
+ const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(a));
+ const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
+ const B3DVector aVector(aNextPoint - aCurrentPoint);
+ fRetval += aVector.getLength();
+ }
+ }
+
+ return fRetval;
+ }
+
+ B3DPoint getPositionAbsolute(const B3DPolygon& rCandidate, double fDistance, double fLength)
+ {
+ B3DPoint aRetval;
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1L)
+ {
+ sal_uInt32 nIndex(0L);
+ bool bIndexDone(false);
+ const double fZero(0.0);
+ double fEdgeLength(fZero);
+
+ // get length if not given
+ if(fTools::equalZero(fLength))
+ {
+ fLength = getLength(rCandidate);
+ }
+
+ // handle fDistance < 0.0
+ if(fTools::less(fDistance, fZero))
+ {
+ if(rCandidate.isClosed())
+ {
+ // if fDistance < 0.0 increment with multiple of fLength
+ sal_uInt32 nCount(sal_uInt32(-fDistance / fLength));
+ fDistance += double(nCount + 1L) * fLength;
+ }
+ else
+ {
+ // crop to polygon start
+ fDistance = fZero;
+ bIndexDone = true;
+ }
+ }
+
+ // handle fDistance >= fLength
+ if(fTools::moreOrEqual(fDistance, fLength))
+ {
+ if(rCandidate.isClosed())
+ {
+ // if fDistance >= fLength decrement with multiple of fLength
+ sal_uInt32 nCount(sal_uInt32(fDistance / fLength));
+ fDistance -= (double)(nCount) * fLength;
+ }
+ else
+ {
+ // crop to polygon end
+ fDistance = fZero;
+ nIndex = nPointCount - 1L;
+ bIndexDone = true;
+ }
+ }
+
+ // look for correct index. fDistance is now [0.0 .. fLength[
+ if(!bIndexDone)
+ {
+ do
+ {
+ // get length of next edge
+ fEdgeLength = getEdgeLength(rCandidate, nIndex);
+
+ if(fTools::moreOrEqual(fDistance, fEdgeLength))
+ {
+ // go to next edge
+ fDistance -= fEdgeLength;
+ nIndex++;
+ }
+ else
+ {
+ // it's on this edge, stop
+ bIndexDone = true;
+ }
+ } while (!bIndexDone);
+ }
+
+ // get the point using nIndex
+ aRetval = rCandidate.getB3DPoint(nIndex);
+
+ // if fDistance != 0.0, move that length on the edge. The edge
+ // length is in fEdgeLength.
+ if(!fTools::equalZero(fDistance))
+ {
+ sal_uInt32 nNextIndex(getIndexOfSuccessor(nIndex, rCandidate));
+ const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
+ double fRelative(fZero);
+
+ if(!fTools::equalZero(fEdgeLength))
+ {
+ fRelative = fDistance / fEdgeLength;
+ }
+
+ // add calculated average value to the return value
+ aRetval += interpolate(aRetval, aNextPoint, fRelative);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPoint getPositionRelative(const B3DPolygon& rCandidate, double fDistance, double fLength)
+ {
+ // get length if not given
+ if(fTools::equalZero(fLength))
+ {
+ fLength = getLength(rCandidate);
+ }
+
+ // multiply fDistance with real length to get absolute position and
+ // use getPositionAbsolute
+ return getPositionAbsolute(rCandidate, fDistance * fLength, fLength);
+ }
+
+ void applyLineDashing(const B3DPolygon& rCandidate, const ::std::vector<double>& rDotDashArray, B3DPolyPolygon* pLineTarget, B3DPolyPolygon* pGapTarget, double fDotDashLength)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ const sal_uInt32 nDotDashCount(rDotDashArray.size());
+
+ if(fTools::lessOrEqual(fDotDashLength, 0.0))
+ {
+ fDotDashLength = ::std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
+ }
+
+ if(fTools::more(fDotDashLength, 0.0) && (pLineTarget || pGapTarget) && nPointCount)
+ {
+ // clear targets
+ if(pLineTarget)
+ {
+ pLineTarget->clear();
+ }
+
+ if(pGapTarget)
+ {
+ pGapTarget->clear();
+ }
+
+ // prepare current edge's start
+ B3DPoint aCurrentPoint(rCandidate.getB3DPoint(0));
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+
+ // prepare DotDashArray iteration and the line/gap switching bool
+ sal_uInt32 nDotDashIndex(0);
+ bool bIsLine(true);
+ double fDotDashMovingLength(rDotDashArray[0]);
+ B3DPolygon aSnippet;
+
+ // iterate over all edges
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // update current edge
+ double fLastDotDashMovingLength(0.0);
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
+ const double fEdgeLength(B3DVector(aNextPoint - aCurrentPoint).getLength());
+
+ 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)
+ {
+ if(!aSnippet.count())
+ {
+ aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fLastDotDashMovingLength / fEdgeLength));
+ }
+
+ aSnippet.append(interpolate(aCurrentPoint, aNextPoint, 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;
+ }
+
+ // append snippet [fLastDotDashMovingLength, fEdgeLength]
+ const bool bHandleLine(bIsLine && pLineTarget);
+ const bool bHandleGap(!bIsLine && pGapTarget);
+
+ if(bHandleLine || bHandleGap)
+ {
+ if(!aSnippet.count())
+ {
+ aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fLastDotDashMovingLength / fEdgeLength));
+ }
+
+ aSnippet.append(aNextPoint);
+ }
+
+ // prepare move to next edge
+ fDotDashMovingLength -= fEdgeLength;
+
+ // prepare next edge step (end point gets new start point)
+ aCurrentPoint = aNextPoint;
+ }
+
+ // append last intermediate results (if exists)
+ if(aSnippet.count())
+ {
+ if(bIsLine && pLineTarget)
+ {
+ pLineTarget->append(aSnippet);
+ }
+ else if(!bIsLine && pGapTarget)
+ {
+ pGapTarget->append(aSnippet);
+ }
+ }
+
+ // check if start and end polygon may be merged
+ if(pLineTarget)
+ {
+ const sal_uInt32 nCount(pLineTarget->count());
+
+ if(nCount > 1)
+ {
+ // these polygons were created above, there exists none with less than two points,
+ // thus dircet point access below is allowed
+ const B3DPolygon aFirst(pLineTarget->getB3DPolygon(0));
+ B3DPolygon aLast(pLineTarget->getB3DPolygon(nCount - 1));
+
+ if(aFirst.getB3DPoint(0).equal(aLast.getB3DPoint(aLast.count() - 1)))
+ {
+ // start of first and end of last are the same -> merge them
+ aLast.append(aFirst);
+ aLast.removeDoublePoints();
+ pLineTarget->setB3DPolygon(0, aLast);
+ pLineTarget->remove(nCount - 1);
+ }
+ }
+ }
+
+ if(pGapTarget)
+ {
+ const sal_uInt32 nCount(pGapTarget->count());
+
+ if(nCount > 1)
+ {
+ // these polygons were created above, there exists none with less than two points,
+ // thus dircet point access below is allowed
+ const B3DPolygon aFirst(pGapTarget->getB3DPolygon(0));
+ B3DPolygon aLast(pGapTarget->getB3DPolygon(nCount - 1));
+
+ if(aFirst.getB3DPoint(0).equal(aLast.getB3DPoint(aLast.count() - 1)))
+ {
+ // start of first and end of last are the same -> merge them
+ aLast.append(aFirst);
+ aLast.removeDoublePoints();
+ pGapTarget->setB3DPolygon(0, aLast);
+ pGapTarget->remove(nCount - 1);
+ }
+ }
+ }
+ }
+ else
+ {
+ // parameters make no sense, just add source to targets
+ if(pLineTarget)
+ {
+ pLineTarget->append(rCandidate);
+ }
+
+ if(pGapTarget)
+ {
+ pGapTarget->append(rCandidate);
+ }
+ }
+ }
+
+ B3DPolygon applyDefaultNormalsSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter)
+ {
+ B3DPolygon aRetval(rCandidate);
+
+ for(sal_uInt32 a(0L); a < aRetval.count(); a++)
+ {
+ B3DVector aVector(aRetval.getB3DPoint(a) - rCenter);
+ aVector.normalize();
+ aRetval.setNormal(a, aVector);
+ }
+
+ return aRetval;
+ }
+
+ B3DPolygon invertNormals( const B3DPolygon& rCandidate)
+ {
+ B3DPolygon aRetval(rCandidate);
+
+ if(aRetval.areNormalsUsed())
+ {
+ for(sal_uInt32 a(0L); a < aRetval.count(); a++)
+ {
+ aRetval.setNormal(a, -aRetval.getNormal(a));
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPolygon applyDefaultTextureCoordinatesParallel( const B3DPolygon& rCandidate, const B3DRange& rRange, bool bChangeX, bool bChangeY)
+ {
+ B3DPolygon aRetval(rCandidate);
+
+ if(bChangeX || bChangeY)
+ {
+ // create projection of standard texture coordinates in (X, Y) onto
+ // the 3d coordinates straight
+ const double fWidth(rRange.getWidth());
+ const double fHeight(rRange.getHeight());
+ const bool bWidthSet(!fTools::equalZero(fWidth));
+ const bool bHeightSet(!fTools::equalZero(fHeight));
+ const double fOne(1.0);
+
+ for(sal_uInt32 a(0L); a < aRetval.count(); a++)
+ {
+ const B3DPoint aPoint(aRetval.getB3DPoint(a));
+ B2DPoint aTextureCoordinate(aRetval.getTextureCoordinate(a));
+
+ if(bChangeX)
+ {
+ if(bWidthSet)
+ {
+ aTextureCoordinate.setX((aPoint.getX() - rRange.getMinX()) / fWidth);
+ }
+ else
+ {
+ aTextureCoordinate.setX(0.0);
+ }
+ }
+
+ if(bChangeY)
+ {
+ if(bHeightSet)
+ {
+ aTextureCoordinate.setY(fOne - ((aPoint.getY() - rRange.getMinY()) / fHeight));
+ }
+ else
+ {
+ aTextureCoordinate.setY(fOne);
+ }
+ }
+
+ aRetval.setTextureCoordinate(a, aTextureCoordinate);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPolygon applyDefaultTextureCoordinatesSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter, bool bChangeX, bool bChangeY)
+ {
+ B3DPolygon aRetval(rCandidate);
+
+ if(bChangeX || bChangeY)
+ {
+ // create texture coordinates using sphere projection to cartesian coordinates,
+ // use object's center as base
+ const double fOne(1.0);
+ const sal_uInt32 nPointCount(aRetval.count());
+ bool bPolarPoints(false);
+ sal_uInt32 a;
+
+ // create center cartesian coordinates to have a possibility to decide if on boundary
+ // transitions which value to choose
+ const B3DRange aPlaneRange(getRange(rCandidate));
+ const B3DPoint aPlaneCenter(aPlaneRange.getCenter() - rCenter);
+ const double fXCenter(fOne - ((atan2(aPlaneCenter.getZ(), aPlaneCenter.getX()) + F_PI) / F_2PI));
+
+ for(a = 0L; a < nPointCount; a++)
+ {
+ const B3DVector aVector(aRetval.getB3DPoint(a) - rCenter);
+ const double fY(fOne - ((atan2(aVector.getY(), aVector.getXZLength()) + F_PI2) / F_PI));
+ B2DPoint aTexCoor(aRetval.getTextureCoordinate(a));
+
+ if(fTools::equalZero(fY))
+ {
+ // point is a north polar point, no useful X-coordinate can be created.
+ if(bChangeY)
+ {
+ aTexCoor.setY(0.0);
+
+ if(bChangeX)
+ {
+ bPolarPoints = true;
+ }
+ }
+ }
+ else if(fTools::equal(fY, fOne))
+ {
+ // point is a south polar point, no useful X-coordinate can be created. Set
+ // Y-coordinte, though
+ if(bChangeY)
+ {
+ aTexCoor.setY(fOne);
+
+ if(bChangeX)
+ {
+ bPolarPoints = true;
+ }
+ }
+ }
+ else
+ {
+ double fX(fOne - ((atan2(aVector.getZ(), aVector.getX()) + F_PI) / F_2PI));
+
+ // correct cartesinan point coordiante dependent from center value
+ if(fX > fXCenter + 0.5)
+ {
+ fX -= fOne;
+ }
+ else if(fX < fXCenter - 0.5)
+ {
+ fX += fOne;
+ }
+
+ if(bChangeX)
+ {
+ aTexCoor.setX(fX);
+ }
+
+ if(bChangeY)
+ {
+ aTexCoor.setY(fY);
+ }
+ }
+
+ aRetval.setTextureCoordinate(a, aTexCoor);
+ }
+
+ if(bPolarPoints)
+ {
+ // correct X-texture coordinates if polar points are contained. Those
+ // coordinates cannot be correct, so use prev or next X-coordinate
+ for(a = 0L; a < nPointCount; a++)
+ {
+ B2DPoint aTexCoor(aRetval.getTextureCoordinate(a));
+
+ if(fTools::equalZero(aTexCoor.getY()) || fTools::equal(aTexCoor.getY(), fOne))
+ {
+ // get prev, next TexCoor and test for pole
+ const B2DPoint aPrevTexCoor(aRetval.getTextureCoordinate(a ? a - 1L : nPointCount - 1L));
+ const B2DPoint aNextTexCoor(aRetval.getTextureCoordinate((a + 1L) % nPointCount));
+ const bool bPrevPole(fTools::equalZero(aPrevTexCoor.getY()) || fTools::equal(aPrevTexCoor.getY(), fOne));
+ const bool bNextPole(fTools::equalZero(aNextTexCoor.getY()) || fTools::equal(aNextTexCoor.getY(), fOne));
+
+ if(!bPrevPole && !bNextPole)
+ {
+ // both no poles, mix them
+ aTexCoor.setX((aPrevTexCoor.getX() + aNextTexCoor.getX()) / 2.0);
+ }
+ else if(!bNextPole)
+ {
+ // copy next
+ aTexCoor.setX(aNextTexCoor.getX());
+ }
+ else
+ {
+ // copy prev, even if it's a pole, hopefully it is already corrected
+ aTexCoor.setX(aPrevTexCoor.getX());
+ }
+
+ aRetval.setTextureCoordinate(a, aTexCoor);
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ bool isInEpsilonRange(const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, const B3DPoint& rTestPosition, double fDistance)
+ {
+ // build edge vector
+ const B3DVector aEdge(rEdgeEnd - rEdgeStart);
+ bool bDoDistanceTestStart(false);
+ bool bDoDistanceTestEnd(false);
+
+ if(aEdge.equalZero())
+ {
+ // no edge, just a point. Do one of the distance tests.
+ bDoDistanceTestStart = true;
+ }
+ else
+ {
+ // calculate fCut in aEdge
+ const B3DVector aTestEdge(rTestPosition - rEdgeStart);
+ const double fScalarTestEdge(aEdge.scalar(aTestEdge));
+ const double fScalarStartEdge(aEdge.scalar(rEdgeStart));
+ const double fScalarEdge(aEdge.scalar(aEdge));
+ const double fCut((fScalarTestEdge - fScalarStartEdge) / fScalarEdge);
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ if(fTools::less(fCut, fZero))
+ {
+ // left of rEdgeStart
+ bDoDistanceTestStart = true;
+ }
+ else if(fTools::more(fCut, fOne))
+ {
+ // right of rEdgeEnd
+ bDoDistanceTestEnd = true;
+ }
+ else
+ {
+ // inside line [0.0 .. 1.0]
+ const B3DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut));
+ const B3DVector aDelta(rTestPosition - aCutPoint);
+ const double fDistanceSquare(aDelta.scalar(aDelta));
+
+ if(fDistanceSquare <= fDistance * fDistance * fDistance)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ if(bDoDistanceTestStart)
+ {
+ const B3DVector aDelta(rTestPosition - rEdgeStart);
+ const double fDistanceSquare(aDelta.scalar(aDelta));
+
+ if(fDistanceSquare <= fDistance * fDistance * fDistance)
+ {
+ return true;
+ }
+ }
+ else if(bDoDistanceTestEnd)
+ {
+ const B3DVector aDelta(rTestPosition - rEdgeEnd);
+ const double fDistanceSquare(aDelta.scalar(aDelta));
+
+ if(fDistanceSquare <= fDistance * fDistance * fDistance)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool isInEpsilonRange(const B3DPolygon& rCandidate, const B3DPoint& rTestPosition, double fDistance)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+ B3DPoint aCurrent(rCandidate.getB3DPoint(0));
+
+ if(nEdgeCount)
+ {
+ // edges
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B3DPoint aNext(rCandidate.getB3DPoint(nNextIndex));
+
+ 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;
+ }
+
+ bool isInside(const B3DPolygon& rCandidate, const B3DPoint& rPoint, bool bWithBorder)
+ {
+ if(bWithBorder && isPointOnPolygon(rCandidate, rPoint, true))
+ {
+ return true;
+ }
+ else
+ {
+ bool bRetval(false);
+ const B3DVector aPlaneNormal(rCandidate.getNormal());
+
+ if(!aPlaneNormal.equalZero())
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ B3DPoint aCurrentPoint(rCandidate.getB3DPoint(nPointCount - 1));
+ const double fAbsX(fabs(aPlaneNormal.getX()));
+ const double fAbsY(fabs(aPlaneNormal.getY()));
+ const double fAbsZ(fabs(aPlaneNormal.getZ()));
+
+ if(fAbsX > fAbsY && fAbsX > fAbsZ)
+ {
+ // normal points mostly in X-Direction, use YZ-Polygon projection for check
+ // x -> y, y -> z
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B3DPoint aPreviousPoint(aCurrentPoint);
+ aCurrentPoint = rCandidate.getB3DPoint(a);
+
+ // cross-over in Z?
+ const bool bCompZA(fTools::more(aPreviousPoint.getZ(), rPoint.getZ()));
+ const bool bCompZB(fTools::more(aCurrentPoint.getZ(), rPoint.getZ()));
+
+ if(bCompZA != bCompZB)
+ {
+ // cross-over in Y?
+ const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY()));
+ const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY()));
+
+ if(bCompYA == bCompYB)
+ {
+ if(bCompYA)
+ {
+ bRetval = !bRetval;
+ }
+ }
+ else
+ {
+ const double fCompare(
+ aCurrentPoint.getY() - (aCurrentPoint.getZ() - rPoint.getZ()) *
+ (aPreviousPoint.getY() - aCurrentPoint.getY()) /
+ (aPreviousPoint.getZ() - aCurrentPoint.getZ()));
+
+ if(fTools::more(fCompare, rPoint.getY()))
+ {
+ bRetval = !bRetval;
+ }
+ }
+ }
+ }
+ }
+ else if(fAbsY > fAbsX && fAbsY > fAbsZ)
+ {
+ // normal points mostly in Y-Direction, use XZ-Polygon projection for check
+ // x -> x, y -> z
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B3DPoint aPreviousPoint(aCurrentPoint);
+ aCurrentPoint = rCandidate.getB3DPoint(a);
+
+ // cross-over in Z?
+ const bool bCompZA(fTools::more(aPreviousPoint.getZ(), rPoint.getZ()));
+ const bool bCompZB(fTools::more(aCurrentPoint.getZ(), rPoint.getZ()));
+
+ if(bCompZA != bCompZB)
+ {
+ // cross-over in X?
+ const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX()));
+ const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX()));
+
+ if(bCompXA == bCompXB)
+ {
+ if(bCompXA)
+ {
+ bRetval = !bRetval;
+ }
+ }
+ else
+ {
+ const double fCompare(
+ aCurrentPoint.getX() - (aCurrentPoint.getZ() - rPoint.getZ()) *
+ (aPreviousPoint.getX() - aCurrentPoint.getX()) /
+ (aPreviousPoint.getZ() - aCurrentPoint.getZ()));
+
+ if(fTools::more(fCompare, rPoint.getX()))
+ {
+ bRetval = !bRetval;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // normal points mostly in Z-Direction, use XY-Polygon projection for check
+ // x -> x, y -> y
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B3DPoint aPreviousPoint(aCurrentPoint);
+ aCurrentPoint = rCandidate.getB3DPoint(a);
+
+ // cross-over in Y?
+ const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY()));
+ const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY()));
+
+ if(bCompYA != bCompYB)
+ {
+ // cross-over in X?
+ const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX()));
+ const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX()));
+
+ if(bCompXA == bCompXB)
+ {
+ if(bCompXA)
+ {
+ bRetval = !bRetval;
+ }
+ }
+ else
+ {
+ const double fCompare(
+ aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) *
+ (aPreviousPoint.getX() - aCurrentPoint.getX()) /
+ (aPreviousPoint.getY() - aCurrentPoint.getY()));
+
+ if(fTools::more(fCompare, rPoint.getX()))
+ {
+ bRetval = !bRetval;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return bRetval;
+ }
+ }
+
+ bool isInside(const B3DPolygon& rCandidate, const B3DPolygon& rPolygon, bool bWithBorder)
+ {
+ const sal_uInt32 nPointCount(rPolygon.count());
+
+ for(sal_uInt32 a(0L); a < nPointCount; a++)
+ {
+ const B3DPoint aTestPoint(rPolygon.getB3DPoint(a));
+
+ if(!isInside(rCandidate, aTestPoint, bWithBorder))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool isPointOnLine(const B3DPoint& rStart, const B3DPoint& rEnd, const B3DPoint& rCandidate, bool bWithPoints)
+ {
+ if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
+ {
+ // candidate is in epsilon around start or end -> inside
+ return bWithPoints;
+ }
+ else if(rStart.equal(rEnd))
+ {
+ // start and end are equal, but candidate is outside their epsilon -> outside
+ return false;
+ }
+ else
+ {
+ const B3DVector aEdgeVector(rEnd - rStart);
+ const B3DVector aTestVector(rCandidate - rStart);
+
+ if(areParallel(aEdgeVector, aTestVector))
+ {
+ const double fZero(0.0);
+ const double fOne(1.0);
+ double fParamTestOnCurr(0.0);
+
+ if(aEdgeVector.getX() > aEdgeVector.getY())
+ {
+ if(aEdgeVector.getX() > aEdgeVector.getZ())
+ {
+ // X is biggest
+ fParamTestOnCurr = aTestVector.getX() / aEdgeVector.getX();
+ }
+ else
+ {
+ // Z is biggest
+ fParamTestOnCurr = aTestVector.getZ() / aEdgeVector.getZ();
+ }
+ }
+ else
+ {
+ if(aEdgeVector.getY() > aEdgeVector.getZ())
+ {
+ // Y is biggest
+ fParamTestOnCurr = aTestVector.getY() / aEdgeVector.getY();
+ }
+ else
+ {
+ // Z is biggest
+ fParamTestOnCurr = aTestVector.getZ() / aEdgeVector.getZ();
+ }
+ }
+
+ if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ bool isPointOnPolygon(const B3DPolygon& rCandidate, const B3DPoint& rPoint, bool bWithPoints)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1L)
+ {
+ const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L);
+ B3DPoint aCurrentPoint(rCandidate.getB3DPoint(0));
+
+ for(sal_uInt32 a(0); a < nLoopCount; a++)
+ {
+ const B3DPoint aNextPoint(rCandidate.getB3DPoint((a + 1) % nPointCount));
+
+ if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints))
+ {
+ return true;
+ }
+
+ aCurrentPoint = aNextPoint;
+ }
+ }
+ else if(nPointCount && bWithPoints)
+ {
+ return rPoint.equal(rCandidate.getB3DPoint(0));
+ }
+
+ return false;
+ }
+
+ bool getCutBetweenLineAndPlane(const B3DVector& rPlaneNormal, const B3DPoint& rPlanePoint, const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, double& fCut)
+ {
+ if(!rPlaneNormal.equalZero() && !rEdgeStart.equal(rEdgeEnd))
+ {
+ const B3DVector aTestEdge(rEdgeEnd - rEdgeStart);
+ const double fScalarEdge(rPlaneNormal.scalar(aTestEdge));
+
+ if(!fTools::equalZero(fScalarEdge))
+ {
+ const B3DVector aCompareEdge(rPlanePoint - rEdgeStart);
+ const double fScalarCompare(rPlaneNormal.scalar(aCompareEdge));
+
+ fCut = fScalarCompare / fScalarEdge;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool getCutBetweenLineAndPolygon(const B3DPolygon& rCandidate, const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, double& fCut)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 2 && !rEdgeStart.equal(rEdgeEnd))
+ {
+ const B3DVector aPlaneNormal(rCandidate.getNormal());
+
+ if(!aPlaneNormal.equalZero())
+ {
+ const B3DPoint aPointOnPlane(rCandidate.getB3DPoint(0));
+
+ return getCutBetweenLineAndPlane(aPlaneNormal, aPointOnPlane, rEdgeStart, rEdgeEnd, fCut);
+ }
+ }
+
+ return false;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ // comparators with tolerance for 3D Polygons
+
+ bool equal(const B3DPolygon& rCandidateA, const B3DPolygon& rCandidateB, const double& rfSmallValue)
+ {
+ const sal_uInt32 nPointCount(rCandidateA.count());
+
+ if(nPointCount != rCandidateB.count())
+ return false;
+
+ const bool bClosed(rCandidateA.isClosed());
+
+ if(bClosed != rCandidateB.isClosed())
+ return false;
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B3DPoint aPoint(rCandidateA.getB3DPoint(a));
+
+ if(!aPoint.equal(rCandidateB.getB3DPoint(a), rfSmallValue))
+ return false;
+ }
+
+ return true;
+ }
+
+ bool equal(const B3DPolygon& rCandidateA, const B3DPolygon& rCandidateB)
+ {
+ const double fSmallValue(fTools::getSmallValue());
+
+ return equal(rCandidateA, rCandidateB, fSmallValue);
+ }
+
+ // snap points of horizontal or vertical edges to discrete values
+ B3DPolygon snapPointsOfHorizontalOrVerticalEdges(const B3DPolygon& rCandidate)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1)
+ {
+ // Start by copying the source polygon to get a writeable copy. The closed state is
+ // copied by aRetval's initialisation, too, so no need to copy it in this method
+ B3DPolygon aRetval(rCandidate);
+
+ // prepare geometry data. Get rounded from original
+ B3ITuple aPrevTuple(basegfx::fround(rCandidate.getB3DPoint(nPointCount - 1)));
+ B3DPoint aCurrPoint(rCandidate.getB3DPoint(0));
+ B3ITuple aCurrTuple(basegfx::fround(aCurrPoint));
+
+ // loop over all points. This will also snap the implicit closing edge
+ // even when not closed, but that's no problem here
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ // get next point. Get rounded from original
+ const bool bLastRun(a + 1 == nPointCount);
+ const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
+ const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
+ const B3ITuple aNextTuple(basegfx::fround(aNextPoint));
+
+ // get the states
+ const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
+ const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
+ const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
+ const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
+ const bool bSnapX(bPrevVertical || bNextVertical);
+ const bool bSnapY(bPrevHorizontal || bNextHorizontal);
+
+ if(bSnapX || bSnapY)
+ {
+ const B3DPoint aSnappedPoint(
+ bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
+ bSnapY ? aCurrTuple.getY() : aCurrPoint.getY(),
+ aCurrPoint.getZ());
+
+ aRetval.setB3DPoint(a, aSnappedPoint);
+ }
+
+ // prepare next point
+ if(!bLastRun)
+ {
+ aPrevTuple = aCurrTuple;
+ aCurrPoint = aNextPoint;
+ aCurrTuple = aNextTuple;
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ } // end of namespace tools
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+// eof
diff --git a/basegfx/source/polygon/b3dpolypolygon.cxx b/basegfx/source/polygon/b3dpolypolygon.cxx
new file mode 100644
index 000000000000..a29680b14a59
--- /dev/null
+++ b/basegfx/source/polygon/b3dpolypolygon.cxx
@@ -0,0 +1,446 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <osl/diagnose.h>
+#include <basegfx/polygon/b3dpolypolygon.hxx>
+#include <basegfx/polygon/b3dpolygon.hxx>
+#include <rtl/instance.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <functional>
+#include <vector>
+#include <algorithm>
+
+//////////////////////////////////////////////////////////////////////////////
+
+class ImplB3DPolyPolygon
+{
+ typedef ::std::vector< ::basegfx::B3DPolygon > PolygonVector;
+
+ PolygonVector maPolygons;
+
+public:
+ ImplB3DPolyPolygon() : maPolygons()
+ {
+ }
+
+ ImplB3DPolyPolygon(const ::basegfx::B3DPolygon& rToBeCopied) :
+ maPolygons(1,rToBeCopied)
+ {
+ }
+
+ bool operator==(const ImplB3DPolyPolygon& rPolygonList) const
+ {
+ // same polygon count?
+ if(maPolygons.size() != rPolygonList.maPolygons.size())
+ return false;
+
+ // compare polygon content
+ if(maPolygons != rPolygonList.maPolygons)
+ return false;
+
+ return true;
+ }
+
+ const ::basegfx::B3DPolygon& getB3DPolygon(sal_uInt32 nIndex) const
+ {
+ return maPolygons[nIndex];
+ }
+
+ void setB3DPolygon(sal_uInt32 nIndex, const ::basegfx::B3DPolygon& rPolygon)
+ {
+ maPolygons[nIndex] = rPolygon;
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::B3DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rPolygon
+ PolygonVector::iterator aIndex(maPolygons.begin());
+ aIndex += nIndex;
+ maPolygons.insert(aIndex, nCount, rPolygon);
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::B3DPolyPolygon& rPolyPolygon)
+ {
+ const sal_uInt32 nCount = rPolyPolygon.count();
+
+ if(nCount)
+ {
+ // add nCount polygons from rPolyPolygon
+ maPolygons.reserve(maPolygons.size() + nCount);
+ PolygonVector::iterator aIndex(maPolygons.begin());
+ aIndex += nIndex;
+
+ for(sal_uInt32 a(0L); a < nCount; a++)
+ {
+ maPolygons.insert(aIndex, rPolyPolygon.getB3DPolygon(a));
+ aIndex++;
+ }
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // remove polygon data
+ PolygonVector::iterator aStart(maPolygons.begin());
+ aStart += nIndex;
+ const PolygonVector::iterator aEnd(aStart + nCount);
+
+ maPolygons.erase(aStart, aEnd);
+ }
+ }
+
+ sal_uInt32 count() const
+ {
+ return maPolygons.size();
+ }
+
+ void setClosed(bool bNew)
+ {
+ for(sal_uInt32 a(0L); a < maPolygons.size(); a++)
+ {
+ maPolygons[a].setClosed(bNew);
+ }
+ }
+
+ void flip()
+ {
+ std::for_each( maPolygons.begin(),
+ maPolygons.end(),
+ std::mem_fun_ref( &::basegfx::B3DPolygon::flip ));
+ }
+
+ void removeDoublePoints()
+ {
+ std::for_each( maPolygons.begin(),
+ maPolygons.end(),
+ std::mem_fun_ref( &::basegfx::B3DPolygon::removeDoublePoints ));
+ }
+
+ void transform(const ::basegfx::B3DHomMatrix& rMatrix)
+ {
+ for(sal_uInt32 a(0L); a < maPolygons.size(); a++)
+ {
+ maPolygons[a].transform(rMatrix);
+ }
+ }
+
+ void clearBColors()
+ {
+ for(sal_uInt32 a(0L); a < maPolygons.size(); a++)
+ {
+ maPolygons[a].clearBColors();
+ }
+ }
+
+ void transformNormals(const ::basegfx::B3DHomMatrix& rMatrix)
+ {
+ for(sal_uInt32 a(0L); a < maPolygons.size(); a++)
+ {
+ maPolygons[a].transformNormals(rMatrix);
+ }
+ }
+
+ void clearNormals()
+ {
+ for(sal_uInt32 a(0L); a < maPolygons.size(); a++)
+ {
+ maPolygons[a].clearNormals();
+ }
+ }
+
+ void transformTextureCoordiantes(const ::basegfx::B2DHomMatrix& rMatrix)
+ {
+ for(sal_uInt32 a(0L); a < maPolygons.size(); a++)
+ {
+ maPolygons[a].transformTextureCoordiantes(rMatrix);
+ }
+ }
+
+ void clearTextureCoordinates()
+ {
+ for(sal_uInt32 a(0L); a < maPolygons.size(); a++)
+ {
+ maPolygons[a].clearTextureCoordinates();
+ }
+ }
+
+ void makeUnique()
+ {
+ std::for_each( maPolygons.begin(),
+ maPolygons.end(),
+ std::mem_fun_ref( &::basegfx::B3DPolygon::makeUnique ));
+ }
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace { struct DefaultPolyPolygon : public rtl::Static<B3DPolyPolygon::ImplType,
+ DefaultPolyPolygon> {}; }
+
+ B3DPolyPolygon::B3DPolyPolygon() :
+ mpPolyPolygon(DefaultPolyPolygon::get())
+ {
+ }
+
+ B3DPolyPolygon::B3DPolyPolygon(const B3DPolyPolygon& rPolyPolygon) :
+ mpPolyPolygon(rPolyPolygon.mpPolyPolygon)
+ {
+ }
+
+ B3DPolyPolygon::B3DPolyPolygon(const B3DPolygon& rPolygon) :
+ mpPolyPolygon( ImplB3DPolyPolygon(rPolygon) )
+ {
+ }
+
+ B3DPolyPolygon::~B3DPolyPolygon()
+ {
+ }
+
+ B3DPolyPolygon& B3DPolyPolygon::operator=(const B3DPolyPolygon& rPolyPolygon)
+ {
+ mpPolyPolygon = rPolyPolygon.mpPolyPolygon;
+ return *this;
+ }
+
+ void B3DPolyPolygon::makeUnique()
+ {
+ mpPolyPolygon.make_unique();
+ mpPolyPolygon->makeUnique();
+ }
+
+ bool B3DPolyPolygon::operator==(const B3DPolyPolygon& rPolyPolygon) const
+ {
+ if(mpPolyPolygon.same_object(rPolyPolygon.mpPolyPolygon))
+ return true;
+
+ return ((*mpPolyPolygon) == (*rPolyPolygon.mpPolyPolygon));
+ }
+
+ bool B3DPolyPolygon::operator!=(const B3DPolyPolygon& rPolyPolygon) const
+ {
+ return !(*this == rPolyPolygon);
+ }
+
+ sal_uInt32 B3DPolyPolygon::count() const
+ {
+ return mpPolyPolygon->count();
+ }
+
+ B3DPolygon B3DPolyPolygon::getB3DPolygon(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolyPolygon->count(), "B3DPolyPolygon access outside range (!)");
+
+ return mpPolyPolygon->getB3DPolygon(nIndex);
+ }
+
+ void B3DPolyPolygon::setB3DPolygon(sal_uInt32 nIndex, const B3DPolygon& rPolygon)
+ {
+ OSL_ENSURE(nIndex < mpPolyPolygon->count(), "B3DPolyPolygon access outside range (!)");
+
+ if(getB3DPolygon(nIndex) != rPolygon)
+ mpPolyPolygon->setB3DPolygon(nIndex, rPolygon);
+ }
+
+ bool B3DPolyPolygon::areBColorsUsed() const
+ {
+ for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++)
+ {
+ if((mpPolyPolygon->getB3DPolygon(a)).areBColorsUsed())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void B3DPolyPolygon::clearBColors()
+ {
+ if(areBColorsUsed())
+ mpPolyPolygon->clearBColors();
+ }
+
+ void B3DPolyPolygon::transformNormals(const B3DHomMatrix& rMatrix)
+ {
+ if(!rMatrix.isIdentity())
+ mpPolyPolygon->transformNormals(rMatrix);
+ }
+
+ bool B3DPolyPolygon::areNormalsUsed() const
+ {
+ for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++)
+ {
+ if((mpPolyPolygon->getB3DPolygon(a)).areNormalsUsed())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void B3DPolyPolygon::clearNormals()
+ {
+ if(areNormalsUsed())
+ mpPolyPolygon->clearNormals();
+ }
+
+ void B3DPolyPolygon::transformTextureCoordiantes(const B2DHomMatrix& rMatrix)
+ {
+ if(!rMatrix.isIdentity())
+ mpPolyPolygon->transformTextureCoordiantes(rMatrix);
+ }
+
+ bool B3DPolyPolygon::areTextureCoordinatesUsed() const
+ {
+ for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++)
+ {
+ if((mpPolyPolygon->getB3DPolygon(a)).areTextureCoordinatesUsed())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void B3DPolyPolygon::clearTextureCoordinates()
+ {
+ if(areTextureCoordinatesUsed())
+ mpPolyPolygon->clearTextureCoordinates();
+ }
+
+ void B3DPolyPolygon::insert(sal_uInt32 nIndex, const B3DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex <= mpPolyPolygon->count(), "B3DPolyPolygon Insert outside range (!)");
+
+ if(nCount)
+ mpPolyPolygon->insert(nIndex, rPolygon, nCount);
+ }
+
+ void B3DPolyPolygon::append(const B3DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ if(nCount)
+ mpPolyPolygon->insert(mpPolyPolygon->count(), rPolygon, nCount);
+ }
+
+ void B3DPolyPolygon::insert(sal_uInt32 nIndex, const B3DPolyPolygon& rPolyPolygon)
+ {
+ OSL_ENSURE(nIndex <= mpPolyPolygon->count(), "B3DPolyPolygon Insert outside range (!)");
+
+ if(rPolyPolygon.count())
+ mpPolyPolygon->insert(nIndex, rPolyPolygon);
+ }
+
+ void B3DPolyPolygon::append(const B3DPolyPolygon& rPolyPolygon)
+ {
+ if(rPolyPolygon.count())
+ mpPolyPolygon->insert(mpPolyPolygon->count(), rPolyPolygon);
+ }
+
+ void B3DPolyPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex + nCount <= mpPolyPolygon->count(), "B3DPolyPolygon Remove outside range (!)");
+
+ if(nCount)
+ mpPolyPolygon->remove(nIndex, nCount);
+ }
+
+ void B3DPolyPolygon::clear()
+ {
+ mpPolyPolygon = DefaultPolyPolygon::get();
+ }
+
+ bool B3DPolyPolygon::isClosed() const
+ {
+ bool bRetval(true);
+
+ // PolyPOlygon is closed when all contained Polygons are closed or
+ // no Polygon exists.
+ for(sal_uInt32 a(0L); bRetval && a < mpPolyPolygon->count(); a++)
+ {
+ if(!(mpPolyPolygon->getB3DPolygon(a)).isClosed())
+ {
+ bRetval = false;
+ }
+ }
+
+ return bRetval;
+ }
+
+ void B3DPolyPolygon::setClosed(bool bNew)
+ {
+ if(bNew != isClosed())
+ mpPolyPolygon->setClosed(bNew);
+ }
+
+ void B3DPolyPolygon::flip()
+ {
+ mpPolyPolygon->flip();
+ }
+
+ bool B3DPolyPolygon::hasDoublePoints() const
+ {
+ bool bRetval(false);
+
+ for(sal_uInt32 a(0L); !bRetval && a < mpPolyPolygon->count(); a++)
+ {
+ if((mpPolyPolygon->getB3DPolygon(a)).hasDoublePoints())
+ {
+ bRetval = true;
+ }
+ }
+
+ return bRetval;
+ }
+
+ void B3DPolyPolygon::removeDoublePoints()
+ {
+ if(hasDoublePoints())
+ mpPolyPolygon->removeDoublePoints();
+ }
+
+ void B3DPolyPolygon::transform(const B3DHomMatrix& rMatrix)
+ {
+ if(mpPolyPolygon->count() && !rMatrix.isIdentity())
+ {
+ mpPolyPolygon->transform(rMatrix);
+ }
+ }
+} // end of namespace basegfx
+
+// eof
diff --git a/basegfx/source/polygon/b3dpolypolygontools.cxx b/basegfx/source/polygon/b3dpolypolygontools.cxx
new file mode 100644
index 000000000000..d86a4526acfd
--- /dev/null
+++ b/basegfx/source/polygon/b3dpolypolygontools.cxx
@@ -0,0 +1,556 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_basegfx.hxx"
+#include <basegfx/polygon/b3dpolypolygontools.hxx>
+#include <basegfx/range/b3drange.hxx>
+#include <basegfx/polygon/b3dpolypolygon.hxx>
+#include <basegfx/polygon/b3dpolygon.hxx>
+#include <basegfx/polygon/b3dpolygontools.hxx>
+#include <numeric>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <osl/mutex.hxx>
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace basegfx
+{
+ namespace tools
+ {
+ // B3DPolyPolygon tools
+ B3DRange getRange(const B3DPolyPolygon& rCandidate)
+ {
+ B3DRange aRetval;
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+
+ for(sal_uInt32 a(0L); a < nPolygonCount; a++)
+ {
+ B3DPolygon aCandidate = rCandidate.getB3DPolygon(a);
+ aRetval.expand(getRange(aCandidate));
+ }
+
+ return aRetval;
+ }
+
+ void applyLineDashing(const B3DPolyPolygon& rCandidate, const ::std::vector<double>& rDotDashArray, B3DPolyPolygon* pLineTarget, B3DPolyPolygon* pGapTarget, double fFullDashDotLen)
+ {
+ if(0.0 == fFullDashDotLen && rDotDashArray.size())
+ {
+ // calculate fFullDashDotLen from rDotDashArray
+ fFullDashDotLen = ::std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
+ }
+
+ if(rCandidate.count() && fFullDashDotLen > 0.0)
+ {
+ B3DPolyPolygon aLineTarget, aGapTarget;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ const B3DPolygon aCandidate(rCandidate.getB3DPolygon(a));
+
+ applyLineDashing(
+ aCandidate,
+ rDotDashArray,
+ pLineTarget ? &aLineTarget : 0,
+ pGapTarget ? &aGapTarget : 0,
+ fFullDashDotLen);
+
+ if(pLineTarget)
+ {
+ pLineTarget->append(aLineTarget);
+ }
+
+ if(pGapTarget)
+ {
+ pGapTarget->append(aGapTarget);
+ }
+ }
+ }
+ }
+
+ B3DPolyPolygon createUnitCubePolyPolygon()
+ {
+ static B3DPolyPolygon aRetval;
+ ::osl::Mutex m_mutex;
+
+ if(!aRetval.count())
+ {
+ B3DPolygon aTemp;
+ aTemp.append(B3DPoint(0.0, 0.0, 1.0));
+ aTemp.append(B3DPoint(0.0, 1.0, 1.0));
+ aTemp.append(B3DPoint(1.0, 1.0, 1.0));
+ aTemp.append(B3DPoint(1.0, 0.0, 1.0));
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ aTemp.clear();
+ aTemp.append(B3DPoint(0.0, 0.0, 0.0));
+ aTemp.append(B3DPoint(0.0, 1.0, 0.0));
+ aTemp.append(B3DPoint(1.0, 1.0, 0.0));
+ aTemp.append(B3DPoint(1.0, 0.0, 0.0));
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ aTemp.clear();
+ aTemp.append(B3DPoint(0.0, 0.0, 0.0));
+ aTemp.append(B3DPoint(0.0, 0.0, 1.0));
+ aRetval.append(aTemp);
+
+ aTemp.clear();
+ aTemp.append(B3DPoint(0.0, 1.0, 0.0));
+ aTemp.append(B3DPoint(0.0, 1.0, 1.0));
+ aRetval.append(aTemp);
+
+ aTemp.clear();
+ aTemp.append(B3DPoint(1.0, 1.0, 0.0));
+ aTemp.append(B3DPoint(1.0, 1.0, 1.0));
+ aRetval.append(aTemp);
+
+ aTemp.clear();
+ aTemp.append(B3DPoint(1.0, 0.0, 0.0));
+ aTemp.append(B3DPoint(1.0, 0.0, 1.0));
+ aRetval.append(aTemp);
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon createUnitCubeFillPolyPolygon()
+ {
+ static B3DPolyPolygon aRetval;
+ ::osl::Mutex m_mutex;
+
+ if(!aRetval.count())
+ {
+ B3DPolygon aTemp;
+
+ // all points
+ const B3DPoint A(0.0, 0.0, 0.0);
+ const B3DPoint B(0.0, 1.0, 0.0);
+ const B3DPoint C(1.0, 1.0, 0.0);
+ const B3DPoint D(1.0, 0.0, 0.0);
+ const B3DPoint E(0.0, 0.0, 1.0);
+ const B3DPoint F(0.0, 1.0, 1.0);
+ const B3DPoint G(1.0, 1.0, 1.0);
+ const B3DPoint H(1.0, 0.0, 1.0);
+
+ // create bottom
+ aTemp.append(D);
+ aTemp.append(A);
+ aTemp.append(E);
+ aTemp.append(H);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ // create front
+ aTemp.clear();
+ aTemp.append(B);
+ aTemp.append(A);
+ aTemp.append(D);
+ aTemp.append(C);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ // create left
+ aTemp.clear();
+ aTemp.append(E);
+ aTemp.append(A);
+ aTemp.append(B);
+ aTemp.append(F);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ // create top
+ aTemp.clear();
+ aTemp.append(C);
+ aTemp.append(G);
+ aTemp.append(F);
+ aTemp.append(B);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ // create right
+ aTemp.clear();
+ aTemp.append(H);
+ aTemp.append(G);
+ aTemp.append(C);
+ aTemp.append(D);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ // create back
+ aTemp.clear();
+ aTemp.append(F);
+ aTemp.append(G);
+ aTemp.append(H);
+ aTemp.append(E);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon createCubePolyPolygonFromB3DRange( const B3DRange& rRange)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(!rRange.isEmpty())
+ {
+ aRetval = createUnitCubePolyPolygon();
+ B3DHomMatrix aTrans;
+ aTrans.scale(rRange.getWidth(), rRange.getHeight(), rRange.getDepth());
+ aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ());
+ aRetval.transform(aTrans);
+ aRetval.removeDoublePoints();
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon createCubeFillPolyPolygonFromB3DRange( const B3DRange& rRange)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(!rRange.isEmpty())
+ {
+ aRetval = createUnitCubeFillPolyPolygon();
+ B3DHomMatrix aTrans;
+ aTrans.scale(rRange.getWidth(), rRange.getHeight(), rRange.getDepth());
+ aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ());
+ aRetval.transform(aTrans);
+ aRetval.removeDoublePoints();
+ }
+
+ return aRetval;
+ }
+
+ // helper for getting the 3D Point from given cartesian coordiantes. fVer is defined from
+ // [F_PI2 .. -F_PI2], fHor from [0.0 .. F_2PI]
+ inline B3DPoint getPointFromCartesian(double fVer, double fHor)
+ {
+ const double fCosHor(cos(fHor));
+ return B3DPoint(fCosHor * cos(fVer), sin(fHor), fCosHor * -sin(fVer));
+ }
+
+ B3DPolyPolygon createUnitSpherePolyPolygon(
+ sal_uInt32 nHorSeg, sal_uInt32 nVerSeg,
+ double fVerStart, double fVerStop,
+ double fHorStart, double fHorStop)
+ {
+ B3DPolyPolygon aRetval;
+ sal_uInt32 a, b;
+
+ if(!nHorSeg)
+ {
+ nHorSeg = fround(fabs(fHorStop - fHorStart) / (F_2PI / 24.0));
+ }
+
+ if(!nHorSeg)
+ {
+ nHorSeg = 1L;
+ }
+
+ if(!nVerSeg)
+ {
+ nVerSeg = fround(fabs(fVerStop - fVerStart) / (F_2PI / 24.0));
+ }
+
+ if(!nVerSeg)
+ {
+ nVerSeg = 1L;
+ }
+
+ // create constants
+ const double fVerDiffPerStep((fVerStop - fVerStart) / (double)nVerSeg);
+ const double fHorDiffPerStep((fHorStop - fHorStart) / (double)nHorSeg);
+ bool bHorClosed(fTools::equal(fHorStop - fHorStart, F_2PI));
+ bool bVerFromTop(fTools::equal(fVerStart, F_PI2));
+ bool bVerToBottom(fTools::equal(fVerStop, -F_PI2));
+
+ // create horizontal rings
+ const sal_uInt32 nLoopVerInit(bVerFromTop ? 1L : 0L);
+ const sal_uInt32 nLoopVerLimit(bVerToBottom ? nVerSeg : nVerSeg + 1L);
+ const sal_uInt32 nLoopHorLimit(bHorClosed ? nHorSeg : nHorSeg + 1L);
+
+ for(a = nLoopVerInit; a < nLoopVerLimit; a++)
+ {
+ const double fVer(fVerStart + ((double)(a) * fVerDiffPerStep));
+ B3DPolygon aNew;
+
+ for(b = 0L; b < nLoopHorLimit; b++)
+ {
+ const double fHor(fHorStart + ((double)(b) * fHorDiffPerStep));
+ aNew.append(getPointFromCartesian(fHor, fVer));
+ }
+
+ aNew.setClosed(bHorClosed);
+ aRetval.append(aNew);
+ }
+
+ // create vertical half-rings
+ for(a = 0L; a < nLoopHorLimit; a++)
+ {
+ const double fHor(fHorStart + ((double)(a) * fHorDiffPerStep));
+ B3DPolygon aNew;
+
+ if(bVerFromTop)
+ {
+ aNew.append(B3DPoint(0.0, 1.0, 0.0));
+ }
+
+ for(b = nLoopVerInit; b < nLoopVerLimit; b++)
+ {
+ const double fVer(fVerStart + ((double)(b) * fVerDiffPerStep));
+ aNew.append(getPointFromCartesian(fHor, fVer));
+ }
+
+ if(bVerToBottom)
+ {
+ aNew.append(B3DPoint(0.0, -1.0, 0.0));
+ }
+
+ aRetval.append(aNew);
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon createSpherePolyPolygonFromB3DRange( const B3DRange& rRange,
+ sal_uInt32 nHorSeg, sal_uInt32 nVerSeg,
+ double fVerStart, double fVerStop,
+ double fHorStart, double fHorStop)
+ {
+ B3DPolyPolygon aRetval(createUnitSpherePolyPolygon(nHorSeg, nVerSeg, fVerStart, fVerStop, fHorStart, fHorStop));
+
+ if(aRetval.count())
+ {
+ // move and scale whole construct which is now in [-1.0 .. 1.0] in all directions
+ B3DHomMatrix aTrans;
+ aTrans.translate(1.0, 1.0, 1.0);
+ aTrans.scale(rRange.getWidth() / 2.0, rRange.getHeight() / 2.0, rRange.getDepth() / 2.0);
+ aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ());
+ aRetval.transform(aTrans);
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon createUnitSphereFillPolyPolygon(
+ sal_uInt32 nHorSeg, sal_uInt32 nVerSeg,
+ bool bNormals,
+ double fVerStart, double fVerStop,
+ double fHorStart, double fHorStop)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(!nHorSeg)
+ {
+ nHorSeg = fround(fabs(fHorStop - fHorStart) / (F_2PI / 24.0));
+ }
+
+ if(!nHorSeg)
+ {
+ nHorSeg = 1L;
+ }
+
+ if(!nVerSeg)
+ {
+ nVerSeg = fround(fabs(fVerStop - fVerStart) / (F_2PI / 24.0));
+ }
+
+ if(!nVerSeg)
+ {
+ nVerSeg = 1L;
+ }
+
+ // vertical loop
+ for(sal_uInt32 a(0L); a < nVerSeg; a++)
+ {
+ const double fVer(fVerStart + (((fVerStop - fVerStart) * a) / nVerSeg));
+ const double fVer2(fVerStart + (((fVerStop - fVerStart) * (a + 1)) / nVerSeg));
+
+ // horizontal loop
+ for(sal_uInt32 b(0L); b < nHorSeg; b++)
+ {
+ const double fHor(fHorStart + (((fHorStop - fHorStart) * b) / nHorSeg));
+ const double fHor2(fHorStart + (((fHorStop - fHorStart) * (b + 1)) / nHorSeg));
+ B3DPolygon aNew;
+
+ aNew.append(getPointFromCartesian(fHor, fVer));
+ aNew.append(getPointFromCartesian(fHor2, fVer));
+ aNew.append(getPointFromCartesian(fHor2, fVer2));
+ aNew.append(getPointFromCartesian(fHor, fVer2));
+
+ if(bNormals)
+ {
+ for(sal_uInt32 c(0L); c < aNew.count(); c++)
+ {
+ aNew.setNormal(c, ::basegfx::B3DVector(aNew.getB3DPoint(c)));
+ }
+ }
+
+ aNew.setClosed(true);
+ aRetval.append(aNew);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon createSphereFillPolyPolygonFromB3DRange( const B3DRange& rRange,
+ sal_uInt32 nHorSeg, sal_uInt32 nVerSeg,
+ bool bNormals,
+ double fVerStart, double fVerStop,
+ double fHorStart, double fHorStop)
+ {
+ B3DPolyPolygon aRetval(createUnitSphereFillPolyPolygon(nHorSeg, nVerSeg, bNormals, fVerStart, fVerStop, fHorStart, fHorStop));
+
+ if(aRetval.count())
+ {
+ // move and scale whole construct which is now in [-1.0 .. 1.0] in all directions
+ B3DHomMatrix aTrans;
+ aTrans.translate(1.0, 1.0, 1.0);
+ aTrans.scale(rRange.getWidth() / 2.0, rRange.getHeight() / 2.0, rRange.getDepth() / 2.0);
+ aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ());
+ aRetval.transform(aTrans);
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon applyDefaultNormalsSphere( const B3DPolyPolygon& rCandidate, const B3DPoint& rCenter)
+ {
+ B3DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(applyDefaultNormalsSphere(rCandidate.getB3DPolygon(a), rCenter));
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon invertNormals( const B3DPolyPolygon& rCandidate)
+ {
+ B3DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(invertNormals(rCandidate.getB3DPolygon(a)));
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon applyDefaultTextureCoordinatesParallel( const B3DPolyPolygon& rCandidate, const B3DRange& rRange, bool bChangeX, bool bChangeY)
+ {
+ B3DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(applyDefaultTextureCoordinatesParallel(rCandidate.getB3DPolygon(a), rRange, bChangeX, bChangeY));
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon applyDefaultTextureCoordinatesSphere( const B3DPolyPolygon& rCandidate, const B3DPoint& rCenter, bool bChangeX, bool bChangeY)
+ {
+ B3DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0L); a < rCandidate.count(); a++)
+ {
+ aRetval.append(applyDefaultTextureCoordinatesSphere(rCandidate.getB3DPolygon(a), rCenter, bChangeX, bChangeY));
+ }
+
+ return aRetval;
+ }
+
+ bool isInside(const B3DPolyPolygon& rCandidate, const B3DPoint& rPoint, bool bWithBorder)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+
+ if(1L == nPolygonCount)
+ {
+ return isInside(rCandidate.getB3DPolygon(0), rPoint, bWithBorder);
+ }
+ else
+ {
+ sal_Int32 nInsideCount(0);
+
+ for(sal_uInt32 a(0); a < nPolygonCount; a++)
+ {
+ const B3DPolygon aPolygon(rCandidate.getB3DPolygon(a));
+ const bool bInside(isInside(aPolygon, rPoint, bWithBorder));
+
+ if(bInside)
+ {
+ nInsideCount++;
+ }
+ }
+
+ return (nInsideCount % 2L);
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ // comparators with tolerance for 3D PolyPolygons
+
+ bool equal(const B3DPolyPolygon& rCandidateA, const B3DPolyPolygon& rCandidateB, const double& rfSmallValue)
+ {
+ const sal_uInt32 nPolygonCount(rCandidateA.count());
+
+ if(nPolygonCount != rCandidateB.count())
+ return false;
+
+ for(sal_uInt32 a(0); a < nPolygonCount; a++)
+ {
+ const B3DPolygon aCandidate(rCandidateA.getB3DPolygon(a));
+
+ if(!equal(aCandidate, rCandidateB.getB3DPolygon(a), rfSmallValue))
+ return false;
+ }
+
+ return true;
+ }
+
+ bool equal(const B3DPolyPolygon& rCandidateA, const B3DPolyPolygon& rCandidateB)
+ {
+ const double fSmallValue(fTools::getSmallValue());
+
+ return equal(rCandidateA, rCandidateB, fSmallValue);
+ }
+
+ } // end of namespace tools
+} // end of namespace basegfx
+
+//////////////////////////////////////////////////////////////////////////////
+
+// eof
diff --git a/basegfx/source/polygon/makefile.mk b/basegfx/source/polygon/makefile.mk
new file mode 100644
index 000000000000..aeb0af6c5d1b
--- /dev/null
+++ b/basegfx/source/polygon/makefile.mk
@@ -0,0 +1,62 @@
+#*************************************************************************
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# Copyright 2000, 2010 Oracle and/or its affiliates.
+#
+# OpenOffice.org - a multi-platform office productivity suite
+#
+# This file is part of OpenOffice.org.
+#
+# OpenOffice.org is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3
+# only, as published by the Free Software Foundation.
+#
+# OpenOffice.org is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License version 3 for more details
+# (a copy is included in the LICENSE file that accompanied this code).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# version 3 along with OpenOffice.org. If not, see
+# <http://www.openoffice.org/license.html>
+# for a copy of the LGPLv3 License.
+#
+#*************************************************************************
+
+PRJ=..$/..
+PRJNAME=basegfx
+TARGET=polygon
+
+#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb
+ENABLE_EXCEPTIONS=TRUE
+#USE_DEFFILE=TRUE
+
+# --- Settings ----------------------------------
+
+.INCLUDE : settings.mk
+
+# --- Files -------------------------------------
+
+SLOFILES= \
+ $(SLO)$/b2dpolygon.obj \
+ $(SLO)$/b2dpolygontools.obj \
+ $(SLO)$/b2dpolypolygon.obj \
+ $(SLO)$/b2dpolypolygontools.obj \
+ $(SLO)$/b2dsvgpolypolygon.obj \
+ $(SLO)$/b2dlinegeometry.obj \
+ $(SLO)$/b2dpolypolygoncutter.obj \
+ $(SLO)$/b2dpolypolygonrasterconverter.obj \
+ $(SLO)$/b2dpolygonclipper.obj \
+ $(SLO)$/b2dpolygontriangulator.obj \
+ $(SLO)$/b2dpolygoncutandtouch.obj \
+ $(SLO)$/b3dpolygon.obj \
+ $(SLO)$/b3dpolygontools.obj \
+ $(SLO)$/b3dpolypolygon.obj \
+ $(SLO)$/b3dpolypolygontools.obj \
+ $(SLO)$/b3dpolygonclipper.obj
+
+# --- Targets ----------------------------------
+
+.INCLUDE : target.mk