diff options
Diffstat (limited to 'basegfx')
180 files changed, 51217 insertions, 0 deletions
diff --git a/basegfx/inc/basegfx/color/bcolor.hxx b/basegfx/inc/basegfx/color/bcolor.hxx new file mode 100644 index 000000000000..db66dd8dbdee --- /dev/null +++ b/basegfx/inc/basegfx/color/bcolor.hxx @@ -0,0 +1,237 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_COLOR_BCOLOR_HXX +#define _BGFX_COLOR_BCOLOR_HXX + +#include <basegfx/tuple/b3dtuple.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <vector> + +////////////////////////////////////////////////////////////////////////////// +// predeclarations + +namespace com { namespace sun { namespace star { namespace rendering { + class XGraphicDevice; +}}}} + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + /** Base Color class with three double values + + This class derives all operators and common handling for + a 3D data class from B3DTuple. All necessary extensions + which are special for colors will be added here. + + @see B3DTuple + */ + class BColor : public B3DTuple + { + public: + /** Create a Color with red, green and blue components from [0.0 to 1.0] + + The color is initialized to (0.0, 0.0, 0.0) + */ + BColor() + : B3DTuple() + {} + + /** Create a 3D Color + + @param fRed + @param fGreen + @param fBlue + These parameters are used to initialize the red, green and blue intensities of the color + */ + BColor(double fRed, double fGreen, double fBlue) + : B3DTuple(fRed, fGreen, fBlue) + {} + + /** Create a 3D Color + + @param fLuminosity + The parameter is used to initialize the red, green and blue intensities of the color + */ + BColor(double fLuminosity) + : B3DTuple(fLuminosity, fLuminosity, fLuminosity) + {} + + /** Create a copy of a Color + + @param rVec + The Color which will be copied. + */ + BColor(const BColor& rVec) + : B3DTuple(rVec) + {} + + /** constructor with tuple to allow copy-constructing + from B3DTuple-based classes + */ + BColor(const ::basegfx::B3DTuple& rTuple) + : B3DTuple(rTuple) + {} + + ~BColor() + {} + + // data access read + double getRed() const { return mfX; } + double getGreen() const { return mfY; } + double getBlue() const { return mfZ; } + + // data access write + void setRed(double fNew) { mfX = fNew; } + void setGreen(double fNew) { mfY = fNew; } + void setBlue(double fNew) { mfZ = fNew; } + + /** *=operator to allow usage from BColor, too + */ + BColor& operator*=( const BColor& rPnt ) + { + mfX *= rPnt.mfX; + mfY *= rPnt.mfY; + mfZ *= rPnt.mfZ; + return *this; + } + + /** *=operator to allow usage from BColor, too + */ + BColor& operator*=(double t) + { + mfX *= t; + mfY *= t; + mfZ *= t; + return *this; + } + + /** assignment operator to allow assigning the results + of B3DTuple calculations + */ + BColor& operator=( const ::basegfx::B3DTuple& rVec ) + { + mfX = rVec.getX(); + mfY = rVec.getY(); + mfZ = rVec.getZ(); + return *this; + } + + // blend to another color using luminance + void blend(const BColor& rColor) + { + const double fLuminance(luminance()); + mfX = rColor.getRed() * fLuminance; + mfY = rColor.getGreen() * fLuminance; + mfZ = rColor.getBlue() * fLuminance; + } + + // luminance + double luminance() const + { + const double fRedWeight(77.0 / 256.0); + const double fGreenWeight(151.0 / 256.0); + const double fBlueWeight(28.0 / 256.0); + + return (mfX * fRedWeight + mfY * fGreenWeight + mfZ * fBlueWeight); + } + + // distances in color space + double getDistanceRed(const BColor& rColor) const { return (getRed() > rColor.getRed() ? getRed() - rColor.getRed() : rColor.getRed() - getRed()); } + double getDistanceGreen(const BColor& rColor) const { return (getGreen() > rColor.getGreen() ? getGreen() - rColor.getGreen() : rColor.getGreen() - getGreen()); } + double getDistanceBlue(const BColor& rColor) const { return (getBlue() > rColor.getBlue() ? getBlue() - rColor.getBlue() : rColor.getBlue() - getBlue()); } + + double getDistance(const BColor& rColor) const + { + const double fDistR(getDistanceRed(rColor)); + const double fDistG(getDistanceGreen(rColor)); + const double fDistB(getDistanceBlue(rColor)); + + return sqrt(fDistR * fDistR + fDistG * fDistG + fDistB * fDistB); + } + + double getMinimumDistance(const BColor& rColor) const + { + const double fDistR(getDistanceRed(rColor)); + const double fDistG(getDistanceGreen(rColor)); + const double fDistB(getDistanceBlue(rColor)); + + double fRetval(fDistR < fDistG ? fDistR : fDistG); + return (fRetval < fDistB ? fRetval : fDistB); + } + + double getMaximumDistance(const BColor& rColor) const + { + const double fDistR(getDistanceRed(rColor)); + const double fDistG(getDistanceGreen(rColor)); + const double fDistB(getDistanceBlue(rColor)); + + double fRetval(fDistR > fDistG ? fDistR : fDistG); + return (fRetval > fDistB ? fRetval : fDistB); + } + + // clamp color to [0.0..1.0] values in all three intensity components + void clamp() + { + mfX = basegfx::clamp(mfX, 0.0, 1.0); + mfY = basegfx::clamp(mfY, 0.0, 1.0); + mfZ = basegfx::clamp(mfZ, 0.0, 1.0); + } + + void invert() + { + mfX = 1.0 - mfX; + mfY = 1.0 - mfY; + mfZ = 1.0 - mfZ; + } + + static const BColor& getEmptyBColor() + { + return (const BColor&) ::basegfx::B3DTuple::getEmptyTuple(); + } + + com::sun::star::uno::Sequence< double > colorToDoubleSequence(const com::sun::star::uno::Reference< com::sun::star::rendering::XGraphicDevice >& /*xGraphicDevice*/) const + { + com::sun::star::uno::Sequence< double > aRet(4); + double* pRet = aRet.getArray(); + + pRet[0] = mfX; + pRet[1] = mfY; + pRet[2] = mfZ; + pRet[3] = 1.0; + + return aRet; + } + }; +} // end of namespace basegfx + +#endif /* _BGFX_COLOR_BCOLOR_HXX */ + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/inc/basegfx/color/bcolormodifier.hxx b/basegfx/inc/basegfx/color/bcolormodifier.hxx new file mode 100644 index 000000000000..962da760f850 --- /dev/null +++ b/basegfx/inc/basegfx/color/bcolormodifier.hxx @@ -0,0 +1,143 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_COLOR_BCOLORMODIFIER_HXX +#define _BGFX_COLOR_BCOLORMODIFIER_HXX + +#include <basegfx/color/bcolor.hxx> +#include <vector> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + /** Descriptor for type of color modification + */ + enum BColorModifyMode + { + BCOLORMODIFYMODE_REPLACE, // replace all color with local color + BCOLORMODIFYMODE_INTERPOLATE, // interpolate color between given and local with local value + BCOLORMODIFYMODE_GRAY, // convert color to gray + BCOLORMODIFYMODE_BLACKANDWHITE // convert color to B&W, local value is treshhold + }; + + /** Class to hold a color, value and mode for a color modification. Color modification is + done calling the getModifiedColor() method + */ + class BColorModifier + { + protected: + ::basegfx::BColor maBColor; + double mfValue; + BColorModifyMode meMode; + + public: + BColorModifier( + const ::basegfx::BColor& rBColor, + double fValue = 0.5, + BColorModifyMode eMode = BCOLORMODIFYMODE_REPLACE) + : maBColor(rBColor), + mfValue(fValue), + meMode(eMode) + {} + + // compare operator(s) + bool operator==(const BColorModifier& rCompare) const + { + return (maBColor == rCompare.maBColor && mfValue == rCompare.mfValue && meMode == rCompare.meMode); + } + + bool operator!=(const BColorModifier& rCompare) const + { + return !(operator==(rCompare)); + } + + // data access + const ::basegfx::BColor& getBColor() const { return maBColor; } + double getValue() const { return mfValue; } + BColorModifyMode getMode() const { return meMode; } + + // compute modified color + ::basegfx::BColor getModifiedColor(const ::basegfx::BColor& aSourceColor) const; + }; + + /** Class to hold a stack of BColorModifiers and to get the modified color with + applying all existing entry changes + */ + class BColorModifierStack + { + protected: + ::std::vector< BColorModifier > maBColorModifiers; + + public: + sal_uInt32 count() const + { + return maBColorModifiers.size(); + } + + const BColorModifier& getBColorModifier(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < count(), "BColorModifierStack: Access out of range (!)"); + return maBColorModifiers[nIndex]; + } + + ::basegfx::BColor getModifiedColor(const ::basegfx::BColor& rSource) const + { + if(count()) + { + ::basegfx::BColor aRetval(rSource); + ::std::vector< BColorModifier >::const_iterator aEnd(maBColorModifiers.end()); + + while(aEnd != maBColorModifiers.begin()) + { + aRetval = (--aEnd)->getModifiedColor(aRetval); + } + + return aRetval; + } + else + { + return rSource; + } + } + + void push(const BColorModifier& rNew) + { + maBColorModifiers.push_back(rNew); + } + + void pop() + { + maBColorModifiers.pop_back(); + } + }; +} // end of namespace basegfx + +#endif // _BGFX_COLOR_BCOLORMODIFIER_HXX + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/inc/basegfx/color/bcolortools.hxx b/basegfx/inc/basegfx/color/bcolortools.hxx new file mode 100644 index 000000000000..9121b145afa7 --- /dev/null +++ b/basegfx/inc/basegfx/color/bcolortools.hxx @@ -0,0 +1,72 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_COLOR_BCOLORTOOLS_HXX +#define _BGFX_COLOR_BCOLORTOOLS_HXX + +#include <sal/types.h> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class BColor; + + namespace tools + { + /// Transform from RGB to HSL + BColor rgb2hsl(const BColor& rRGBColor); + /// Transform from HSL to RGB + BColor hsl2rgb(const BColor& rHSLColor); + + /// Transform from RGB to HSV + BColor rgb2hsv(const BColor& rRGBColor); + /// Transform from HSV to RGB + BColor hsv2rgb(const BColor& rHSVColor); + + /// Transform from R'G'B' to YIQ (NTSC color model - Y is used in monochrome mode) + BColor rgb2yiq(const BColor& rRGBColor); + /// Transform from YIQ to R'G'B' (NTSC color model - Y is used in monochrome mode) + BColor yiq2rgb(const BColor& rYIQColor); + + /// Transform from R'G'B' to Y'PbPr (the [0,1]x[-.5,.5]x[-.5,.5] equivalent of Y'CbCr (which is scaled into 8bit)) + BColor rgb2ypbpr(const BColor& rRGBColor); + /// Transform from Y'PbPr (the [0,1]x[-.5,.5]x[-.5,.5] equivalent of Y'CbCr (which is scaled into 8bit)) into R'G'B' + BColor ypbpr2rgb(const BColor& rYPbPrColor); + + /// Transform from CIE XYZ into Rec. 709 RGB (D65 white point) + BColor ciexyz2rgb( const BColor& rXYZColor ); + /// Transform from Rec. 709 RGB (D65 white point) into CIE XYZ + BColor rgb2ciexyz( const BColor& rRGBColor ); + + } +} // end of namespace basegfx + +#endif /* _BGFX_COLOR_BCOLORTOOLS_HXX */ + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/inc/basegfx/curve/b2dbeziertools.hxx b/basegfx/inc/basegfx/curve/b2dbeziertools.hxx new file mode 100644 index 000000000000..3cc34fec5482 --- /dev/null +++ b/basegfx/inc/basegfx/curve/b2dbeziertools.hxx @@ -0,0 +1,63 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_CURVE_B2DBEZIERTOOLS_HXX +#define _BGFX_CURVE_B2DBEZIERTOOLS_HXX + +#include <sal/types.h> +#include <vector> + +////////////////////////////////////////////////////////////////////////////// +// predefines + +namespace basegfx +{ + class B2DCubicBezier; +} + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class B2DCubicBezierHelper + { + private: + ::std::vector< double > maLengthArray; + sal_uInt32 mnEdgeCount; + + public: + B2DCubicBezierHelper(const B2DCubicBezier& rBase, sal_uInt32 nDivisions = 9); + + double getLength() const { if(maLengthArray.size()) return maLengthArray[maLengthArray.size() - 1]; else return 0.0; } + double distanceToRelative(double fDistance) const; + double relativeToDistance(double fRelative) const; + }; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +#endif /* _BGFX_CURVE_B2DBEZIERTOOLS_HXX */ diff --git a/basegfx/inc/basegfx/curve/b2dcubicbezier.hxx b/basegfx/inc/basegfx/curve/b2dcubicbezier.hxx new file mode 100644 index 000000000000..ea58a0a50b80 --- /dev/null +++ b/basegfx/inc/basegfx/curve/b2dcubicbezier.hxx @@ -0,0 +1,222 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_CURVE_B2DCUBICBEZIER_HXX +#define _BGFX_CURVE_B2DCUBICBEZIER_HXX + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drange.hxx> + +////////////////////////////////////////////////////////////////////////////// +// predeclarations + +namespace basegfx +{ + class B2DPolygon; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class B2DCubicBezier + { + B2DPoint maStartPoint; + B2DPoint maEndPoint; + B2DPoint maControlPointA; + B2DPoint maControlPointB; + + public: + B2DCubicBezier(); + B2DCubicBezier(const B2DCubicBezier& rBezier); + B2DCubicBezier(const B2DPoint& rStart, const B2DPoint& rEnd); + B2DCubicBezier(const B2DPoint& rStart, const B2DPoint& rControlPointA, const B2DPoint& rControlPointB, const B2DPoint& rEnd); + ~B2DCubicBezier(); + + // assignment operator + B2DCubicBezier& operator=(const B2DCubicBezier& rBezier); + + // compare operators + bool operator==(const B2DCubicBezier& rBezier) const; + bool operator!=(const B2DCubicBezier& rBezier) const; + bool equal(const B2DCubicBezier& rBezier) const; + + // test if vectors are used + bool isBezier() const; + + // test if contained bezier is trivial and reset vectors accordingly + void testAndSolveTrivialBezier(); + + /** get length of edge + + This method handles beziers and simple edges. For + beziers, the deviation describes the maximum allowed + deviation from the real edge length. The default + allows a deviation of 1% from the correct length. + + For beziers, there is no direct way to get the length, + thus this method may subdivide the bezier edge and may + not be cheap. + + @param fDeviation + The maximal allowed deviation between correct length + and bezier edge length + + @return + The length of the edge + */ + double getLength(double fDeviation = 0.01) const; + + // get distance between start and end point + double getEdgeLength() const; + + // get length of control polygon + double getControlPolygonLength() const; + + // data interface + B2DPoint getStartPoint() const { return maStartPoint; } + void setStartPoint(const B2DPoint& rValue) { maStartPoint = rValue; } + + B2DPoint getEndPoint() const { return maEndPoint; } + void setEndPoint(const B2DPoint& rValue) { maEndPoint = rValue; } + + B2DPoint getControlPointA() const { return maControlPointA; } + void setControlPointA(const B2DPoint& rValue) { maControlPointA = rValue; } + + B2DPoint getControlPointB() const { return maControlPointB; } + void setControlPointB(const B2DPoint& rValue) { maControlPointB = rValue; } + + /** get the tangent in point t + + This method handles all the exceptions, e.g. when control point + A is equal to start point and/or control point B is equal to end + point + + @param t + The bezier index in the range [0.0 .. 1.0]. It will be truncated. + + @return + The tangent vector in point t + */ + B2DVector getTangent(double t) const; + + /** adaptive subdivide by angle criteria + no start point is added, but all necessary created edges + and the end point + #i37443# allow the criteria to get unsharp in recursions + */ + void adaptiveSubdivideByAngle(B2DPolygon& rTarget, double fAngleBound, bool bAllowUnsharpen) const; + + /** #i37443# adaptive subdivide by nCount subdivisions + no start point is added, but all necessary created edges + and the end point + */ + void adaptiveSubdivideByCount(B2DPolygon& rTarget, sal_uInt32 nCount) const; + + /** Subdivide cubic bezier segment. + + This function adaptively subdivides the bezier + segment into as much straight line segments as necessary, + such that the maximal orthogonal distance from any of the + segments to the true curve is less than the given error + value. + No start point is added, but all necessary created edges + and the end point + + @param rPoly + Output polygon. The subdivided bezier segment is added to + this polygon via B2DPolygon::append(). + + @param rCurve + The cubic bezier curve to subdivide + + @param fDistanceBound + Bound on the maximal distance of the approximation to the + true curve. + */ + void adaptiveSubdivideByDistance(B2DPolygon& rTarget, double fDistanceBound) const; + + // get point at given relative position + B2DPoint interpolatePoint(double t) const; + + // calculate the smallest distance from given point to this cubic bezier segment + // and return the value. The relative position on the segment is returned in rCut. + double getSmallestDistancePointToBezierSegment(const B2DPoint& rTestPoint, double& rCut) const; + + // do a split at position t and fill both resulting segments + void split(double t, B2DCubicBezier* pBezierA, B2DCubicBezier* pBezierB) const; + + // extract snippet from fStart to fEnd from this bezier + B2DCubicBezier snippet(double fStart, double fEnd) const; + + // get range including conrol points + B2DRange getRange() const; + + /** Get the minimum extremum position t + + @param rfResult + Will be changed and set to a eventually found split value which should be in the + range [0.0 .. 1.0]. It will be the smallest current extremum; there may be more + + @return + Returns true if there was at least one extremum found + */ + bool getMinimumExtremumPosition(double& rfResult) const; + + /** Get all extremum pos of this segment + + This method will calculate all extremum positions of the segment + and add them to rResults if they are in the range ]0.0 .. 1.0[ + + @param rResults + The vector of doubles where the results will be added. Evtl. + existing contents will be removed since an empty vector is a + necessary result to express that there are no extreme positions + anymore. Since there is an upper maximum of 4 values, it makes + sense to use reserve(4) at the vector as preparation. + */ + void getAllExtremumPositions(::std::vector< double >& rResults) const; + + /** Get optimum-split position on this segment + + This method calculates the positions of all points of the segment + that have the maximimum distance to the corresponding line from + startpoint-endpoint. This helps to approximate the bezier curve + with a minimum number of line segments + + @param fResults + Result positions are in the range ]0.0 .. 1.0[ + Cubic beziers have at most two of these positions + + @return + Returns the number of split positions found + */ + int getMaxDistancePositions( double fResults[2]) const; + }; +} // end of namespace basegfx + +#endif /* _BGFX_CURVE_B2DCUBICBEZIER_HXX */ diff --git a/basegfx/inc/basegfx/curve/b2dquadraticbezier.hxx b/basegfx/inc/basegfx/curve/b2dquadraticbezier.hxx new file mode 100644 index 000000000000..08cd6be59c78 --- /dev/null +++ b/basegfx/inc/basegfx/curve/b2dquadraticbezier.hxx @@ -0,0 +1,73 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_CURVE_B2DQUADRATICBEZIER_HXX +#define _BGFX_CURVE_B2DQUADRATICBEZIER_HXX + +#include <basegfx/point/b2dpoint.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class B2DQuadraticBezier + { + ::basegfx::B2DPoint maStartPoint; + ::basegfx::B2DPoint maEndPoint; + ::basegfx::B2DPoint maControlPoint; + + public: + B2DQuadraticBezier(); + B2DQuadraticBezier(const B2DQuadraticBezier& rBezier); + B2DQuadraticBezier(const ::basegfx::B2DPoint& rStart, const ::basegfx::B2DPoint& rEnd); + B2DQuadraticBezier(const ::basegfx::B2DPoint& rStart, + const ::basegfx::B2DPoint& rControlPoint, const ::basegfx::B2DPoint& rEnd); + ~B2DQuadraticBezier(); + + // assignment operator + B2DQuadraticBezier& operator=(const B2DQuadraticBezier& rBezier); + + // compare operators + bool operator==(const B2DQuadraticBezier& rBezier) const; + bool operator!=(const B2DQuadraticBezier& rBezier) const; + + // test if control point is placed on the edge + bool isBezier() const; + + // data interface + ::basegfx::B2DPoint getStartPoint() const { return maStartPoint; } + void setStartPoint(const ::basegfx::B2DPoint& rValue) { maStartPoint = rValue; } + + ::basegfx::B2DPoint getEndPoint() const { return maEndPoint; } + void setEndPoint(const ::basegfx::B2DPoint& rValue) { maEndPoint = rValue; } + + ::basegfx::B2DPoint getControlPoint() const { return maControlPoint; } + void setControlPoint(const ::basegfx::B2DPoint& rValue) { maControlPoint = rValue; } + }; +} // end of namespace basegfx + +#endif /* _BGFX_CURVE_B2DQUADRATICBEZIER_HXX */ diff --git a/basegfx/inc/basegfx/matrix/b2dhommatrix.hxx b/basegfx/inc/basegfx/matrix/b2dhommatrix.hxx new file mode 100644 index 000000000000..910224897301 --- /dev/null +++ b/basegfx/inc/basegfx/matrix/b2dhommatrix.hxx @@ -0,0 +1,168 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_MATRIX_B2DHOMMATRIX_HXX +#define _BGFX_MATRIX_B2DHOMMATRIX_HXX + +#include <sal/types.h> +#include <o3tl/cow_wrapper.hxx> + +namespace basegfx +{ + class B2DTuple; + class Impl2DHomMatrix; + + class B2DHomMatrix + { + public: + typedef o3tl::cow_wrapper< Impl2DHomMatrix > ImplType; + + private: + ImplType mpImpl; + + public: + B2DHomMatrix(); + B2DHomMatrix(const B2DHomMatrix& rMat); + ~B2DHomMatrix(); + + /** constructor to allow setting all needed values for a 3x2 matrix at once. The + parameter f_0x1 e.g. is the same as using set(0, 1, f) + */ + B2DHomMatrix(double f_0x0, double f_0x1, double f_0x2, double f_1x0, double f_1x1, double f_1x2); + + /// unshare this matrix with all internally shared instances + void makeUnique(); + + double get(sal_uInt16 nRow, sal_uInt16 nColumn) const; + void set(sal_uInt16 nRow, sal_uInt16 nColumn, double fValue); + + /** allow setting all needed values for a 3x2 matrix in one call. The + parameter f_0x1 e.g. is the same as using set(0, 1, f) + */ + void set3x2(double f_0x0, double f_0x1, double f_0x2, double f_1x0, double f_1x1, double f_1x2); + + // test if last line is default to see if last line needs to be + // involved in calculations + bool isLastLineDefault() const; + + // Auf Einheitsmatrix zuruecksetzen + bool isIdentity() const; + void identity(); + + // Invertierung + bool isInvertible() const; + bool invert(); + + // Normalisierung + bool isNormalized() const; + void normalize(); + + // Determinante + double determinant() const; + + // Trace + double trace() const; + + // Transpose + void transpose(); + + // Rotation + void rotate(double fRadiant); + + // Translation + void translate(double fX, double fY); + + // Skalierung + void scale(double fX, double fY); + + // Shearing-Matrices + void shearX(double fSx); + void shearY(double fSy); + + // Addition, Subtraktion + B2DHomMatrix& operator+=(const B2DHomMatrix& rMat); + B2DHomMatrix& operator-=(const B2DHomMatrix& rMat); + + // Vergleichsoperatoren + bool operator==(const B2DHomMatrix& rMat) const; + bool operator!=(const B2DHomMatrix& rMat) const; + + // Multiplikation, Division mit Konstante + B2DHomMatrix& operator*=(double fValue); + B2DHomMatrix& operator/=(double fValue); + + // Matritzenmultiplikation von links auf die lokale + B2DHomMatrix& operator*=(const B2DHomMatrix& rMat); + + // assignment operator + B2DHomMatrix& operator=(const B2DHomMatrix& rMat); + + // Help routine to decompose given homogen 3x3 matrix to components. A correction of + // the components is done to avoid inaccuracies. + // Zerlegung + bool decompose(B2DTuple& rScale, B2DTuple& rTranslate, double& rRotate, double& rShearX) const; + }; + + // Addition, Subtraktion + inline B2DHomMatrix operator+(const B2DHomMatrix& rMatA, const B2DHomMatrix& rMatB) + { + B2DHomMatrix aSum(rMatA); + aSum += rMatB; + return aSum; + } + + inline B2DHomMatrix operator-(const B2DHomMatrix& rMatA, const B2DHomMatrix& rMatB) + { + B2DHomMatrix aDiv(rMatA); + aDiv -= rMatB; + return aDiv; + } + + // Multiplikation, Division mit Konstante + inline B2DHomMatrix operator*(const B2DHomMatrix& rMat, double fValue) + { + B2DHomMatrix aNew(rMat); + aNew *= fValue; + return aNew; + } + + inline B2DHomMatrix operator/(const B2DHomMatrix& rMat, double fValue) + { + B2DHomMatrix aNew(rMat); + aNew *= 1.0 / fValue; + return aNew; + } + + inline B2DHomMatrix operator*(const B2DHomMatrix& rMatA, const B2DHomMatrix& rMatB) + { + B2DHomMatrix aMul(rMatB); + aMul *= rMatA; + return aMul; + } +} // end of namespace basegfx + +#endif /* _BGFX_MATRIX_B2DHOMMATRIX_HXX */ diff --git a/basegfx/inc/basegfx/matrix/b2dhommatrixtools.hxx b/basegfx/inc/basegfx/matrix/b2dhommatrixtools.hxx new file mode 100644 index 000000000000..7ce9e75c3058 --- /dev/null +++ b/basegfx/inc/basegfx/matrix/b2dhommatrixtools.hxx @@ -0,0 +1,234 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_MATRIX_B2DHOMMATRIXTOOLS_HXX +#define _BGFX_MATRIX_B2DHOMMATRIXTOOLS_HXX + +#include <sal/types.h> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/vector/b2dvector.hxx> + +namespace rtl { class OUString; } + +/////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + /** If the rotation angle is an approximate multiple of pi/2, + force fSin/fCos to -1/0/1, to maintain orthogonality (which + might also be advantageous for the other cases, but: for + multiples of pi/2, the exact values _can_ be attained. It + would be largely unintuitive, if a 180 degrees rotation + would introduce slight roundoff errors, instead of exactly + mirroring the coordinate system) + */ + void createSinCosOrthogonal(double& o_rSin, double& rCos, double fRadiant); + + /** Tooling methods for on-the-fly matrix generation e.g. for inline + multiplications + */ + B2DHomMatrix createScaleB2DHomMatrix(double fScaleX, double fScaleY); + B2DHomMatrix createShearXB2DHomMatrix(double fShearX); + B2DHomMatrix createShearYB2DHomMatrix(double fShearY); + B2DHomMatrix createRotateB2DHomMatrix(double fRadiant); + B2DHomMatrix createTranslateB2DHomMatrix(double fTranslateX, double fTranslateY); + + /// inline versions for parameters as tuples + inline B2DHomMatrix createScaleB2DHomMatrix(const B2DTuple& rScale) + { + return createScaleB2DHomMatrix(rScale.getX(), rScale.getY()); + } + + inline B2DHomMatrix createTranslateB2DHomMatrix(const B2DTuple& rTranslate) + { + return createTranslateB2DHomMatrix(rTranslate.getX(), rTranslate.getY()); + } + + /** Tooling methods for faster completely combined matrix creation + when scale, shearX, rotation and translation needs to be done in + exactly that order. It's faster since it direcly calculates + each matrix value based on a symbolic calculation of the three + matrix multiplications. + Inline versions for parameters as tuples added, too. + */ + B2DHomMatrix createScaleShearXRotateTranslateB2DHomMatrix( + double fScaleX, double fScaleY, + double fShearX, + double fRadiant, + double fTranslateX, double fTranslateY); + inline B2DHomMatrix createScaleShearXRotateTranslateB2DHomMatrix( + const B2DTuple& rScale, + double fShearX, + double fRadiant, + const B2DTuple& rTranslate) + { + return createScaleShearXRotateTranslateB2DHomMatrix( + rScale.getX(), rScale.getY(), + fShearX, + fRadiant, + rTranslate.getX(), rTranslate.getY()); + } + + B2DHomMatrix createShearXRotateTranslateB2DHomMatrix( + double fShearX, + double fRadiant, + double fTranslateX, double fTranslateY); + inline B2DHomMatrix createShearXRotateTranslateB2DHomMatrix( + double fShearX, + double fRadiant, + const B2DTuple& rTranslate) + { + return createShearXRotateTranslateB2DHomMatrix( + fShearX, + fRadiant, + rTranslate.getX(), rTranslate.getY()); + } + + B2DHomMatrix createScaleTranslateB2DHomMatrix( + double fScaleX, double fScaleY, + double fTranslateX, double fTranslateY); + inline B2DHomMatrix createScaleTranslateB2DHomMatrix( + const B2DTuple& rScale, + const B2DTuple& rTranslate) + { + return createScaleTranslateB2DHomMatrix( + rScale.getX(), rScale.getY(), + rTranslate.getX(), rTranslate.getY()); + } + + /// special for the often used case of rotation around a point + B2DHomMatrix createRotateAroundPoint( + double fPointX, double fPointY, + double fRadiant); + inline B2DHomMatrix createRotateAroundPoint( + const B2DTuple& rPoint, + double fRadiant) + { + return createRotateAroundPoint( + rPoint.getX(), rPoint.getY(), + fRadiant); + } + + } // end of namespace tools +} // end of namespace basegfx + +/////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + class B2DHomMatrixBufferedDecompose + { + private: + B2DVector maScale; + B2DVector maTranslate; + double mfRotate; + double mfShearX; + + public: + B2DHomMatrixBufferedDecompose(const B2DHomMatrix& rB2DHomMatrix) + : maScale(), + maTranslate(), + mfRotate(0.0), + mfShearX(0.0) + { + rB2DHomMatrix.decompose(maScale, maTranslate, mfRotate, mfShearX); + } + + // data access + B2DHomMatrix getB2DHomMatrix() const + { + return createScaleShearXRotateTranslateB2DHomMatrix( + maScale, mfShearX, mfRotate, maTranslate); + } + + const B2DVector& getScale() const { return maScale; } + const B2DVector& getTranslate() const { return maTranslate; } + double getRotate() const { return mfRotate; } + double getShearX() const { return mfShearX; } + }; + } // end of namespace tools +} // end of namespace basegfx + +/////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + class B2DHomMatrixBufferedOnDemandDecompose + { + private: + B2DHomMatrix maB2DHomMatrix; + B2DVector maScale; + B2DVector maTranslate; + double mfRotate; + double mfShearX; + + // bitfield + unsigned mbDecomposed : 1; + + void impCheckDecompose() + { + if(!mbDecomposed) + { + maB2DHomMatrix.decompose(maScale, maTranslate, mfRotate, mfShearX); + mbDecomposed = true; + } + } + + public: + B2DHomMatrixBufferedOnDemandDecompose(const B2DHomMatrix& rB2DHomMatrix) + : maB2DHomMatrix(rB2DHomMatrix), + maScale(), + maTranslate(), + mfRotate(0.0), + mfShearX(0.0), + mbDecomposed(false) + { + } + + // data access + const B2DHomMatrix& getB2DHomMatrix() const { return maB2DHomMatrix; } + const B2DVector& getScale() const { const_cast< B2DHomMatrixBufferedOnDemandDecompose* >(this)->impCheckDecompose(); return maScale; } + const B2DVector& getTranslate() const { const_cast< B2DHomMatrixBufferedOnDemandDecompose* >(this)->impCheckDecompose(); return maTranslate; } + double getRotate() const { const_cast< B2DHomMatrixBufferedOnDemandDecompose* >(this)->impCheckDecompose(); return mfRotate; } + double getShearX() const { const_cast< B2DHomMatrixBufferedOnDemandDecompose* >(this)->impCheckDecompose(); return mfShearX; } + }; + } // end of namespace tools + + /// Returns a string with svg's "matrix(m00,m10,m01,m11,m02,m12)" representation + ::rtl::OUString exportToSvg( const B2DHomMatrix& rMatrix ); + +} // end of namespace basegfx + +/////////////////////////////////////////////////////////////////////////////// + +#endif /* _BGFX_MATRIX_B2DHOMMATRIXTOOLS_HXX */ diff --git a/basegfx/inc/basegfx/matrix/b3dhommatrix.hxx b/basegfx/inc/basegfx/matrix/b3dhommatrix.hxx new file mode 100644 index 000000000000..bc35fcde321e --- /dev/null +++ b/basegfx/inc/basegfx/matrix/b3dhommatrix.hxx @@ -0,0 +1,175 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_MATRIX_B3DHOMMATRIX_HXX +#define _BGFX_MATRIX_B3DHOMMATRIX_HXX + +#include <sal/types.h> +#include <basegfx/point/b3dpoint.hxx> +#include <basegfx/vector/b3dvector.hxx> +#include <o3tl/cow_wrapper.hxx> + +namespace basegfx +{ + class B3DTuple; + class Impl3DHomMatrix; + + class B3DHomMatrix + { + public: + typedef o3tl::cow_wrapper< Impl3DHomMatrix > ImplType; + + private: + ImplType mpImpl; + + public: + B3DHomMatrix(); + B3DHomMatrix(const B3DHomMatrix& rMat); + ~B3DHomMatrix(); + + /// unshare this matrix with all internally shared instances + void makeUnique(); + + double get(sal_uInt16 nRow, sal_uInt16 nColumn) const; + void set(sal_uInt16 nRow, sal_uInt16 nColumn, double fValue); + + // test if last line is default to see if last line needs to be + // involved in calculations + bool isLastLineDefault() const; + + bool isIdentity() const; + /// Reset to the identity matrix + void identity(); + + bool isInvertible() const; + /// Invert the matrix (if possible) + bool invert(); + + bool isNormalized() const; + /// Normalize (i.e. force w=1) the matrix + void normalize(); + + /// Calc the matrix determinant + double determinant() const; + + /// Calc the matrix trace + double trace() const; + + /// Transpose the matrix + void transpose(); + + /// Rotation + void rotate(double fAngleX,double fAngleY,double fAngleZ); + + /// Translation + void translate(double fX, double fY, double fZ); + + /// Scaling + void scale(double fX, double fY, double fZ); + + // Shearing-Matrices + void shearXY(double fSx, double fSy); + void shearYZ(double fSy, double fSz); + void shearXZ(double fSx, double fSz); + + // Projection matrices, used for converting between eye and + // clip coordinates + void frustum(double fLeft = -1.0, double fRight = 1.0, + double fBottom = -1.0, double fTop = 1.0, + double fNear = 0.001, double fFar = 1.0); + + void ortho(double fLeft = -1.0, double fRight = 1.0, + double fBottom = -1.0, double fTop = 1.0, + double fNear = 0.0, double fFar = 1.0); + + // build orientation matrix + void orientation( + B3DPoint aVRP = B3DPoint(0.0,0.0,1.0), + B3DVector aVPN = B3DVector(0.0,0.0,1.0), + B3DVector aVUV = B3DVector(0.0,1.0,0.0)); + + // addition, subtraction + B3DHomMatrix& operator+=(const B3DHomMatrix& rMat); + B3DHomMatrix& operator-=(const B3DHomMatrix& rMat); + + // comparison + bool operator==(const B3DHomMatrix& rMat) const; + bool operator!=(const B3DHomMatrix& rMat) const; + + // multiplication, division by constant value + B3DHomMatrix& operator*=(double fValue); + B3DHomMatrix& operator/=(double fValue); + + // matrix multiplication (from the left) + B3DHomMatrix& operator*=(const B3DHomMatrix& rMat); + + // assignment operator + B3DHomMatrix& operator=(const B3DHomMatrix& rMat); + + // decomposition + bool decompose(B3DTuple& rScale, B3DTuple& rTranslate, B3DTuple& rRotate, B3DTuple& rShear) const; + }; + + // addition, subtraction + inline B3DHomMatrix operator+(const B3DHomMatrix& rMatA, const B3DHomMatrix& rMatB) + { + B3DHomMatrix aSum(rMatA); + aSum += rMatB; + return aSum; + } + + inline B3DHomMatrix operator-(const B3DHomMatrix& rMatA, const B3DHomMatrix& rMatB) + { + B3DHomMatrix aDiv(rMatA); + aDiv -= rMatB; + return aDiv; + } + + // multiplication, division by constant value + inline B3DHomMatrix operator*(const B3DHomMatrix& rMat, double fValue) + { + B3DHomMatrix aNew(rMat); + aNew *= fValue; + return aNew; + } + + inline B3DHomMatrix operator/(const B3DHomMatrix& rMat, double fValue) + { + B3DHomMatrix aNew(rMat); + aNew *= 1.0 / fValue; + return aNew; + } + + inline B3DHomMatrix operator*(const B3DHomMatrix& rMatA, const B3DHomMatrix& rMatB) + { + B3DHomMatrix aMul(rMatB); + aMul *= rMatA; + return aMul; + } +} // end of namespace basegfx + +#endif /* _BGFX_MATRIX_B3DHOMMATRIX_HXX */ diff --git a/basegfx/inc/basegfx/numeric/ftools.hxx b/basegfx/inc/basegfx/numeric/ftools.hxx new file mode 100644 index 000000000000..65a6566dae25 --- /dev/null +++ b/basegfx/inc/basegfx/numeric/ftools.hxx @@ -0,0 +1,203 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_NUMERIC_FTOOLS_HXX +#define _BGFX_NUMERIC_FTOOLS_HXX + +#include <rtl/math.hxx> + +////////////////////////////////////////////////////////////////////////////// +// standard PI defines from solar.h, but we do not want to link against tools + +#ifndef F_PI +#define F_PI M_PI +#endif +#ifndef F_PI2 +#define F_PI2 M_PI_2 +#endif +#ifndef F_PI4 +#define F_PI4 M_PI_4 +#endif +#ifndef F_PI180 +#define F_PI180 (M_PI/180.0) +#endif +#ifndef F_PI1800 +#define F_PI1800 (M_PI/1800.0) +#endif +#ifndef F_PI18000 +#define F_PI18000 (M_PI/18000.0) +#endif +#ifndef F_2PI +#define F_2PI (2.0*M_PI) +#endif + +////////////////////////////////////////////////////////////////////////////// +// fTools defines + +namespace basegfx +{ + /** Round double to nearest integer + + @return the nearest integer + */ + inline sal_Int32 fround( double fVal ) + { + return fVal > 0.0 ? static_cast<sal_Int32>( fVal + .5 ) : -static_cast<sal_Int32>( -fVal + .5 ); + } + + /** Round double to nearest integer + + @return the nearest 64 bit integer + */ + inline sal_Int64 fround64( double fVal ) + { + return fVal > 0.0 ? static_cast<sal_Int64>( fVal + .5 ) : -static_cast<sal_Int64>( -fVal + .5 ); + } + + /** Prune a small epsilon range around zero. + + Use this method e.g. for calculating scale values. There, it + is usually advisable not to set a scaling to 0.0, because that + yields singular transformation matrices. + + @param fVal + An arbitrary, but finite and valid number + + @return either fVal, or a small value slightly above (when + fVal>0) or below (when fVal<0) zero. + */ + inline double pruneScaleValue( double fVal ) + { + // old version used ::std::min/max, but this collides if min is defined as preprocessor + // macro which is the case e.g with windows.h headers. The simplest way to avoid this is to + // just use the full comparison. I keep the original here, maybe there will be a better + // solution some day. + // + //return fVal < 0.0 ? + // (::std::min(fVal,-0.00001)) : + // (::std::max(fVal,0.00001)); + + if(fVal < 0.0) + return (fVal < -0.00001 ? fVal : -0.00001); + else + return (fVal > 0.00001 ? fVal : 0.00001); + } + + /** clamp given value against given minimum and maximum values + */ + template <class T> inline const T& clamp(const T& value, const T& minimum, const T& maximum) + { + if(value < minimum) + { + return minimum; + } + else if(value > maximum) + { + return maximum; + } + else + { + return value; + } + } + + /** Convert value from degrees to radians + */ + inline double deg2rad( double v ) + { + // divide first, to get exact values for v being a multiple of + // 90 degrees + return v / 90.0 * M_PI_2; + } + + /** Convert value radians to degrees + */ + inline double rad2deg( double v ) + { + // divide first, to get exact values for v being a multiple of + // pi/2 + return v / M_PI_2 * 90.0; + } + + + class fTools + { + /// Threshold value for equalZero() + static double mfSmallValue; + + public: + /// Get threshold value for equalZero and friends + static double getSmallValue() { return mfSmallValue; } + /// Set threshold value for equalZero and friends + static void setSmallValue(const double& rfNew) { mfSmallValue = rfNew; } + + /// Compare against small value + static bool equalZero(const double& rfVal) + { + return (fabs(rfVal) <= getSmallValue()); + } + + /// Compare against given small value + static bool equalZero(const double& rfVal, const double& rfSmallValue) + { + return (fabs(rfVal) <= rfSmallValue); + } + + static bool equal(const double& rfValA, const double& rfValB) + { + // changed to approxEqual usage for better numerical correctness + return rtl::math::approxEqual(rfValA, rfValB); + } + + static bool equal(const double& rfValA, const double& rfValB, const double& rfSmallValue) + { + return (fabs(rfValA - rfValB) <= rfSmallValue); + } + + static bool less(const double& rfValA, const double& rfValB) + { + return (rfValA < rfValB && !equal(rfValA, rfValB)); + } + + static bool lessOrEqual(const double& rfValA, const double& rfValB) + { + return (rfValA < rfValB || equal(rfValA, rfValB)); + } + + static bool more(const double& rfValA, const double& rfValB) + { + return (rfValA > rfValB && !equal(rfValA, rfValB)); + } + + static bool moreOrEqual(const double& rfValA, const double& rfValB) + { + return (rfValA > rfValB || equal(rfValA, rfValB)); + } + }; +} // end of namespace basegfx + +#endif /* _BGFX_NUMERIC_FTOOLS_HXX */ diff --git a/basegfx/inc/basegfx/pixel/bpixel.hxx b/basegfx/inc/basegfx/pixel/bpixel.hxx new file mode 100644 index 000000000000..2684ff44e5d6 --- /dev/null +++ b/basegfx/inc/basegfx/pixel/bpixel.hxx @@ -0,0 +1,222 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_PIXEL_BPIXEL_HXX +#define _BGFX_PIXEL_BPIXEL_HXX + +#include <sal/types.h> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/color/bcolor.hxx> + +////////////////////////////////////////////////////////////////////////////// +// predeclarations + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class BPixel + { + protected: + union + { + struct + { + // bitfield + unsigned mnR : 8; // red intensity + unsigned mnG : 8; // green intensity + unsigned mnB : 8; // blue intensity + unsigned mnO : 8; // opacity, 0 == full transparence + } maRGBO; + + struct + { + // bitfield + unsigned mnValue : 32; // all values + } maCombinedRGBO; + } maPixelUnion; + + public: + BPixel() + { + maPixelUnion.maCombinedRGBO.mnValue = 0L; + } + + // use explicit here to make sure everyone knows what he is doing. Values range from + // 0..255 integer here. + explicit BPixel(sal_uInt8 nRed, sal_uInt8 nGreen, sal_uInt8 nBlue, sal_uInt8 nOpacity) + { + maPixelUnion.maRGBO.mnR = nRed; + maPixelUnion.maRGBO.mnG = nGreen; + maPixelUnion.maRGBO.mnB = nBlue; + maPixelUnion.maRGBO.mnO = nOpacity; + } + + // constructor from BColor which uses double precision color, so change it + // to local integer format. It will also be clamped here. + BPixel(const BColor& rColor, sal_uInt8 nOpacity) + { + maPixelUnion.maRGBO.mnR = sal_uInt8((rColor.getRed() * 255.0) + 0.5); + maPixelUnion.maRGBO.mnG = sal_uInt8((rColor.getGreen() * 255.0) + 0.5); + maPixelUnion.maRGBO.mnB = sal_uInt8((rColor.getBlue() * 255.0) + 0.5); + maPixelUnion.maRGBO.mnO = nOpacity; + } + + // copy constructor + BPixel(const BPixel& rPixel) + { + maPixelUnion.maCombinedRGBO.mnValue = rPixel.maPixelUnion.maCombinedRGBO.mnValue; + } + + ~BPixel() + {} + + // assignment operator + BPixel& operator=( const BPixel& rPixel ) + { + maPixelUnion.maCombinedRGBO.mnValue = rPixel.maPixelUnion.maCombinedRGBO.mnValue; + return *this; + } + + // data access read + sal_uInt8 getRed() const { return maPixelUnion.maRGBO.mnR; } + sal_uInt8 getGreen() const { return maPixelUnion.maRGBO.mnG; } + sal_uInt8 getBlue() const { return maPixelUnion.maRGBO.mnB; } + sal_uInt8 getOpacity() const { return maPixelUnion.maRGBO.mnO; } + sal_uInt32 getRedGreenBlueOpacity() const { return maPixelUnion.maCombinedRGBO.mnValue; } + + // data access write + void setRed(sal_uInt8 nNew) { maPixelUnion.maRGBO.mnR = nNew; } + void setGreen(sal_uInt8 nNew) { maPixelUnion.maRGBO.mnG = nNew; } + void setBlue(sal_uInt8 nNew) { maPixelUnion.maRGBO.mnB = nNew; } + void setOpacity(sal_uInt8 nNew) { maPixelUnion.maRGBO.mnO = nNew; } + void setRedGreenBlueOpacity(sal_uInt32 nRedGreenBlueOpacity) { maPixelUnion.maCombinedRGBO.mnValue = nRedGreenBlueOpacity; } + void setRedGreenBlue(sal_uInt8 nR, sal_uInt8 nG, sal_uInt8 nB) { maPixelUnion.maRGBO.mnR = nR; maPixelUnion.maRGBO.mnG = nG; maPixelUnion.maRGBO.mnB = nB; } + + // comparators + bool isInvisible() const { return (0 == maPixelUnion.maRGBO.mnO); } + bool isVisible() const { return (0 != maPixelUnion.maRGBO.mnO); } + bool isEmpty() const { return isInvisible(); } + bool isUsed() const { return isVisible(); } + + bool operator==( const BPixel& rPixel ) const + { + return (rPixel.maPixelUnion.maCombinedRGBO.mnValue == maPixelUnion.maCombinedRGBO.mnValue); + } + + bool operator!=( const BPixel& rPixel ) const + { + return (rPixel.maPixelUnion.maCombinedRGBO.mnValue != maPixelUnion.maCombinedRGBO.mnValue); + } + + // empty element + static const BPixel& getEmptyBPixel(); + }; + + ////////////////////////////////////////////////////////////////////////// + // external operators + + inline BPixel minimum(const BPixel& rTupA, const BPixel& rTupB) + { + BPixel aMin( + (rTupB.getRed() < rTupA.getRed()) ? rTupB.getRed() : rTupA.getRed(), + (rTupB.getGreen() < rTupA.getGreen()) ? rTupB.getGreen() : rTupA.getGreen(), + (rTupB.getBlue() < rTupA.getBlue()) ? rTupB.getBlue() : rTupA.getBlue(), + (rTupB.getOpacity() < rTupA.getOpacity()) ? rTupB.getOpacity() : rTupA.getOpacity()); + return aMin; + } + + inline BPixel maximum(const BPixel& rTupA, const BPixel& rTupB) + { + BPixel aMax( + (rTupB.getRed() > rTupA.getRed()) ? rTupB.getRed() : rTupA.getRed(), + (rTupB.getGreen() > rTupA.getGreen()) ? rTupB.getGreen() : rTupA.getGreen(), + (rTupB.getBlue() > rTupA.getBlue()) ? rTupB.getBlue() : rTupA.getBlue(), + (rTupB.getOpacity() > rTupA.getOpacity()) ? rTupB.getOpacity() : rTupA.getOpacity()); + return aMax; + } + + inline BPixel interpolate(const BPixel& rOld1, const BPixel& rOld2, double t) + { + if(rOld1 == rOld2) + { + return rOld1; + } + else if(0.0 >= t) + { + return rOld1; + } + else if(1.0 <= t) + { + return rOld2; + } + else + { + const sal_uInt32 nFactor(fround(256.0 * t)); + const sal_uInt32 nNegFac(256L - nFactor); + return BPixel( + (sal_uInt8)(((sal_uInt32)rOld1.getRed() * nNegFac + (sal_uInt32)rOld2.getRed() * nFactor) >> 8L), + (sal_uInt8)(((sal_uInt32)rOld1.getGreen() * nNegFac + (sal_uInt32)rOld2.getGreen() * nFactor) >> 8L), + (sal_uInt8)(((sal_uInt32)rOld1.getBlue() * nNegFac + (sal_uInt32)rOld2.getBlue() * nFactor) >> 8L), + (sal_uInt8)(((sal_uInt32)rOld1.getOpacity() * nNegFac + (sal_uInt32)rOld2.getOpacity() * nFactor) >> 8L)); + } + } + + inline BPixel average(const BPixel& rOld1, const BPixel& rOld2) + { + if(rOld1 == rOld2) + { + return rOld1; + } + else + { + return BPixel( + (sal_uInt8)(((sal_uInt32)rOld1.getRed() + (sal_uInt32)rOld2.getRed()) >> 1L), + (sal_uInt8)(((sal_uInt32)rOld1.getGreen() + (sal_uInt32)rOld2.getGreen()) >> 1L), + (sal_uInt8)(((sal_uInt32)rOld1.getBlue() + (sal_uInt32)rOld2.getBlue()) >> 1L), + (sal_uInt8)(((sal_uInt32)rOld1.getOpacity() + (sal_uInt32)rOld2.getOpacity()) >> 1L)); + } + } + + inline BPixel average(const BPixel& rOld1, const BPixel& rOld2, const BPixel& rOld3) + { + if(rOld1 == rOld2 && rOld2 == rOld3) + { + return rOld1; + } + else + { + return BPixel( + (sal_uInt8)(((sal_uInt32)rOld1.getRed() + (sal_uInt32)rOld2.getRed() + (sal_uInt32)rOld3.getRed()) / 3L), + (sal_uInt8)(((sal_uInt32)rOld1.getGreen() + (sal_uInt32)rOld2.getGreen() + (sal_uInt32)rOld3.getGreen()) / 3L), + (sal_uInt8)(((sal_uInt32)rOld1.getBlue() + (sal_uInt32)rOld2.getBlue() + (sal_uInt32)rOld3.getBlue()) / 3L), + (sal_uInt8)(((sal_uInt32)rOld1.getOpacity() + (sal_uInt32)rOld2.getOpacity() + (sal_uInt32)rOld3.getOpacity()) / 3L)); + } + } +} // end of namespace basegfx + +#endif /* _BGFX_PIXEL_BPIXEL_HXX */ diff --git a/basegfx/inc/basegfx/point/b2dhompoint.hxx b/basegfx/inc/basegfx/point/b2dhompoint.hxx new file mode 100644 index 000000000000..254ee9251dd3 --- /dev/null +++ b/basegfx/inc/basegfx/point/b2dhompoint.hxx @@ -0,0 +1,235 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POINT_B2DHOMPOINT_HXX +#define _BGFX_POINT_B2DHOMPOINT_HXX + +#include <basegfx/point/b2dpoint.hxx> + +namespace basegfx +{ + /** Basic homogen Point class with two double values and one homogen factor + + This class provides access to homogen coordinates in 2D. + For this purpose all the operators which need to do specific + action due to their homogenity are implemented here. + The only caveat are member methods which are declared as const + but do change the content. These are documented for that reason. + The class is designed to provide homogenous coordinates without + direct access to the homogen part (mfW). This is also the reason + for leaving out the [] operators which return references to members. + + @see B2DTuple + */ + class B2DHomPoint + { + protected: + /// This member contains the coordinate part of the point + ::basegfx::B2DTuple maTuple; + + /// This Member holds the homogenous part of the point + double mfW; + + /** Test if this homogen point does have a homogenous part + + @return Returns true if this point has no homogenous part + */ + bool implIsHomogenized() const; + + /** Remove homogenous part of this Point + + This method does necessary calculations to remove + the evtl. homogenous part of this Point. This may + change all members. + */ + void implHomogenize(); + + /** Test and on demand remove homogenous part + + This method tests if this Point does have a homogenous part + and then evtl. takes actions to remove that part. + + @attention Even when this method is const it may change all + members of this instance. This is due to the fact that changing + the homogenous part of a homogenous point does from a mathematical + point of view not change the point at all. + */ + void implTestAndHomogenize() const; + + public: + /** Create a homogen point + + The point is initialized to (0.0, 0.0) + */ + B2DHomPoint() + : maTuple(), + mfW(1.0) + {} + + /** Create a homogen point + + @param fX + This parameter is used to initialize the X-coordinate + of the Point. The homogenous part is initialized to 1.0. + + @param fY + This parameter is used to initialize the Y-coordinate + of the Point. The homogenous part is initialized to 1.0. + */ + B2DHomPoint(double fX, double fY) + : maTuple(fX, fY), + mfW(1.0) + {} + + /** Create a copy of a 2D Point + + @param rVec + The 2D point which will be copied. The homogenous part + is initialized to 1.0. + */ + B2DHomPoint(const B2DPoint& rVec) + : maTuple(rVec), + mfW(1.0) + {} + + /** Create a copy of a homogen point + + @param rVec + The homogen point which will be copied. The homogenous part + is copied, too. + */ + B2DHomPoint(const B2DHomPoint& rVec) + : maTuple(rVec.maTuple.getX(), rVec.maTuple.getY()), + mfW(rVec.mfW) + {} + + ~B2DHomPoint() + {} + + /** Get a 2D point from this homogenous point + + This method normalizes this homogen point if necessary and + returns the corresponding 2D point for this homogen point. + + @attention Even when this method is const it may change all + members of this instance. + */ + B2DPoint getB2DPoint() const; + + /** Get X-coordinate + + This method normalizes this homogen point if necessary and + returns the corresponding X-coordinate for this homogen point. + + @attention Even when this method is const it may change all + members of this instance. + */ + double getX() const; + + /** Get Y-coordinate + + This method normalizes this homogen point if necessary and + returns the corresponding Y-coordinate for this homogen point. + + @attention Even when this method is const it may change all + members of this instance. + */ + double getY() const; + + /** Set X-coordinate of the homogen point. + + This method sets the X-coordinate of the homogen point. If + the point does have a homogenous part this is taken into account. + + @param fX + The to-be-set X-coordinate without homogenous part. + */ + void setX(double fX); + + /** Set Y-coordinate of the homogen point. + + This method sets the Y-coordinate of the homogen point. If + the point does have a homogenous part this is taken into account. + + @param fY + The to-be-set Y-coordinate without homogenous part. + */ + void setY(double fY); + + // operators + ////////////////////////////////////////////////////////////////////// + + B2DHomPoint& operator+=( const B2DHomPoint& rPnt ); + + B2DHomPoint& operator-=( const B2DHomPoint& rPnt ); + + B2DHomPoint& operator*=(double t); + + B2DHomPoint& operator*=( const B2DHomMatrix& rMat ); + + B2DHomPoint& operator/=(double t); + + B2DHomPoint& operator-(void); + + bool operator==( const B2DHomPoint& rPnt ) const; + + bool operator!=( const B2DHomPoint& rPnt ) const; + + B2DHomPoint& operator=( const B2DHomPoint& rPnt ); + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + B2DHomPoint minimum(const B2DHomPoint& rVecA, const B2DHomPoint& rVecB); + + B2DHomPoint maximum(const B2DHomPoint& rVecA, const B2DHomPoint& rVecB); + + B2DHomPoint absolute(const B2DHomPoint& rVec); + + B2DHomPoint interpolate(B2DHomPoint& rOld1, B2DHomPoint& rOld2, double t); + + B2DHomPoint average(B2DHomPoint& rOld1, B2DHomPoint& rOld2); + + B2DHomPoint average(B2DHomPoint& rOld1, B2DHomPoint& rOld2, B2DHomPoint& rOld3); + + B2DHomPoint operator+(const B2DHomPoint& rVecA, const B2DHomPoint& rVecB); + + B2DHomPoint operator-(const B2DHomPoint& rVecA, const B2DHomPoint& rVecB); + + B2DHomPoint operator*(const B2DHomPoint& rVec, double t); + + B2DHomPoint operator*(double t, const B2DHomPoint& rVec); + + B2DHomPoint operator*( const B2DHomMatrix& rMat, const B2DHomPoint& rPoint ); + + B2DHomPoint operator/(const B2DHomPoint& rVec, double t); + + B2DHomPoint operator/(double t, const B2DHomPoint& rVec); +} // end of namespace basegfx + +#endif /* _BGFX_POINT_B2DHOMPOINT_HXX */ diff --git a/basegfx/inc/basegfx/point/b2dpoint.hxx b/basegfx/inc/basegfx/point/b2dpoint.hxx new file mode 100644 index 000000000000..273a1c54e3ba --- /dev/null +++ b/basegfx/inc/basegfx/point/b2dpoint.hxx @@ -0,0 +1,151 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POINT_B2DPOINT_HXX +#define _BGFX_POINT_B2DPOINT_HXX + +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/point/b2ipoint.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + // predeclaration + class B2DHomMatrix; + + /** Base Point class with two double values + + This class derives all operators and common handling for + a 2D data class from B2DTuple. All necessary extensions + which are special for points will be added here. + + @see B2DTuple + */ + class B2DPoint : public ::basegfx::B2DTuple + { + public: + /** Create a 2D Point + + The point is initialized to (0.0, 0.0) + */ + B2DPoint() + : B2DTuple() + {} + + /** Create a 2D Point + + @param fX + This parameter is used to initialize the X-coordinate + of the 2D Point. + + @param fY + This parameter is used to initialize the Y-coordinate + of the 2D Point. + */ + B2DPoint(double fX, double fY) + : B2DTuple(fX, fY) + {} + + /** Create a copy of a 2D Point + + @param rPoint + The 2D Point which will be copied. + */ + B2DPoint(const B2DPoint& rPoint) + : B2DTuple(rPoint) + {} + + /** Create a copy of a 2D Point + + @param rPoint + The 2D Point which will be copied. + */ + B2DPoint(const ::basegfx::B2IPoint& rPoint) + : B2DTuple(rPoint) + {} + + /** constructor with tuple to allow copy-constructing + from B2DTuple-based classes + */ + B2DPoint(const ::basegfx::B2DTuple& rTuple) + : B2DTuple(rTuple) + {} + + ~B2DPoint() + {} + + /** *=operator to allow usage from B2DPoint, too + */ + B2DPoint& operator*=( const B2DPoint& rPnt ) + { + mfX *= rPnt.mfX; + mfY *= rPnt.mfY; + return *this; + } + + /** *=operator to allow usage from B2DPoint, too + */ + B2DPoint& operator*=(double t) + { + mfX *= t; + mfY *= t; + return *this; + } + + /** assignment operator to allow assigning the results + of B2DTuple calculations + */ + B2DPoint& operator=( const ::basegfx::B2DTuple& rPoint ); + + /** Transform point by given transformation matrix. + + The translational components of the matrix are, in + contrast to B2DVector, applied. + */ + B2DPoint& operator*=( const ::basegfx::B2DHomMatrix& rMat ); + + static const B2DPoint& getEmptyPoint() + { + return (const B2DPoint&) ::basegfx::B2DTuple::getEmptyTuple(); + } + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + /** Transform B2DPoint by given transformation matrix. + + Since this is a Point, translational components of the + matrix are used. + */ + B2DPoint operator*( const B2DHomMatrix& rMat, const B2DPoint& rPoint ); +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +#endif /* _BGFX_POINT_B2DPOINT_HXX */ diff --git a/basegfx/inc/basegfx/point/b2ipoint.hxx b/basegfx/inc/basegfx/point/b2ipoint.hxx new file mode 100644 index 000000000000..dec51735de7c --- /dev/null +++ b/basegfx/inc/basegfx/point/b2ipoint.hxx @@ -0,0 +1,127 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POINT_B2IPOINT_HXX +#define _BGFX_POINT_B2IPOINT_HXX + +#include <basegfx/tuple/b2ituple.hxx> + +namespace basegfx +{ + // predeclaration + class B2DHomMatrix; + + /** Base Point class with two sal_Int32 values + + This class derives all operators and common handling for + a 2D data class from B2ITuple. All necessary extensions + which are special for points will be added here. + + @see B2ITuple + */ + class B2IPoint : public ::basegfx::B2ITuple + { + public: + /** Create a 2D Point + + The point is initialized to (0, 0) + */ + B2IPoint() + : B2ITuple() + {} + + /** Create a 2D Point + + @param nX + This parameter is used to initialize the X-coordinate + of the 2D Point. + + @param nY + This parameter is used to initialize the Y-coordinate + of the 2D Point. + */ + B2IPoint(sal_Int32 nX, sal_Int32 nY) + : B2ITuple(nX, nY) + {} + + /** Create a copy of a 2D Point + + @param rPoint + The 2D Point which will be copied. + */ + B2IPoint(const B2IPoint& rPoint) + : B2ITuple(rPoint) + {} + + /** constructor with tuple to allow copy-constructing + from B2ITuple-based classes + */ + B2IPoint(const ::basegfx::B2ITuple& rTuple) + : B2ITuple(rTuple) + {} + + ~B2IPoint() + {} + + /** *=operator to allow usage from B2IPoint, too + */ + B2IPoint& operator*=( const B2IPoint& rPnt ) + { + mnX *= rPnt.mnX; + mnY *= rPnt.mnY; + return *this; + } + + /** *=operator to allow usage from B2IPoint, too + */ + B2IPoint& operator*=(sal_Int32 t) + { + mnX *= t; + mnY *= t; + return *this; + } + + /** assignment operator to allow assigning the results + of B2ITuple calculations + */ + B2IPoint& operator=( const ::basegfx::B2ITuple& rPoint ); + + /** Transform point by given transformation matrix. + + The translational components of the matrix are, in + contrast to B2DVector, applied. + */ + B2IPoint& operator*=( const ::basegfx::B2DHomMatrix& rMat ); + + static const B2IPoint& getEmptyPoint() + { + return (const B2IPoint&) ::basegfx::B2ITuple::getEmptyTuple(); + } + }; +} // end of namespace basegfx + +#endif /* _BGFX_POINT_B2IPOINT_HXX */ diff --git a/basegfx/inc/basegfx/point/b3dhompoint.hxx b/basegfx/inc/basegfx/point/b3dhompoint.hxx new file mode 100644 index 000000000000..7417d7528d16 --- /dev/null +++ b/basegfx/inc/basegfx/point/b3dhompoint.hxx @@ -0,0 +1,405 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POINT_B3DHOMPOINT_HXX +#define _BGFX_POINT_B3DHOMPOINT_HXX + +#include <basegfx/point/b3dpoint.hxx> + +namespace basegfx +{ + /** Basic homogen Point class with three double values and one homogen factor + + This class provides access to homogen coordinates in 3D. + For this purpose all the operators which need to do specific + action due to their homogenity are implemented here. + The only caveat are member methods which are declared as const + but do change the content. These are documented for that reason. + The class is designed to provide homogenous coordinates without + direct access to the homogen part (mfW). This is also the reason + for leaving out the [] operators which return references to members. + + @see B3DTuple + */ + class B3DHomPoint + { + protected: + /// This member contains the coordinate part of the point + ::basegfx::B3DTuple maTuple; + + /// This Member holds the homogenous part of the point + double mfW; + + /** Test if this homogen point does have a homogenous part + + @return Returns true if this point has no homogenous part + */ + bool implIsHomogenized() const + { + const double fOne(1.0); + return ::basegfx::fTools::equal(mfW, fOne); + } + + /** Remove homogenous part of this Point + + This method does necessary calculations to remove + the evtl. homogenous part of this Point. This may + change all members. + */ + void implHomogenize(); + + /** Test and on demand remove homogenous part + + This method tests if this Point does have a homogenous part + and then evtl. takes actions to remove that part. + + @attention Even when this method is const it may change all + members of this instance. This is due to the fact that changing + the homogenous part of a homogenous point does from a mathematical + point of view not change the point at all. + */ + void implTestAndHomogenize() const + { + if(!implIsHomogenized()) + ((B3DHomPoint*)this)->implHomogenize(); + } + + public: + /** Create a homogen point + + The point is initialized to (0.0, 0.0, 0.0) + */ + B3DHomPoint() + : maTuple(), + mfW(1.0) + {} + + /** Create a homogen point + + @param fX + This parameter is used to initialize the X-coordinate + of the Point. The homogenous part is initialized to 1.0. + + @param fY + This parameter is used to initialize the Y-coordinate + of the Point. The homogenous part is initialized to 1.0. + + @param fZ + This parameter is used to initialize the Z-coordinate + of the Point. The homogenous part is initialized to 1.0. + */ + B3DHomPoint(double fX, double fY, double fZ) + : maTuple(fX, fY, fZ), + mfW(1.0) + {} + + /** Create a copy of a 3D Point + + @param rVec + The 3D point which will be copied. The homogenous part + is initialized to 1.0. + */ + B3DHomPoint(const B3DPoint& rVec) + : maTuple(rVec), + mfW(1.0) + {} + + /** Create a copy of a homogen point + + @param rVec + The homogen point which will be copied. The homogenous part + is copied, too. + */ + B3DHomPoint(const B3DHomPoint& rVec) + : maTuple(rVec.maTuple.getX(), rVec.maTuple.getY(), rVec.maTuple.getZ()), + mfW(rVec.mfW) + {} + + ~B3DHomPoint() + {} + + /** get a 3D point from this homogenous point + + This method normalizes this homogen point if necessary and + returns the corresponding 3D point for this homogen point. + + @attention Even when this method is const it may change all + members of this instance. + */ + B3DPoint getB3DPoint() const + { + implTestAndHomogenize(); + return B3DPoint(maTuple.getX(), maTuple.getY(), maTuple.getZ()); + } + + /** get X-coordinate + + This method normalizes this homogen point if necessary and + returns the corresponding X-coordinate for this homogen point. + + @attention Even when this method is const it may change all + members of this instance. + */ + double getX() const + { + implTestAndHomogenize(); + return maTuple.getX(); + } + + /** get Y-coordinate + + This method normalizes this homogen point if necessary and + returns the corresponding Y-coordinate for this homogen point. + + @attention Even when this method is const it may change all + members of this instance. + */ + double getY() const + { + implTestAndHomogenize(); + return maTuple.getY(); + } + + /** get Z-coordinate + + This method normalizes this homogen point if necessary and + returns the corresponding Z-coordinate for this homogen point. + + @attention Even when this method is const it may change all + members of this instance. + */ + double getZ() const + { + implTestAndHomogenize(); + return maTuple.getY(); + } + + /** Set X-coordinate of the homogen point. + + This method sets the X-coordinate of the homogen point. If + the point does have a homogenous part this is taken into account. + + @param fX + The to-be-set X-coordinate without homogenous part. + */ + void setX(double fX) + { + maTuple.setX(implIsHomogenized() ? fX : fX * mfW ); + } + + /** Set Y-coordinate of the homogen point. + + This method sets the Y-coordinate of the homogen point. If + the point does have a homogenous part this is taken into account. + + @param fY + The to-be-set Y-coordinate without homogenous part. + */ + void setY(double fY) + { + maTuple.setY(implIsHomogenized() ? fY : fY * mfW ); + } + + /** Set Z-coordinate of the homogen point. + + This method sets the Z-coordinate of the homogen point. If + the point does have a homogenous part this is taken into account. + + @param fZ + The to-be-set Z-coordinate without homogenous part. + */ + void setZ(double fZ) + { + maTuple.setZ(implIsHomogenized() ? fZ : fZ * mfW ); + } + + // operators + ////////////////////////////////////////////////////////////////////// + + B3DHomPoint& operator+=( const B3DHomPoint& rPnt ) + { + maTuple.setX(getX() * rPnt.mfW + rPnt.getX() * mfW); + maTuple.setY(getY() * rPnt.mfW + rPnt.getY() * mfW); + maTuple.setZ(getZ() * rPnt.mfW + rPnt.getZ() * mfW); + mfW = mfW * rPnt.mfW; + + return *this; + } + + B3DHomPoint& operator-=( const B3DHomPoint& rPnt ) + { + maTuple.setX(getX() * rPnt.mfW - rPnt.getX() * mfW); + maTuple.setY(getY() * rPnt.mfW - rPnt.getY() * mfW); + maTuple.setZ(getZ() * rPnt.mfW - rPnt.getZ() * mfW); + mfW = mfW * rPnt.mfW; + + return *this; + } + + B3DHomPoint& operator*=(double t) + { + if(!::basegfx::fTools::equalZero(t)) + { + mfW /= t; + } + + return *this; + } + + B3DHomPoint& operator/=(double t) + { + mfW *= t; + return *this; + } + + B3DHomPoint& operator-(void) + { + mfW = -mfW; + return *this; + } + + bool operator==( const B3DHomPoint& rPnt ) const + { + implTestAndHomogenize(); + return (maTuple == rPnt.maTuple); + } + + bool operator!=( const B3DHomPoint& rPnt ) const + { + implTestAndHomogenize(); + return (maTuple != rPnt.maTuple); + } + + B3DHomPoint& operator=( const B3DHomPoint& rPnt ) + { + maTuple = rPnt.maTuple; + mfW = rPnt.mfW; + return *this; + } + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + inline B3DHomPoint minimum(const B3DHomPoint& rVecA, const B3DHomPoint& rVecB) + { + B3DHomPoint aMin( + (rVecB.getX() < rVecA.getX()) ? rVecB.getX() : rVecA.getX(), + (rVecB.getY() < rVecA.getY()) ? rVecB.getY() : rVecA.getY(), + (rVecB.getZ() < rVecA.getZ()) ? rVecB.getZ() : rVecA.getZ()); + return aMin; + } + + inline B3DHomPoint maximum(const B3DHomPoint& rVecA, const B3DHomPoint& rVecB) + { + B3DHomPoint aMax( + (rVecB.getX() > rVecA.getX()) ? rVecB.getX() : rVecA.getX(), + (rVecB.getY() > rVecA.getY()) ? rVecB.getY() : rVecA.getY(), + (rVecB.getZ() > rVecA.getZ()) ? rVecB.getZ() : rVecA.getZ()); + return aMax; + } + + inline B3DHomPoint absolute(const B3DHomPoint& rVec) + { + B3DHomPoint aAbs( + (0.0 > rVec.getX()) ? -rVec.getX() : rVec.getX(), + (0.0 > rVec.getY()) ? -rVec.getY() : rVec.getY(), + (0.0 > rVec.getZ()) ? -rVec.getZ() : rVec.getZ()); + return aAbs; + } + + inline B3DHomPoint interpolate(B3DHomPoint& rOld1, B3DHomPoint& rOld2, double t) + { + B3DHomPoint aInt( + ((rOld2.getX() - rOld1.getX()) * t) + rOld1.getX(), + ((rOld2.getY() - rOld1.getY()) * t) + rOld1.getY(), + ((rOld2.getZ() - rOld1.getZ()) * t) + rOld1.getZ()); + return aInt; + } + + inline B3DHomPoint average(B3DHomPoint& rOld1, B3DHomPoint& rOld2) + { + B3DHomPoint aAvg( + (rOld1.getX() + rOld2.getX()) * 0.5, + (rOld1.getY() + rOld2.getY()) * 0.5, + (rOld1.getZ() + rOld2.getZ()) * 0.5); + return aAvg; + } + + inline B3DHomPoint average(B3DHomPoint& rOld1, B3DHomPoint& rOld2, B3DHomPoint& rOld3) + { + B3DHomPoint aAvg( + (rOld1.getX() + rOld2.getX() + rOld3.getX()) * (1.0 / 3.0), + (rOld1.getY() + rOld2.getY() + rOld3.getY()) * (1.0 / 3.0), + (rOld1.getZ() + rOld2.getZ() + rOld3.getZ()) * (1.0 / 3.0)); + return aAvg; + } + + inline B3DHomPoint operator+(const B3DHomPoint& rVecA, const B3DHomPoint& rVecB) + { + B3DHomPoint aSum(rVecA); + aSum += rVecB; + return aSum; + } + + inline B3DHomPoint operator-(const B3DHomPoint& rVecA, const B3DHomPoint& rVecB) + { + B3DHomPoint aSub(rVecA); + aSub -= rVecB; + return aSub; + } + + inline B3DHomPoint operator*(const B3DHomPoint& rVec, double t) + { + B3DHomPoint aNew(rVec); + aNew *= t; + return aNew; + } + + inline B3DHomPoint operator*(double t, const B3DHomPoint& rVec) + { + B3DHomPoint aNew(rVec); + aNew *= t; + return aNew; + } + + inline B3DHomPoint operator/(const B3DHomPoint& rVec, double t) + { + B3DHomPoint aNew(rVec); + aNew /= t; + return aNew; + } + + inline B3DHomPoint operator/(double t, const B3DHomPoint& rVec) + { + B3DHomPoint aNew(rVec); + aNew /= t; + return aNew; + } +} // end of namespace basegfx + +#endif /* _BGFX_POINT_B3DHOMPOINT_HXX */ diff --git a/basegfx/inc/basegfx/point/b3dpoint.hxx b/basegfx/inc/basegfx/point/b3dpoint.hxx new file mode 100644 index 000000000000..943f74c1352d --- /dev/null +++ b/basegfx/inc/basegfx/point/b3dpoint.hxx @@ -0,0 +1,150 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POINT_B3DPOINT_HXX +#define _BGFX_POINT_B3DPOINT_HXX + +#include <basegfx/tuple/b3dtuple.hxx> + +namespace basegfx +{ + // predeclaration + class B3DHomMatrix; + + /** Base Point class with three double values + + This class derives all operators and common handling for + a 3D data class from B3DTuple. All necessary extensions + which are special for points will be added here. + + @see B3DTuple + */ + class B3DPoint : public ::basegfx::B3DTuple + { + public: + /** Create a 3D Point + + The point is initialized to (0.0, 0.0, 0.0) + */ + B3DPoint() + : B3DTuple() + {} + + /** Create a 3D Point + + @param fX + This parameter is used to initialize the X-coordinate + of the 3D Point. + + @param fY + This parameter is used to initialize the Y-coordinate + of the 3D Point. + + @param fZ + This parameter is used to initialize the Z-coordinate + of the 3D Point. + */ + B3DPoint(double fX, double fY, double fZ) + : B3DTuple(fX, fY, fZ) + {} + + /** Create a copy of a 3D Point + + @param rVec + The 3D Point which will be copied. + */ + B3DPoint(const B3DPoint& rVec) + : B3DTuple(rVec) + {} + + /** constructor with tuple to allow copy-constructing + from B3DTuple-based classes + */ + B3DPoint(const ::basegfx::B3DTuple& rTuple) + : B3DTuple(rTuple) + {} + + ~B3DPoint() + {} + + /** *=operator to allow usage from B3DPoint, too + */ + B3DPoint& operator*=( const B3DPoint& rPnt ) + { + mfX *= rPnt.mfX; + mfY *= rPnt.mfY; + mfZ *= rPnt.mfZ; + return *this; + } + + /** *=operator to allow usage from B3DPoint, too + */ + B3DPoint& operator*=(double t) + { + mfX *= t; + mfY *= t; + mfZ *= t; + return *this; + } + + /** assignment operator to allow assigning the results + of B3DTuple calculations + */ + B3DPoint& operator=( const ::basegfx::B3DTuple& rVec ) + { + mfX = rVec.getX(); + mfY = rVec.getY(); + mfZ = rVec.getZ(); + return *this; + } + + /** Transform point by given transformation matrix. + + The translational components of the matrix are, in + contrast to B3DVector, applied. + */ + B3DPoint& operator*=( const ::basegfx::B3DHomMatrix& rMat ); + + static const B3DPoint& getEmptyPoint() + { + return (const B3DPoint&) ::basegfx::B3DTuple::getEmptyTuple(); + } + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + /** Transform B3DPoint by given transformation matrix. + + Since this is a Point, translational components of the + matrix are used. + */ + B3DPoint operator*( const B3DHomMatrix& rMat, const B3DPoint& rPoint ); + +} // end of namespace basegfx + +#endif /* _BGFX_POINT_B3DPOINT_HXX */ diff --git a/basegfx/inc/basegfx/point/b3ipoint.hxx b/basegfx/inc/basegfx/point/b3ipoint.hxx new file mode 100644 index 000000000000..f20fe3d99c79 --- /dev/null +++ b/basegfx/inc/basegfx/point/b3ipoint.hxx @@ -0,0 +1,139 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POINT_B3IPOINT_HXX +#define _BGFX_POINT_B3IPOINT_HXX + +#include <basegfx/tuple/b3ituple.hxx> + +namespace basegfx +{ + // predeclaration + class B3DHomMatrix; + + /** Base Point class with three sal_Int32 values + + This class derives all operators and common handling for + a 3D data class from B3ITuple. All necessary extensions + which are special for points will be added here. + + @see B3ITuple + */ + class B3IPoint : public ::basegfx::B3ITuple + { + public: + /** Create a 3D Point + + The point is initialized to (0, 0, 0) + */ + B3IPoint() + : B3ITuple() + {} + + /** Create a 3D Point + + @param nX + This parameter is used to initialize the X-coordinate + of the 3D Point. + + @param nY + This parameter is used to initialize the Y-coordinate + of the 3D Point. + + @param nZ + This parameter is used to initialize the Z-coordinate + of the 3D Point. + */ + B3IPoint(sal_Int32 nX, sal_Int32 nY, sal_Int32 nZ) + : B3ITuple(nX, nY, nZ) + {} + + /** Create a copy of a 3D Point + + @param rVec + The 3D Point which will be copied. + */ + B3IPoint(const B3IPoint& rVec) + : B3ITuple(rVec) + {} + + /** constructor with tuple to allow copy-constructing + from B3ITuple-based classes + */ + B3IPoint(const ::basegfx::B3ITuple& rTuple) + : B3ITuple(rTuple) + {} + + ~B3IPoint() + {} + + /** *=operator to allow usage from B3IPoint, too + */ + B3IPoint& operator*=( const B3IPoint& rPnt ) + { + mnX *= rPnt.mnX; + mnY *= rPnt.mnY; + mnZ *= rPnt.mnZ; + return *this; + } + + /** *=operator to allow usage from B3IPoint, too + */ + B3IPoint& operator*=(sal_Int32 t) + { + mnX *= t; + mnY *= t; + mnZ *= t; + return *this; + } + + /** assignment operator to allow assigning the results + of B3ITuple calculations + */ + B3IPoint& operator=( const ::basegfx::B3ITuple& rVec ) + { + mnX = rVec.getX(); + mnY = rVec.getY(); + mnZ = rVec.getZ(); + return *this; + } + + /** Transform point by given transformation matrix. + + The translational components of the matrix are, in + contrast to B3DVector, applied. + */ + B3IPoint& operator*=( const ::basegfx::B3DHomMatrix& rMat ); + + static const B3IPoint& getEmptyPoint() + { + return (const B3IPoint&) ::basegfx::B3ITuple::getEmptyTuple(); + } + }; +} // end of namespace basegfx + +#endif /* _BGFX_POINT_B3IPOINT_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dlinegeometry.hxx b/basegfx/inc/basegfx/polygon/b2dlinegeometry.hxx new file mode 100644 index 000000000000..fd8997fcda4d --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dlinegeometry.hxx @@ -0,0 +1,144 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B2DLINEGEOMETRY_HXX +#define _BGFX_POLYGON_B2DLINEGEOMETRY_HXX + +#include <sal/types.h> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + /** Create line start/end geometry element, mostly arrows and things like that. + + @param rCandidate + The polygon which needs to get that line ends and needs to have two points + at least. + + @param rArrow + The line start/end geometry. It is assumed that the tip is pointing + upwards. Result will be rotated and scaled to fit. + + @param bStart + describes if creation is for start or end of candidate. + + @param fWidth + defines the size of the element, it's describing the target width in X + of the arrow. + + @param fDockingPosition needs to be in [0.0 ..1.0] range, where 0.0 means + that the tip of the arrow will be aligned with the polygon start, 1.0 means + the bottom. The default of 0.5 describes a centered arrow. + + @param pConsumedLength + Using this parameter it is possible to get back how much from the candidate + geometry is overlapped by the created element (consumed). + + @param fCandidateLength + This should contain the length of rCandidate to allow work without + again calculating the length (which may be expensive with beziers). If 0.0 is + given, the length is calculated on demand. + + @return + The Line start and end polygon, correctly rotated and scaled + */ + B2DPolyPolygon createAreaGeometryForLineStartEnd( + const B2DPolygon& rCandidate, + const B2DPolyPolygon& rArrow, + bool bStart, + double fWidth, + double fCandidateLength = 0.0, // 0.0 -> calculate self + double fDockingPosition = 0.5, // 0->top, 1->bottom + double* pConsumedLength = 0L); + + /** create filled polygon geometry for lines with a line width + + This method will create bezier based, fillable polygons which + will resample the curve if it was extended for the given half + line width. It will remove extrema positions from contained + bezier segments and get as close as possible and defined by + the given parameters to the ideal result. + + It will check edges for trivial bezier to avoid unnecessary + bezier polygons. Care is taken to produce the in-between + polygon points (the ones original on the source poygon) since + it has showed that without those, the raster converters leave + non-filled gaps. + + @param rCandidate + The source polygon defining the hairline polygon path + + @param fHalfLineWidth + The width of the line to one side + + @param eJoin + The LineJoin if the edges meeting in a point do not have a C1 + or C2 continuity + + @param fMaxAllowedAngle + Allows to hand over the maximum allowed angle between an edge and + it's control vectors. The smaller, the more subdivisions will be + needed to create the filled geometry. Allowed range is cropped to + [F_PI2 .. 0.01 * F_PI2]. + + @param fMaxPartOfEdge + Allows to influence from with relative length of a control vector + compared to it's edge a split is forced. The smaller, the more + subdivisions will be needed to create the filled geometry. Allowed + range is cropped to [1.0 .. 0.01] + + @praram fMiterMinimumAngle + The minimum wanted angle between two edges when edge rounding + is using miter. When an edge is smaller than this (tighter) + the usual fallback to bevel is used. Allowed range is cropped + to [F_PI .. 0.01 * F_PI]. + + @return + The PolyPolygon containing the geometry of the extended line by + it's line width. Contains bezier segments and edge roundings as + needed and defined. + */ + B2DPolyPolygon createAreaGeometry( + const B2DPolygon& rCandidate, + double fHalfLineWidth, + B2DLineJoin eJoin = B2DLINEJOIN_ROUND, + double fMaxAllowedAngle = (12.5 * F_PI180), + double fMaxPartOfEdge = 0.4, + double fMiterMinimumAngle = (15.0 * F_PI180)); + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +#endif /* _BGFX_POLYGON_B2DLINEGEOMETRY_HXX */ +// eof diff --git a/basegfx/inc/basegfx/polygon/b2dpolygon.hxx b/basegfx/inc/basegfx/polygon/b2dpolygon.hxx new file mode 100644 index 000000000000..a12120b5f441 --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dpolygon.hxx @@ -0,0 +1,274 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B2DPOLYGON_HXX +#define _BGFX_POLYGON_B2DPOLYGON_HXX + +#include <sal/types.h> +#include <o3tl/cow_wrapper.hxx> +#include <basegfx/vector/b2enums.hxx> +#include <basegfx/range/b2drange.hxx> + +////////////////////////////////////////////////////////////////////////////// +// predeclarations +class ImplB2DPolygon; + +namespace basegfx +{ + class B2DPolygon; + class B2DPoint; + class B2DVector; + class B2DHomMatrix; + class B2DCubicBezier; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class B2DPolygon + { + public: + typedef o3tl::cow_wrapper< ImplB2DPolygon > ImplType; + + private: + // internal data. + ImplType mpPolygon; + + public: + /// diverse constructors + B2DPolygon(); + B2DPolygon(const B2DPolygon& rPolygon); + B2DPolygon(const B2DPolygon& rPolygon, sal_uInt32 nIndex, sal_uInt32 nCount); + ~B2DPolygon(); + + /// assignment operator + B2DPolygon& operator=(const B2DPolygon& rPolygon); + + /// unshare this polygon with all internally shared instances + void makeUnique(); + + /// compare operators + bool operator==(const B2DPolygon& rPolygon) const; + bool operator!=(const B2DPolygon& rPolygon) const; + + /// member count + sal_uInt32 count() const; + + /// Coordinate interface + basegfx::B2DPoint getB2DPoint(sal_uInt32 nIndex) const; + void setB2DPoint(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue); + + /// Coordinate insert/append + void insert(sal_uInt32 nIndex, const basegfx::B2DPoint& rPoint, sal_uInt32 nCount = 1); + void append(const basegfx::B2DPoint& rPoint, sal_uInt32 nCount); + void append(const basegfx::B2DPoint& rPoint); + void reserve(sal_uInt32 nCount); + + /// Basic ControlPoint interface + basegfx::B2DPoint getPrevControlPoint(sal_uInt32 nIndex) const; + basegfx::B2DPoint getNextControlPoint(sal_uInt32 nIndex) const; + void setPrevControlPoint(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue); + void setNextControlPoint(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue); + void setControlPoints(sal_uInt32 nIndex, const basegfx::B2DPoint& rPrev, const basegfx::B2DPoint& rNext); + + /// ControlPoint resets + void resetPrevControlPoint(sal_uInt32 nIndex); + void resetNextControlPoint(sal_uInt32 nIndex); + void resetControlPoints(sal_uInt32 nIndex); + void resetControlPoints(); + + /// Bezier segment append with control points. The current last polygon point is implicitly taken as start point. + void appendBezierSegment(const basegfx::B2DPoint& rNextControlPoint, const basegfx::B2DPoint& rPrevControlPoint, const basegfx::B2DPoint& rPoint); + + /// ControlPoint checks + bool areControlPointsUsed() const; + bool isPrevControlPointUsed(sal_uInt32 nIndex) const; + bool isNextControlPointUsed(sal_uInt32 nIndex) const; + B2VectorContinuity getContinuityInPoint(sal_uInt32 nIndex) const; + + /** check edge for being a bezier segment + + This test the existance of control vectors, but do not apply + testAndSolveTrivialBezier() to the bezier segment, so it is still useful + to do so. + Since it can use internal data representations, it is faster + than using getBezierSegment() and applying isBezier() on it. + + @param nIndex + Index of the addressed edge's start point + + @return + true if edge exists and at least one control vector is used + */ + bool isBezierSegment(sal_uInt32 nIndex) const; + + /** bezier segment access + + This method also works when it is no bezier segment at all and will fill + the given B2DCubicBezier as needed. + In any case, the given B2DCubicBezier will be filled, if necessary with + the single start point (if no valid edge exists). + + @param nIndex + Index of the addressed edge's start point + + @param rTarget + The B2DCubicBezier to be filled. It's data WILL be changed. + */ + void getBezierSegment(sal_uInt32 nIndex, B2DCubicBezier& rTarget) const; + + /** Default adaptive subdivision access + + This method will return a default adapive subdivision of the polygon. + If the polygon does not contain any bezier curve segments, it will + just return itself. + + The subdivision is created on first request and buffered, so when using + this subdivision You have the guarantee for fast accesses for multiple + usages. It is intended for tooling usage for tasks which would be hard + to accomplish on bezier segments (e.g. isInEpsilonRange). + + The current default subdivision uses adaptiveSubdivideByCount with 9 + subdivisions which gives 10 edges and 11 points per segment and is + usually pretty usable for processing purposes. There is no parameter + passing here ATM but it may be changed on demand. If needed, a TYPE + and PARAMETER (both defaulted) may be added to allow for switching + between the different kinds of subdivisiond and passing them one + parameter. + + The lifetime of the buffered subdivision is based on polygon changes. + When changing the polygon, it will be flushed. It is buffered at the + refcounted implementation class, so it will survive copy by value and + combinations in PolyPolygons. + + @return + The default (and buffered) subdivision of this polygon. It may + be this polygon itself when it has no bezier segments. It is guaranteed + to have no more bezier segments + */ + B2DPolygon getDefaultAdaptiveSubdivision() const; + + /** Get the B2DRange (Rectangle dimensions) of this B2DPolygon + + A polygon may have up to three ranges: + + (a) the range of the polygon points + (b) the range of the polygon points and control points + (c) the outer range of the subdivided bezier curve + + Ranges (a) and (c) are produced by tools::getRange(); resp. this + getB2DRange(). tools::getRangeWithControlPoints handles case (b). + + To get range (c) a simple solution would be to subdivide the polygon + and use getRange() on it. Since subdivision is expensive and decreases + the polygon quality, i added this new method. It will use a + methodology suggested by HDU. First, it gets the range (a). + Then it iterates over the bezier segments and for each it + first tests if the outer range of the bezier segment is already + contained in the result range. + + The subdivision itself uses getAllExtremumPositions() to only + calculate extremum points and to expand the result accordingly. + Thus it calculates maximal four extremum points on the bezier + segment, no split is used at all. + + @return + The outer range of the bezier curve/polygon + */ + B2DRange getB2DRange() const; + + /** insert other 2D polygons + + The default (with nIndex2 == 0 && nCount == 0) inserts the whole + rPoly at position nIndex + + @param nIndex + Target index for points to be inserted + + @param rPoly + The source for new points + + @param nIndex2 + The index to the first source point into rPoly + + @param nCount + How many points to add from rPoly to this polygon. Null + means to copy all (starting from nIndex2) + */ + void insert(sal_uInt32 nIndex, const B2DPolygon& rPoly, sal_uInt32 nIndex2 = 0, sal_uInt32 nCount = 0); + + /** append other 2D polygons + + The default (nIndex ==0 && nCount == 0) will append + the whole rPoly + + @param rPoly + The source polygon + + @param nIndex + The index to the first point of rPoly to append + + @param nCount + The number of points to append from rPoly, starting + from nIndex. If zero, as much as possibel is appended + */ + void append(const B2DPolygon& rPoly, sal_uInt32 nIndex = 0, sal_uInt32 nCount = 0); + + /// remove points + void remove(sal_uInt32 nIndex, sal_uInt32 nCount = 1); + + /// clear all points + void clear(); + + /// closed state interface + bool isClosed() const; + void setClosed(bool bNew); + + /// flip polygon direction + void flip(); + + /// test if Polygon has double points + bool hasDoublePoints() const; + + /// remove double points, at the begin/end and follow-ups, too + void removeDoublePoints(); + + /// apply transformation given in matrix form + void transform(const basegfx::B2DHomMatrix& rMatrix); + + // point iterators (same iterator validity conditions as for vector) + const B2DPoint* begin() const; + const B2DPoint* end() const; + B2DPoint* begin(); + B2DPoint* end(); + }; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +#endif /* _BGFX_POLYGON_B2DPOLYGON_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dpolygonclipper.hxx b/basegfx/inc/basegfx/polygon/b2dpolygonclipper.hxx new file mode 100644 index 000000000000..d13d3d0a862c --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dpolygonclipper.hxx @@ -0,0 +1,82 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYPOLYGON_B2DPOLYGONCLIPPER_HXX +#define _BGFX_POLYPOLYGON_B2DPOLYGONCLIPPER_HXX + +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + // predefinitions + class B2DRange; + + namespace tools + { + // This method clips the given PolyPolygon against a horizontal or vertical axis (parallell to X or Y axis). The axis is + // defined by bParallelToXAxis (true -> it's parallel to the X-Axis of the coordinate system, else to the Y-Axis) and the + // fValueOnOtherAxis (gives the translation to the coordinate system axis). For example, when You want to define + // a clip axis parallel to X.Axis and 100 above it, use bParallelToXAxis = true and fValueOnOtherAxis = 100. + // The value bAboveAxis defines on which side the return value will be (true -> above X, right of Y). + // The switch bStroke decides if the polygon is interpreted as area (false) or strokes (true). + B2DPolyPolygon clipPolyPolygonOnParallelAxis(const B2DPolyPolygon& rCandidate, bool bParallelToXAxis, bool bAboveAxis, double fValueOnOtherAxis, bool bStroke); + B2DPolyPolygon clipPolygonOnParallelAxis(const B2DPolygon& rCandidate, bool bParallelToXAxis, bool bAboveAxis, double fValueOnOtherAxis, bool bStroke); + + // Clip the given PolyPolygon against the given range. bInside defines if the result will contain the + // parts which are contained in the range or vice versa. + // The switch bStroke decides if the polygon is interpreted as area (false) or strokes (true). + B2DPolyPolygon clipPolyPolygonOnRange(const B2DPolyPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke); + B2DPolyPolygon clipPolygonOnRange(const B2DPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke); + + // Clip given PolyPolygon against the endless edge (ray) defined by the given two points. bAbove defines on which side + // of the edge the result will be together with the definition of the edge. If the edge is seen as a vector + // from A to B and bAbove is true, the result will contain the geometry left of the vector. + // The switch bStroke decides if the polygon is interpreted as area (false) or strokes (true). + B2DPolyPolygon clipPolyPolygonOnEdge(const B2DPolyPolygon& rCandidate, const B2DPoint& rPointA, const B2DPoint& rPointB, bool bAbove, bool bStroke); + B2DPolyPolygon clipPolygonOnEdge(const B2DPolygon& rCandidate, const B2DPoint& rPointA, const B2DPoint& rPointB, bool bAbove, bool bStroke); + + // Clip given PolyPolygon against given clipping polygon. + // The switch bStroke decides if the polygon is interpreted as area (false) or strokes (true). + // With stroke polygons, You get all line snippets inside rCip. + // With filled polygons, You get all PolyPolygon parts which were inside rClip. + // The switch bInside decides if the parts inside the clip polygon or outside shall be created. + // The clip polygon is always assumed closed, even when it's isClosed() is false. + B2DPolyPolygon clipPolyPolygonOnPolyPolygon(const B2DPolyPolygon& rCandidate, const B2DPolyPolygon& rClip, bool bInside, bool bStroke); + B2DPolyPolygon clipPolygonOnPolyPolygon(const B2DPolygon& rCandidate, const B2DPolyPolygon& rClip, bool bInside, bool bStroke); + + // clip the given polygon against the given range. the resulting polygon will always contain + // the inside parts which will always be interpreted as areas. the incoming polygon is expected + // to be a simple triangle list. the result is also a simple triangle list. + B2DPolygon clipTriangleListOnRange( const B2DPolygon& rCandidate, const B2DRange& rRange ); + + } // end of namespace tools +} // end of namespace basegfx + +#endif /* _BGFX_POLYPOLYGON_B2DPOLYGONCLIPPER_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dpolygoncutandtouch.hxx b/basegfx/inc/basegfx/polygon/b2dpolygoncutandtouch.hxx new file mode 100644 index 000000000000..a9d6e0a1b6fe --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dpolygoncutandtouch.hxx @@ -0,0 +1,81 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_CUTANDTOUCH_HXX +#define _BGFX_POLYGON_CUTANDTOUCH_HXX + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + // look for self-intersections and self-touches (points on an edge) in given polygon and add + // extra points there. Result will have no touches or intersections on an edge, only on points + B2DPolygon addPointsAtCutsAndTouches(const B2DPolygon& rCandidate); + + // look for polypolygon-intersections and polypolygon-touches (point of poly A on an edge of poly B) in given PolyPolygon and add + // extra points there. Result will have no touches or intersections between contained polygons on an edge, only on points. For + // convenience, the correction for self-intersections for each member polygon will be used, too. + // Changed: Self intersections are searched by default, but may be switched off by 2nd parameter. + B2DPolyPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rCandidate, bool bSelfIntersections = true); + + // look for intersections of rCandidate with all polygons from rMask and add extra points there. Do + // not change or add points to rMask. + B2DPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rMask, const B2DPolygon& rCandidate); + + // look for intersections of rCandidate with all polygons from rMask and add extra points there. Do + // not change or add points to rMask. + B2DPolyPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rMask, const B2DPolyPolygon& rCandidate); + + // look for intersections of rCandidate with the edge from rStart to rEnd and add extra points there. + // Points are only added in the range of the edge, not on the endless vector. + B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate, const B2DPoint& rStart, const B2DPoint& rEnd); + B2DPolyPolygon addPointsAtCuts(const B2DPolyPolygon& rCandidate, const B2DPoint& rStart, const B2DPoint& rEnd); + + // look for intersections of rCandidate with the mask Polygon and add extra points there. + // The mask polygon is assumed to be closed, even when it's not explicitely. + B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate, const B2DPolyPolygon& rMask); + B2DPolyPolygon addPointsAtCuts(const B2DPolyPolygon& rCandidate, const B2DPolyPolygon& rMask); + + // look for self-intersections in given polygon and add extra points there. Result will have no + // intersections on an edge + B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate); + + // add points at all self-intersections of single polygons (depends on bSelfIntersections) + // and at polygon-polygon intersections + B2DPolyPolygon addPointsAtCuts(const B2DPolyPolygon& rCandidate, bool bSelfIntersections = true); + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +#endif /* _BGFX_POLYGON_CUTANDTOUCH_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dpolygontools.hxx b/basegfx/inc/basegfx/polygon/b2dpolygontools.hxx new file mode 100644 index 000000000000..7d1d0bc9660c --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dpolygontools.hxx @@ -0,0 +1,534 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B2DPOLYGONTOOLS_HXX +#define _BGFX_POLYGON_B2DPOLYGONTOOLS_HXX + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <vector> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + // predefinitions + class B2DPolygon; + class B2DRange; + + namespace tools + { + // B2DPolygon tools + + // open/close with point add/remove and control point corrections + void openWithGeometryChange(B2DPolygon& rCandidate); + void closeWithGeometryChange(B2DPolygon& rCandidate); + + /** Check if given polygon is closed. + + This is kind of a 'classic' method to support old polygon + definitions. Those old polygon definitions define the + closed state of the polygon using identical start and + endpoints. This method corrects this (removes double + start/end points) and sets the Closed()-state of the + polygon correctly. + */ + void checkClosed(B2DPolygon& 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); + sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate); + + // Get orientation of Polygon + B2VectorOrientation getOrientation(const B2DPolygon& rCandidate); + + // isInside tests for B2dPoint and other B2dPolygon. On border is not inside as long as + // not true is given in bWithBorder flag. + bool isInside(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder = false); + bool isInside(const B2DPolygon& rCandidate, const B2DPolygon& rPolygon, bool bWithBorder = false); + + /** Get the range of a polygon including bezier control points + + For detailed discussion, see B2DPolygon::getB2DRange() + + @param rCandidate + The B2DPolygon eventually containing bezier segments + + @return + The outer range of the bezier curve containing bezier control points + */ + B2DRange getRangeWithControlPoints(const B2DPolygon& rCandidate); + + /** Get the range of a polygon + + This method creates the outer range of the subdivided bezier curve. + For detailed discussion see B2DPolygon::getB2DRange() + + @param rCandidate + The B2DPolygon eventually containing bezier segments + + @return + The outer range of the bezier curve + */ + B2DRange getRange(const B2DPolygon& rCandidate); + + // get signed area of polygon + double getSignedArea(const B2DPolygon& rCandidate); + + // get area of polygon + double getArea(const B2DPolygon& rCandidate); + + /** get length of polygon edge from point nIndex to nIndex + 1 */ + double getEdgeLength(const B2DPolygon& rCandidate, sal_uInt32 nIndex); + + /** get length of polygon */ + double getLength(const B2DPolygon& rCandidate); + + // get position on polygon for absolute given distance. If + // length is given, it is assumed the correct polygon length, if 0.0 it is calculated + // using getLength(...) + B2DPoint getPositionAbsolute(const B2DPolygon& rCandidate, double fDistance, double fLength = 0.0); + + // get position on polygon for relative given distance in range [0.0 .. 1.0]. If + // length is given, it is assumed the correct polygon length, if 0.0 it is calculated + // using getLength(...) + B2DPoint getPositionRelative(const B2DPolygon& rCandidate, double fDistance, double fLength = 0.0); + + // get a snippet from given polygon for absolute distances. The polygon is assumed + // to be opened (not closed). fFrom and fTo need to be in range [0.0 .. fLength], where + // fTo >= fFrom. If length is given, it is assumed the correct polygon length, + // if 0.0 it is calculated using getLength(...) + B2DPolygon getSnippetAbsolute(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength = 0.0); + + // get a snippet from given polygon for relative distances. The polygon is assumed + // to be opened (not closed). fFrom and fTo need to be in range [0.0 .. 1.0], where + // fTo >= fFrom. If length is given, it is assumed the correct polygon length, + // if 0.0 it is calculated using getLength(...) + B2DPolygon getSnippetRelative(const B2DPolygon& rCandidate, double fFrom = 0.0, double fTo = 1.0, double fLength = 0.0); + + // Continuity check for point with given index + B2VectorContinuity getContinuityInPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex); + + // Subdivide all contained curves. Use distanceBound value if given. + B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound = 0.0); + + // Subdivide all contained curves. Use angleBound value if given. + B2DPolygon adaptiveSubdivideByAngle(const B2DPolygon& rCandidate, double fAngleBound = 0.0); + + // #i37443# Subdivide all contained curves. + B2DPolygon adaptiveSubdivideByCount(const B2DPolygon& rCandidate, sal_uInt32 nCount = 0L); + + // Definitions for the cut flags used from the findCut methods + typedef sal_uInt16 CutFlagValue; + + #define CUTFLAG_NONE (0x0000) + #define CUTFLAG_LINE (0x0001) + #define CUTFLAG_START1 (0x0002) + #define CUTFLAG_START2 (0x0004) + #define CUTFLAG_END1 (0x0008) + #define CUTFLAG_END2 (0x0010) + #define CUTFLAG_ALL (CUTFLAG_LINE|CUTFLAG_START1|CUTFLAG_START2|CUTFLAG_END1|CUTFLAG_END2) + #define CUTFLAG_DEFAULT (CUTFLAG_LINE|CUTFLAG_START2|CUTFLAG_END2) + + // Calculate cut between the points given by the two indices. pCut1 + // and pCut2 will contain the cut coordinate on each edge in ]0.0, 1.0] + // (if given) and the return value will contain a cut description. + CutFlagValue findCut( + const B2DPolygon& rCandidate, + sal_uInt32 nIndex1, sal_uInt32 nIndex2, + CutFlagValue aCutFlags = CUTFLAG_DEFAULT, + double* pCut1 = 0L, double* pCut2 = 0L); + + // This version is working with two indexed edges from different + // polygons. + CutFlagValue findCut( + const B2DPolygon& rCandidate1, sal_uInt32 nIndex1, + const B2DPolygon& rCandidate2, sal_uInt32 nIndex2, + CutFlagValue aCutFlags = CUTFLAG_DEFAULT, + double* pCut1 = 0L, double* pCut2 = 0L); + + // This version works with two points and vectors to define the + // edges for the cut test. + CutFlagValue findCut( + const B2DPoint& rEdge1Start, const B2DVector& rEdge1Delta, + const B2DPoint& rEdge2Start, const B2DVector& rEdge2Delta, + CutFlagValue aCutFlags = CUTFLAG_DEFAULT, + double* pCut1 = 0L, double* pCut2 = 0L); + + // test if point is on the given edge in range ]0.0..1.0[ without + // the start/end points. If so, return true and put the parameter + // value in pCut (if provided) + bool isPointOnEdge( + const B2DPoint& rPoint, + const B2DPoint& rEdgeStart, + const B2DVector& rEdgeDelta, + double* pCut = 0L); + + /** Apply given LineDashing to given polygon + + This method is used to cut down line polygons to the needed + pieces when a dashing needs to be applied. + It is now capable of keeping contained bezier segments. + It is also capable of delivering line and non-line portions + depending on what target polygons You provide. This is useful + e.g. for dashed lines with two colors. + If the last and the first snippet in one of the results have + a common start/end ppoint, they will be merged to achieve as + view as needed result line snippets. This is also relevant for + further processing the results. + + @param rCandidate + The polygon based on which the snippets will be created. + + @param rDotDashArray + The line pattern given as array of length values + + @param pLineTarget + The target for line snippets, e.g. the first entry will be + a line segment with length rDotDashArray[0]. The given + polygon will be emptied as preparation. + + @param pGapTarget + The target for gap snippets, e.g. the first entry will be + a line segment with length rDotDashArray[1]. The given + polygon will be emptied as preparation. + + @param fFullDashDotLen + The sumed-up length of the rDotDashArray. If zero, it will + be calculated internally. + */ + void applyLineDashing( + const B2DPolygon& rCandidate, + const ::std::vector<double>& rDotDashArray, + B2DPolyPolygon* pLineTarget, + B2DPolyPolygon* pGapTarget = 0, + double fFullDashDotLen = 0.0); + + // 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); + + // test if point is inside epsilon-range around the given Polygon. 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 B2DPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance); + + /** Create a polygon from a rectangle. + + @param rRect + The rectangle which describes the polygon size + + @param fRadius + Radius of the edge rounding, relative to the rectangle size. 0.0 means no + rounding, 1.0 will lead to an ellipse + */ + B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadius ); + + /** Create a polygon from a rectangle. + + @param rRect + The rectangle which describes the polygon size + + @param fRadiusX + @param fRadiusY + Radius of the edge rounding, relative to the rectangle size. 0.0 means no + rounding, 1.0 will lead to an ellipse + */ + B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadiusX, double fRadiusY ); + + /** Create a polygon from a rectangle. + */ + B2DPolygon createPolygonFromRect( const B2DRectangle& rRect ); + + /** Create the unit polygon + */ + B2DPolygon createUnitPolygon(); + + /** Create a circle polygon with given radius. + + This method creates a circle approximation consisting of + four cubic bezier segments, which approximate the given + circle with an error of less than 0.5 percent. + + @param rCenter + Center point of the circle + + @param fRadius + Radius of the circle + */ + B2DPolygon createPolygonFromCircle( const B2DPoint& rCenter, double fRadius ); + + /** create a polygon which describes the unit circle and close it + + @param nStartQuadrant + To be able to rebuild the old behaviour where the circles started at bottom, + this parameter is used. Default is 0 which is the first quadrant and the + polygon's start point will be the rightmost one. When using e.g. 1, the + first created quadrant will start at the YMax-position (with Y down on screens, + this is the lowest one). This is needed since when lines are dashed, toe old + geometry started at bottom point, else it would look different. + */ + B2DPolygon createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant = 0); + + /** Create an ellipse polygon with given radii. + + This method creates an ellipse approximation consisting of + four cubic bezier segments, which approximate the given + ellipse with an error of less than 0.5 percent. + + @param rCenter + Center point of the circle + + @param fRadiusX + Radius of the ellipse in X direction + + @param fRadiusY + Radius of the ellipse in Y direction + */ + B2DPolygon createPolygonFromEllipse( const B2DPoint& rCenter, double fRadiusX, double fRadiusY ); + + /** Create an unit ellipse polygon with the given angles, from start to end + */ + B2DPolygon createPolygonFromEllipseSegment( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd ); + + B2DPolygon createPolygonFromUnitEllipseSegment( double fStart, double fEnd ); + + /** Predicate whether a given polygon is a rectangle. + + @param rPoly + Polygon to check + + @return true, if the polygon describes a rectangle + (polygon is closed, and the points are either cw or ccw + enumerations of a rectangle's vertices). Note that + intermediate points and duplicate points are ignored. + */ + bool isRectangle( const B2DPolygon& rPoly ); + + // create 3d polygon from given 2d polygon. The given fZCoordinate is used to expand the + // third coordinate. + B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon& rCandidate, double fZCoordinate = 0.0); + + // create 2d PolyPolygon from given 3d PolyPolygon. All coordinates are transformed using the given + // matrix and the resulting x,y is used to form the new polygon. + B2DPolygon createB2DPolygonFromB3DPolygon(const B3DPolygon& rCandidate, const B3DHomMatrix& rMat); + + // 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&); + + // calculate the distance to the given endless ray and return. The relative position on the edge is returned in Cut. + // That position may be less than 0.0 or more than 1.0 + double getDistancePointToEndlessRay(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut); + + // calculate the smallest distance to given edge and return. The relative position on the edge is returned in Cut. + // That position is in the range [0.0 .. 1.0] and the returned distance is adapted accordingly to the start or end + // point of the edge + double getSmallestDistancePointToEdge(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut); + + // for each contained edge calculate the smallest distance. Return the index to the smallest + // edge in rEdgeIndex. The relative position on the edge is returned in rCut. + // If nothing was found (e.g. empty input plygon), DBL_MAX is returned. + double getSmallestDistancePointToPolygon(const B2DPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rEdgeIndex, double& rCut); + + // distort single point. rOriginal describes the original range, where the given points describe the distorted corresponding points. + B2DPoint distort(const B2DPoint& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight); + + // distort polygon. rOriginal describes the original range, where the given points describe the distorted corresponding points. + B2DPolygon distort(const B2DPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight); + + // rotate polygon around given point with given angle. + B2DPolygon rotateAroundPoint(const B2DPolygon& rCandidate, const B2DPoint& rCenter, double fAngle); + + // expand all segments (which are not yet) to curve segments. This is done with setting the control + // vectors on the 1/3 resp. 2/3 distances on each segment. + B2DPolygon expandToCurve(const B2DPolygon& rCandidate); + + // expand given segment to curve segment. This is done with setting the control + // vectors on the 1/3 resp. 2/3 distances. The return value describes if a change took place. + bool expandToCurveInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex); + + // set continuity for the whole curve. If not a curve, nothing will change. Non-curve points are not changed, too. + B2DPolygon setContinuity(const B2DPolygon& rCandidate, B2VectorContinuity eContinuity); + + // set continuity for given index. If not a curve, nothing will change. Non-curve points are not changed, too. + // The return value describes if a change took place. + bool setContinuityInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity); + + // test if polygon contains neutral points. A neutral point is one whos orientation is neutral + // e.g. positioned on the edge of it's predecessor and successor + bool hasNeutralPoints(const B2DPolygon& rCandidate); + + // remove neutral points. A neutral point is one whos orientation is neutral + // e.g. positioned on the edge of it's predecessor and successor + B2DPolygon removeNeutralPoints(const B2DPolygon& rCandidate); + + // tests if polygon is convex + bool isConvex(const B2DPolygon& rCandidate); + + // calculates the orientation at edge nIndex + B2VectorOrientation getOrientationForIndex(const B2DPolygon& rCandidate, sal_uInt32 nIndex); + + // calculates if given point is on given line, taking care of the numerical epsilon + bool isPointOnLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidate, bool bWithPoints = false); + + // calculates if given point is on given polygon, taking care of the numerical epsilon. Uses + // isPointOnLine internally + bool isPointOnPolygon(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithPoints = true); + + // test if candidate is inside triangle + bool isPointInTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC, const B2DPoint& rCandidate, bool bWithBorder = false); + + // test if candidateA and candidateB are on the same side of the given line + bool arePointsOnSameSideOfLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidateA, const B2DPoint& rCandidateB, bool bWithLine = false); + + // add triangles for given rCandidate to rTarget. For each triangle, 3 points will be added to rCandidate. + // All triangles will go from the start point of rCandidate to two consecutive points, building (rCandidate.count() - 2) + // triangles. + void addTriangleFan(const B2DPolygon& rCandidate, B2DPolygon& rTarget); + + // grow for polygon. Move all geometry in each point in the direction of the normal in that point + // with the given amount. Value may be negative. + B2DPolygon growInNormalDirection(const B2DPolygon& rCandidate, double fValue); + + // force all sub-polygons to a point count of nSegments + B2DPolygon reSegmentPolygon(const B2DPolygon& rCandidate, sal_uInt32 nSegments); + + // create polygon state at t from 0.0 to 1.0 between the two polygons. Both polygons must have the same + // organisation, e.g. same amount of points + B2DPolygon interpolate(const B2DPolygon& rOld1, const B2DPolygon& rOld2, double t); + + bool isPolyPolygonEqualRectangle( const B2DPolyPolygon& rPolyPoly, const B2DRange& rRect ); + + // #i76891# Try to remove existing curve segments if they are simply edges + B2DPolygon simplifyCurveSegments(const B2DPolygon& 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); + + /** create edges of given length along given B2DPolygon + + @param rCandidate + The polygon to move along. Points at the given polygon are created, starting + at position fStart and stopping at less or equal to fEnd. The closed state is + preserved. + The polygon is subdivided if curve segments are included. That subdivision is the base + for the newly created points. + If the source is closed, the indirectly existing last edge may NOT have the + given length. + If the source is open, all edges will have the given length. You may use the last + point of the original when You want to add the last edge Yourself. + + @param fLength + The length of the created edges. If less or equal zero, an empty polygon is returned. + + @param fStart + The start distance for the first to be generated point. Use 0.0 to get the + original start point. Negative values are truncated to 0.0. + + @param fEnd + The maximum distance for the last point. No more points behind this distance will be created. + Use 0.0 to proccess the whole polygon. Negative values are truncated to 0.0. It also + needs to be more or equal to fStart, else it is truncated to fStart. + + @return + The newly created polygon + */ + B2DPolygon createEdgesOfGivenLength(const B2DPolygon& rCandidate, double fLength, double fStart = 0.0, double fEnd = 0.0); + + /** Create Waveline along given polygon + The implementation is based on createEdgesOfGivenLength and creates a curve + segment with the given dimensions for each created line segment. The polygon + is treated as if opened (closed state will be ignored) and only for whole + edges a curve segment will be created (no rest handling) + + @param rCandidate + The polygon along which the waveline will be created + + @param fWaveWidth + The length of a single waveline curve segment + + @param fgWaveHeight + The height of the waveline (amplitude) + */ + B2DPolygon createWaveline(const B2DPolygon& rCandidate, double fWaveWidth, double fWaveHeight); + + /** split each edge of a polygon in exactly nSubEdges equidistant edges + + @param rCandidate + The source polygon. If too small (no edges), nSubEdges too small (<2) + or neither bHandleCurvedEdgesnor bHandleStraightEdges it will just be returned. + Else for each edge nSubEdges will be created. Closed state is preserved. + + @param nSubEdges + How many edges shall be created as replacement for each single edge + + @param bHandleCurvedEdges + Process curved edges or not. If to handle the curved edges will be splitted + into nSubEdges part curved edges of equidistant bezier distances. If not, + curved edges will just be copied. + + @param bHandleStraightEdges + Process straight edges or not. If to handle the straight edges will be splitted + into nSubEdges part curved edges of equidistant length. If not, + straight edges will just be copied. + */ + B2DPolygon reSegmentPolygonEdges(const B2DPolygon& rCandidate, sal_uInt32 nSubEdges, bool bHandleCurvedEdges, bool bHandleStraightEdges); + + ////////////////////////////////////////////////////////////////////// + // comparators with tolerance for 2D Polygons + bool equal(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, const double& rfSmallValue); + bool equal(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB); + + /** snap some polygon coordinates to discrete coordinates + + This method allows to snap some polygon points to discrete (integer) values + which equals e.g. a snap to discrete coordinates. It will snap points of + horizontal and vertical edges + + @param rCandidate + The source polygon + + @return + The modified version of the source polygon + */ + B2DPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon& rCandidate); + + } // end of namespace tools +} // end of namespace basegfx + +#endif /* _BGFX_POLYGON_B2DPOLYGONTOOLS_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dpolygontriangulator.hxx b/basegfx/inc/basegfx/polygon/b2dpolygontriangulator.hxx new file mode 100644 index 000000000000..e23d640b8833 --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dpolygontriangulator.hxx @@ -0,0 +1,49 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B2DPOLYGONTRIANGULATOR_HXX +#define _BGFX_POLYGON_B2DPOLYGONTRIANGULATOR_HXX + +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <vector> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace triangulator + { + // triangulate given polygon + ::basegfx::B2DPolygon triangulate(const ::basegfx::B2DPolygon& rCandidate); + + // triangulate given PolyPolygon + ::basegfx::B2DPolygon triangulate(const ::basegfx::B2DPolyPolygon& rCandidate); + + } // end of namespace triangulator +} // end of namespace basegfx + +#endif /* _BGFX_POLYGON_B2DPOLYGONTRIANGULATOR_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dpolypolygon.hxx b/basegfx/inc/basegfx/polygon/b2dpolypolygon.hxx new file mode 100644 index 000000000000..0032cc47ce4e --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dpolypolygon.hxx @@ -0,0 +1,137 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B2DPOLYPOLYGON_HXX +#define _BGFX_POLYGON_B2DPOLYPOLYGON_HXX + +#include <sal/types.h> +#include <o3tl/cow_wrapper.hxx> +#include <basegfx/range/b2drange.hxx> + +// predeclarations +class ImplB2DPolyPolygon; + +namespace basegfx +{ + class B2DPolygon; + class B2DHomMatrix; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class B2DPolyPolygon + { + public: + typedef o3tl::cow_wrapper< ImplB2DPolyPolygon > ImplType; + + private: + ImplType mpPolyPolygon; + + public: + B2DPolyPolygon(); + B2DPolyPolygon(const B2DPolyPolygon& rPolyPolygon); + explicit B2DPolyPolygon(const B2DPolygon& rPolygon); + ~B2DPolyPolygon(); + + // assignment operator + B2DPolyPolygon& operator=(const B2DPolyPolygon& rPolyPolygon); + + /// unshare this poly-polygon (and all included polygons) with all internally shared instances + void makeUnique(); + + // compare operators + bool operator==(const B2DPolyPolygon& rPolyPolygon) const; + bool operator!=(const B2DPolyPolygon& rPolyPolygon) const; + + // polygon interface + sal_uInt32 count() const; + + B2DPolygon getB2DPolygon(sal_uInt32 nIndex) const; + void setB2DPolygon(sal_uInt32 nIndex, const B2DPolygon& rPolygon); + + // test for curve + bool areControlPointsUsed() const; + + // insert/append single polygon + void insert(sal_uInt32 nIndex, const B2DPolygon& rPolygon, sal_uInt32 nCount = 1); + void append(const B2DPolygon& rPolygon, sal_uInt32 nCount = 1); + + /** Default adaptive subdivision access + + For details refer to B2DPolygon::getDefaultAdaptiveSubdivision() + + @return + The default subdivision of this polygon + */ + B2DPolyPolygon getDefaultAdaptiveSubdivision() const; + + /** Get the B2DRange (Rectangle dimensions) of this B2DPolyPolygon + + For details refer to B2DPolygon::getB2DRange() + + @return + The outer range of the bezier curve/polygon + */ + B2DRange getB2DRange() const; + + // insert/append multiple polygons + void insert(sal_uInt32 nIndex, const B2DPolyPolygon& rPolyPolygon); + void append(const B2DPolyPolygon& rPolyPolygon); + + // remove + void remove(sal_uInt32 nIndex, sal_uInt32 nCount = 1); + + // reset to empty state + void clear(); + + // closed state + bool isClosed() const; + void setClosed(bool bNew); + + // flip polygon direction + void flip(); + + // test if PolyPolygon has double points + bool hasDoublePoints() const; + + // remove double points, at the begin/end and follow-ups, too + void removeDoublePoints(); + + // apply transformation given in matrix form to the polygon + void transform(const basegfx::B2DHomMatrix& rMatrix); + + // polygon iterators (same iterator validity conditions as for vector) + const B2DPolygon* begin() const; + const B2DPolygon* end() const; + B2DPolygon* begin(); + B2DPolygon* end(); + }; +} // end of namespace basegfx + +#endif /* _BGFX_POLYGON_B2DPOLYPOLYGON_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dpolypolygoncutter.hxx b/basegfx/inc/basegfx/polygon/b2dpolypolygoncutter.hxx new file mode 100644 index 000000000000..7e648302c5e5 --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dpolypolygoncutter.hxx @@ -0,0 +1,119 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B2DPOLYPOLYGONCUTTER_HXX +#define _BGFX_POLYGON_B2DPOLYPOLYGONCUTTER_HXX + +#include <basegfx/polygon/b2dpolypolygon.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + // Solve all crossovers in a polyPolygon. This re-layouts all contained polygons so that the + // result will contain only non-cutting polygons. For that reason, points will be added at + // crossover and touch points and the single Polygons may be re-combined. The orientations + // of the contained polygons in not changed but used as topological information. + // Self crossovers of the contained sub-polygons are implicitely handled, but to not lose + // the topological information, it may be necessary to remove self-intersections of the + // contained sub-polygons in a preparing step and to explicitely correct their orientations. + B2DPolyPolygon solveCrossovers(const B2DPolyPolygon& rCandidate); + + // Version for single polygons. This is for solving self-intersections. Result will be free of + // crossovers. When result contains multiple polygons, it may be necessary to rearrange their + // orientations since holes may have been created (use correctOrientations eventually). + B2DPolyPolygon solveCrossovers(const B2DPolygon& rCandidate); + + // Neutral polygons will be stripped. Neutral polygons are ones who's orientation is + // neutral, so normally they have no volume -> just closed paths. A polygon with the same + // positive and negative oriented volume is also neutral, so this may not be wanted. It is + // safe to call with crossover-free polygons, though (that's where it's mostly used). + B2DPolyPolygon stripNeutralPolygons(const B2DPolyPolygon& rCandidate); + + // Remove not necessary polygons. Works only correct with crossover-free polygons. For each + // polygon, the depth for the PolyPolygon is calculated. The orientation is used to identify holes. + // Start value for holes is -1, for polygons it's zero. Ech time a polygon is contained in another one, + // it's depth is increased when inside a polygon, decreased when inside a hole. The result is a depth + // which e.g. is -1 for holes outside everything, 1 for a polygon covered by another polygon and zero + // for e.g. holes in a polygon or polygons outside everythig else. + // In the 2nd step, all polygons with depth other than zero are removed. If bKeepAboveZero is used, + // all polygons < 1 are removed. The bKeepAboveZero mode is useful for clipping, e.g. just append + // one polygon to another and use this mode -> only parts where two polygons overlapped will be kept. + // In combination with correct orientation of the input orientations and the SolveCrossover calls this + // can be combined for logical polygon operations or polygon clipping. + B2DPolyPolygon stripDispensablePolygons(const B2DPolyPolygon& rCandidate, bool bKeepAboveZero = false); + + // For convenience: The four basic operations OR, XOR, AND and DIFF for + // two PolyPolygons. These are combinations of the above methods. To not be forced + // to do evtl. already done preparations twice, You have to do the operations Yourself. + // + // A source preparation consists of preparing it to be seen as XOR-Rule PolyPolygon, + // so it is freed of intersections, self-intersections and the orientations are corrected. + // Important is that it will define the same areas as before, but is intersection-free. + // As an example think about a single polygon looping in itself and having holes. To + // topologically correctly handle this, it is necessary to remove all intersections and + // to correct the orientations. The orientation of the isolated holes e.g. will be negative. + // Topologically it is necessary to prepare each polygon which is seen as entity. It is + // not sufficient just to concatenate them and prepare the result, this may be topologically + // different since the simple concatenation will be seen as XOR. To work correctly, You + // may need to OR those polygons. + + // Preparations: solve self-intersections and intersections, remove neutral + // parts and correct orientations. + B2DPolyPolygon prepareForPolygonOperation(const B2DPolygon& rCandidate); + B2DPolyPolygon prepareForPolygonOperation(const B2DPolyPolygon& rCandidate); + + // OR: Return all areas where CandidateA or CandidateB exist + B2DPolyPolygon solvePolygonOperationOr(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB); + + // XOR: Return all areas where CandidateA or CandidateB exist, but not both + B2DPolyPolygon solvePolygonOperationXor(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB); + + // AND: Return all areas where CandidateA and CandidateB exist + B2DPolyPolygon solvePolygonOperationAnd(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB); + + // DIFF: Return all areas where CandidateA is not covered by CandidateB (cut B out of A) + B2DPolyPolygon solvePolygonOperationDiff(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB); + + /** merge all single PolyPolygons to a single, OR-ed PolyPolygon + + @param rInput + The source PolyPolygons + + @return A single PolyPolygon containing the Or-merged result + */ + B2DPolyPolygon mergeToSinglePolyPolygon(const std::vector< basegfx::B2DPolyPolygon >& rInput); + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + + +#endif /* _BGFX_POLYGON_B2DPOLYPOLYGONCUTTER_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dpolypolygonfillrule.hxx b/basegfx/inc/basegfx/polygon/b2dpolypolygonfillrule.hxx new file mode 100644 index 000000000000..f5ceb67586e8 --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dpolypolygonfillrule.hxx @@ -0,0 +1,60 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B2DPOLYPOLYGONFILLRULE_HXX +#define _BGFX_POLYGON_B2DPOLYPOLYGONFILLRULE_HXX + +#include <sal/types.h> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + /** Fill rule to use for poly-polygon filling. + + The fill rule determines which areas are inside, and which are + outside the poly-polygon. + */ + enum FillRule + { + /** Areas, for which a scanline has crossed an odd number of + vertices, are regarded 'inside', the remainder 'outside' + of the poly-polygon. + */ + FillRule_EVEN_ODD, + + /** For each edge a scanline crosses, a current winding number + is updated. Downward edges count +1, upward edges count + -1. If the total accumulated winding number for one area + is not zero, this area is regarded 'inside', otherwise, + 'outside'. + */ + FillRule_NONZERO_WINDING_NUMBER + }; +} + +#endif /* _BGFX_POLYGON_B2DPOLYPOLYGONFILLRULE_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dpolypolygonrasterconverter.hxx b/basegfx/inc/basegfx/polygon/b2dpolypolygonrasterconverter.hxx new file mode 100644 index 000000000000..a77f1a371a8a --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dpolypolygonrasterconverter.hxx @@ -0,0 +1,141 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B2DPOLYPOLYGONRASTERCONVERTER_HXX +#define _BGFX_POLYGON_B2DPOLYPOLYGONRASTERCONVERTER_HXX + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygonfillrule.hxx> +#include <vector> +#include <utility> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + /** Raster-convert a poly-polygon. + + This class can raster-convert a given poly-polygon. Simply + derive from this, and override the span() method, which will + get called for every scanline span of the poly-polygon. + + @derive + Overwrite span() with the render output method of your choice. + */ + class B2DPolyPolygonRasterConverter + { + public: + /** Create raster-converter for given poly-polygon + */ + B2DPolyPolygonRasterConverter(const B2DPolyPolygon& rPolyPolyRaster); + + /** Create raster-converter for given poly-polygon and raster + area. + + @param rPolyPolyRaster + Poly-Polygon to raster convert + + @param rMinUpdateArea + Minimal area to touch when raster-converting. The + rectangle given here is guaranteed to be iterated through + scanline by scanline (but the raster converter might + actually use more scanlines, e.g. if the poly-polygon's + bound rect is larger). One of the cases where this + parameter comes in handy is when rendering in the 'off' + spans, and a certain area must be filled. <em>Do not</em> + use this for clipping, as described above, the touched + area might also be larger. + */ + B2DPolyPolygonRasterConverter(const B2DPolyPolygon& rPolyPolyRaster, + const B2DRectangle& rRasterArea ); + + virtual ~B2DPolyPolygonRasterConverter(); + + /** Raster-convert the contained poly-polygon + + @param eFillRule + Fill rule to use for span filling + */ + void rasterConvert( FillRule eFillRule); + + /** Override this method, to be called for every scanline span + of the poly-polygon + + @param rfXLeft + The left end of the current horizontal span + + @param rfXRight + The right end of the current horizontal span + + @param nY + The y position of the current horizontal span + + @param bOn + Denotes whether this span is on or off, according to the + active fill rule. + */ + virtual void span(const double& rfXLeft, + const double& rfXRight, + sal_Int32 nY, + bool bOn ) = 0; + + /// @internal + struct Vertex + { + inline Vertex(); + inline Vertex( const B2DPoint&, const B2DPoint&, bool ); + + B2DPoint aP1; + B2DPoint aP2; + bool bDownwards; + }; + + private: + // default: disabled copy/assignment + B2DPolyPolygonRasterConverter(const B2DPolyPolygonRasterConverter&); + B2DPolyPolygonRasterConverter& operator=( const B2DPolyPolygonRasterConverter& ); + + void init(); + + typedef ::std::vector<Vertex> VectorOfVertices; + typedef ::std::vector<VectorOfVertices> VectorOfVertexVectors; + + /// The poly-polygon to raster-convert + B2DPolyPolygon maPolyPolygon; + /// Total bound rect of the poly-polygon + const B2DRectangle maPolyPolyRectangle; + + /** Vector containing for each scanline a vector which in turn + contains all vertices that start on the specific scanline + */ + VectorOfVertexVectors maScanlines; + }; +} + +#endif /* _BGFX_POLYGON_B2DPOLYPOLYGONRASTERCONVERTER_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dpolypolygontools.hxx b/basegfx/inc/basegfx/polygon/b2dpolypolygontools.hxx new file mode 100644 index 000000000000..1115a96ef8da --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dpolypolygontools.hxx @@ -0,0 +1,279 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYPOLYGON_B2DPOLYGONTOOLS_HXX +#define _BGFX_POLYPOLYGON_B2DPOLYGONTOOLS_HXX + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b3dpolypolygon.hxx> +#include <vector> + +namespace rtl +{ + class OUString; +} + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + // predefinitions + class B2DPolyPolygon; + class B2DRange; + + namespace tools + { + // B2DPolyPolygon tools + + // Check and evtl. correct orientations of all contained Polygons so that + // the orientations of contained polygons will variate to express areas and + // holes + B2DPolyPolygon correctOrientations(const B2DPolyPolygon& rCandidate); + + // make sure polygon with index 0L is not a hole. This may evtl. change the + // sequence of polygons, but allows to use polygon with index 0L to + // get the correct normal for the whole polyPolygon + B2DPolyPolygon correctOutmostPolygon(const B2DPolyPolygon& rCandidate); + + // Subdivide all contained curves. Use distanceBound value if given. + B2DPolyPolygon adaptiveSubdivideByDistance(const B2DPolyPolygon& rCandidate, double fDistanceBound = 0.0); + + // Subdivide all contained curves. Use distanceBound value if given. Else, a convenient one + // is created. + B2DPolyPolygon adaptiveSubdivideByAngle(const B2DPolyPolygon& rCandidate, double fAngleBound = 0.0); + + // Subdivide all contained curves. Use nCount divisions if given. Else, a convenient one + // is created. + B2DPolyPolygon adaptiveSubdivideByCount(const B2DPolyPolygon& rCandidate, sal_uInt32 nCount = 0L); + + // isInside test for B2dPoint. On border is not inside as long as not true is given + // in bWithBorder flag. It is assumed that the orientations of the given polygon are correct. + bool isInside(const B2DPolyPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder = false); + + /** get range of PolyPolygon. Control points are included. + + For detailed description look at getRangeWithControlPoints(const B2DPolygon&). + This method just expands by the range of every sub-Polygon. + + @param rCandidate + The B2DPolyPolygon eventually containing bezier segments + + @return + The outer range including control points + */ + B2DRange getRangeWithControlPoints(const B2DPolyPolygon& rCandidate); + + /** Get the range of a polyPolygon + + For detailed description look at getRange(const B2DPolygon&). + This method just expands by the range of every sub-Polygon. + + @param rCandidate + The B2DPolyPolygon eventually containing bezier segments + + @return + The outer range of the polygon + */ + B2DRange getRange(const B2DPolyPolygon& rCandidate); + + /** Apply given LineDashing to given polyPolygon + + For a description see applyLineDashing in b2dpolygontoos.hxx + */ + void applyLineDashing( + const B2DPolyPolygon& rCandidate, + const ::std::vector<double>& rDotDashArray, + B2DPolyPolygon* pLineTarget, + B2DPolyPolygon* pGapTarget = 0, + double fFullDashDotLen = 0.0); + + // test if point is inside epsilon-range around the given PolyPolygon. Can be used + // for HitTesting. The epsilon-range is defined to be the tube around the PolyPolygon + // with distance fDistance and rounded edges (start and end point). + bool isInEpsilonRange(const B2DPolyPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance); + + /** Read poly-polygon from SVG. + + This function imports a poly-polygon from an SVG-D + attribute. Currently, elliptical arc elements are not yet + supported (and ignored during parsing). + + @param o_rPolyPoly + The output poly-polygon + + @param rSvgDAttribute + A valid SVG-D attribute string + + @return true, if the string was successfully parsed + */ + bool importFromSvgD( B2DPolyPolygon& o_rPolyPoly, + const ::rtl::OUString& rSvgDAttribute ); + + /** Read poly-polygon from SVG. + + This function imports a poly-polygon from an SVG points + attribute (a plain list of coordinate pairs). + + @param o_rPoly + The output polygon. Note that svg:points can only define a + single polygon + + @param rSvgPointsAttribute + A valid SVG points attribute string + + @return true, if the string was successfully parsed + */ + bool importFromSvgPoints( B2DPolygon& o_rPoly, + const ::rtl::OUString& rSvgPointsAttribute ); + + + // grow for polyPolygon. Move all geometry in each point in the direction of the normal in that point + // with the given amount. Value may be negative. + B2DPolyPolygon growInNormalDirection(const B2DPolyPolygon& rCandidate, double fValue); + + // This method will correct a pair of polyPolygons where the goal is to keep same point count + // to allow direct point association and also to remove self-intersections produced by shrinks. + // This method will eventually change both polyPolygons to reach that goal because there are cases + // where it is necessary to add new cut points to the original + void correctGrowShrinkPolygonPair(B2DPolyPolygon& rOriginal, B2DPolyPolygon& rGrown); + + // force all sub-polygons to a point count of nSegments + B2DPolyPolygon reSegmentPolyPolygon(const B2DPolyPolygon& rCandidate, sal_uInt32 nSegments); + + // create polygon state at t from 0.0 to 1.0 between the two polygons. Both polygons must have the same + // organisation, e.g. same amount of polygons + B2DPolyPolygon interpolate(const B2DPolyPolygon& rOld1, const B2DPolyPolygon& rOld2, double t); + + // create 3d PolyPolygon from given 2d PolyPolygon. The given fZCoordinate is used to expand the + // third coordinate. + B3DPolyPolygon createB3DPolyPolygonFromB2DPolyPolygon(const B2DPolyPolygon& rCandidate, double fZCoordinate = 0.0); + + // create 2d PolyPolygon from given 3d PolyPolygon. All coordinates are transformed using the given + // matrix and the resulting x,y is used to form the new polygon. + B2DPolyPolygon createB2DPolyPolygonFromB3DPolyPolygon(const B3DPolyPolygon& rCandidate, const B3DHomMatrix& rMat); + + // for each contained edge in each contained polygon calculate the smallest distance. Return the index to the smallest + // edge in rEdgeIndex and the index to the polygon in rPolygonIndex. The relative position on the edge is returned in rCut. + // If nothing was found (e.g. empty input plygon), DBL_MAX is returned. + double getSmallestDistancePointToPolyPolygon(const B2DPolyPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rPolygonIndex, sal_uInt32& rEdgeIndex, double& rCut); + + // distort PolyPolygon. rOriginal describes the original range, where the given points describe the distorted + // corresponding points. + B2DPolyPolygon distort(const B2DPolyPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight); + + // rotate PolyPolygon around given point with given angle. + B2DPolyPolygon rotateAroundPoint(const B2DPolyPolygon& rCandidate, const B2DPoint& rCenter, double fAngle); + + // expand all segments (which are not yet) to curve segments. This is done with setting the control + // vectors on the 1/3 resp. 2/3 distances on each segment. + B2DPolyPolygon expandToCurve(const B2DPolyPolygon& rCandidate); + + // set continuity for the whole curve. If not a curve, nothing will change. Non-curve points are not changed, too. + B2DPolyPolygon setContinuity(const B2DPolyPolygon& rCandidate, B2VectorContinuity eContinuity); + + /** Predicate whether a given poly-polygon is a rectangle. + + @param rPoly + PolyPolygon to check + + @return true, if the poly-polygon describes a rectangle + (contains exactly one polygon, polygon is closed, and the + points are either cw or ccw enumerations of a rectangle's + vertices). Note that intermediate points and duplicate + points are ignored. + */ + bool isRectangle( const B2DPolyPolygon& rPoly ); + + /** Export poly-polygon to SVG. + + This function exports a poly-polygon into an SVG-D + statement. Currently, output of relative point sequences + is not yet supported (might cause slightly larger output) + + @param rPolyPoly + The poly-polygon to export + + @param bUseRelativeCoordinates + When true, all coordinate values are exported as relative + to the current position. This tends to save some space, + since fewer digits needs to be written. + + @param bDetectQuadraticBeziers + When true, the export tries to detect cubic bezier + segments in the input polygon, which can be represented by + quadratic bezier segments. Note that the generated string + causes versions prior to OOo2.0 to crash. + + @return the generated SVG-D statement (the XML d attribute + value alone, without any "<path ...>" or "d="...") + */ + ::rtl::OUString exportToSvgD( const B2DPolyPolygon& rPolyPoly, + bool bUseRelativeCoordinates=true, + bool bDetectQuadraticBeziers=true ); + + // #i76891# Try to remove existing curve segments if they are simply edges + B2DPolyPolygon simplifyCurveSegments(const B2DPolyPolygon& rCandidate); + + /** split each edge of a polyPolygon in exactly nSubEdges equidistant edges + + @param rCandidate + The source polyPolygon. If too small (no edges), nSubEdges too small (<2) + or neither bHandleCurvedEdgesnor bHandleStraightEdges it will just be returned. + Else for each edge nSubEdges will be created. Closed state is preserved. + + @param nSubEdges + @param bHandleCurvedEdges + @param bHandleStraightEdges + Please take a look at reSegmentPolygonEdges description, these are the same. + */ + B2DPolyPolygon reSegmentPolyPolygonEdges(const B2DPolyPolygon& rCandidate, sal_uInt32 nSubEdges, bool bHandleCurvedEdges, bool bHandleStraightEdges); + + ////////////////////////////////////////////////////////////////////// + // comparators with tolerance for 2D PolyPolygons + bool equal(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB, const double& rfSmallValue); + bool equal(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB); + + /** snap some polygon coordinates to discrete coordinates + + This method allows to snap some polygon points to discrete (integer) values + which equals e.g. a snap to discrete coordinates. It will snap points of + horizontal and vertical edges + + @param rCandidate + The source polygon + + @return + The modified version of the source polygon + */ + B2DPolyPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolyPolygon& rCandidate); + + } // end of namespace tools +} // end of namespace basegfx + +#endif /* _BGFX_POLYPOLYGON_B2DPOLYGONTOOLS_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b2dtrapezoid.hxx b/basegfx/inc/basegfx/polygon/b2dtrapezoid.hxx new file mode 100644 index 000000000000..70ffdf2b7339 --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b2dtrapezoid.hxx @@ -0,0 +1,132 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: b2dpolygontriangulator.hxx,v $ + * $Revision: 1.5 $ + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B2DTRAPEZOID_HXX +#define _BGFX_POLYGON_B2DTRAPEZOID_HXX + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <vector> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + // class to hold a single trapezoid + class B2DTrapezoid + { + private: + // Geometry data. YValues are down-oriented, this means bottom should + // be bigger than top to be below it. The constructor implementation + // guarantees: + // + // - mfBottomY >= mfTopY + // - mfTopXRight >= mfTopXLeft + // - mfBottomXRight >= mfBottomXLeft + double mfTopXLeft; + double mfTopXRight; + double mfTopY; + double mfBottomXLeft; + double mfBottomXRight; + double mfBottomY; + + public: + // constructor + B2DTrapezoid( + const double& rfTopXLeft, + const double& rfTopXRight, + const double& rfTopY, + const double& rfBottomXLeft, + const double& rfBottomXRight, + const double& rfBottomY); + + // data read access + const double& getTopXLeft() const { return mfTopXLeft; } + const double& getTopXRight() const { return mfTopXRight; } + const double& getTopY() const { return mfTopY; } + const double& getBottomXLeft() const { return mfBottomXLeft; } + const double& getBottomXRight() const { return mfBottomXRight; } + const double& getBottomY() const { return mfBottomY; } + + // convenience method to get content as Polygon + B2DPolygon getB2DPolygon() const; + }; + + typedef ::std::vector< B2DTrapezoid > B2DTrapezoidVector; + +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + // convert SourcePolyPolygon to trapezoids. The trapezoids will be appended to + // ro_Result. ro_Result will not be cleared. If SourcePolyPolygon contains curves, + // it's default AdaptiveSubdivision will be used. + // CAUTION: Trapezoids are oreintation-dependent in the sense that the upper and lower + // lines have to be parallel to the X-Axis, thus this subdivision is NOT simply usable + // for primitive decompositions. To use it, the shear and rotate parts of the + // involved transformations HAVE to be taken into account. + void trapezoidSubdivide( + B2DTrapezoidVector& ro_Result, + const B2DPolyPolygon& rSourcePolyPolygon); + + // directly create trapezoids from given edge. Depending on the given geometry, + // none up to three trapezoids will be created + void createLineTrapezoidFromEdge( + B2DTrapezoidVector& ro_Result, + const B2DPoint& rPointA, + const B2DPoint& rPointB, + double fLineWidth = 1.0); + + // create trapezoids for all edges of the given polygon. The closed state of + // the polygon is taken into account. If curves are contaned, the default + // AdaptiveSubdivision will be used. + void createLineTrapezoidFromB2DPolygon( + B2DTrapezoidVector& ro_Result, + const B2DPolygon& rPolygon, + double fLineWidth = 1.0); + + // create trapezoids for all edges of the given polyPolygon. The closed state of + // the PolyPolygon is taken into account. If curves are contaned, the default + // AdaptiveSubdivision will be used. + void createLineTrapezoidFromB2DPolyPolygon( + B2DTrapezoidVector& ro_Result, + const B2DPolyPolygon& rPolyPolygon, + double fLineWidth = 1.0); + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +#endif /* _BGFX_POLYGON_B2DTRAPEZOID_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b3dpolygon.hxx b/basegfx/inc/basegfx/polygon/b3dpolygon.hxx new file mode 100644 index 000000000000..1732c1338042 --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b3dpolygon.hxx @@ -0,0 +1,141 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B3DPOLYGON_HXX +#define _BGFX_POLYGON_B3DPOLYGON_HXX + +#include <sal/types.h> +#include <o3tl/cow_wrapper.hxx> + +////////////////////////////////////////////////////////////////////////////// +// predeclarations +class ImplB3DPolygon; + +namespace basegfx +{ + class B3DPolygon; + class B3DPoint; + class B3DHomMatrix; + class B3DVector; + class B2DPoint; + class B2DHomMatrix; + class BColor; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class B3DPolygon + { + public: + typedef o3tl::cow_wrapper< ImplB3DPolygon > ImplType; + + private: + // internal data. + ImplType mpPolygon; + + public: + B3DPolygon(); + B3DPolygon(const B3DPolygon& rPolygon); + B3DPolygon(const B3DPolygon& rPolygon, sal_uInt32 nIndex, sal_uInt32 nCount); + ~B3DPolygon(); + + // assignment operator + B3DPolygon& operator=(const B3DPolygon& rPolygon); + + /// unshare this polygon with all internally shared instances + void makeUnique(); + + // compare operators + bool operator==(const B3DPolygon& rPolygon) const; + bool operator!=(const B3DPolygon& rPolygon) const; + + // member count + sal_uInt32 count() const; + + // Coordinate interface + B3DPoint getB3DPoint(sal_uInt32 nIndex) const; + void setB3DPoint(sal_uInt32 nIndex, const B3DPoint& rValue); + + // Coordinate insert/append + void insert(sal_uInt32 nIndex, const B3DPoint& rPoint, sal_uInt32 nCount = 1); + void append(const B3DPoint& rPoint, sal_uInt32 nCount = 1); + + // BColor interface + BColor getBColor(sal_uInt32 nIndex) const; + void setBColor(sal_uInt32 nIndex, const BColor& rValue); + bool areBColorsUsed() const; + void clearBColors(); + + // Normals interface + B3DVector getNormal() const; // plane normal + B3DVector getNormal(sal_uInt32 nIndex) const; // normal in each point + void setNormal(sal_uInt32 nIndex, const B3DVector& rValue); + void transformNormals(const B3DHomMatrix& rMatrix); + bool areNormalsUsed() const; + void clearNormals(); + + // TextureCoordinate interface + B2DPoint getTextureCoordinate(sal_uInt32 nIndex) const; + void setTextureCoordinate(sal_uInt32 nIndex, const B2DPoint& rValue); + void transformTextureCoordiantes(const B2DHomMatrix& rMatrix); + bool areTextureCoordinatesUsed() const; + void clearTextureCoordinates(); + + // insert/append other 2D polygons + void insert(sal_uInt32 nIndex, const B3DPolygon& rPoly, sal_uInt32 nIndex2 = 0, sal_uInt32 nCount = 0); + void append(const B3DPolygon& rPoly, sal_uInt32 nIndex = 0, sal_uInt32 nCount = 0); + + // remove + void remove(sal_uInt32 nIndex, sal_uInt32 nCount = 1); + + // clear all points + void clear(); + + // closed state + bool isClosed() const; + void setClosed(bool bNew); + + // flip polygon direction + void flip(); + + // test if Polygon has double points + bool hasDoublePoints() const; + + // remove double points, at the begin/end and follow-ups, too + void removeDoublePoints(); + + // apply transformation given in matrix form to the polygon + void transform(const B3DHomMatrix& rMatrix); + }; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + + +#endif /* _BGFX_POLYGON_B3DPOLYGON_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b3dpolygonclipper.hxx b/basegfx/inc/basegfx/polygon/b3dpolygonclipper.hxx new file mode 100644 index 000000000000..71c7f36d50d8 --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b3dpolygonclipper.hxx @@ -0,0 +1,86 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYPOLYGON_B3DPOLYGONCLIPPER_HXX +#define _BGFX_POLYPOLYGON_B3DPOLYGONCLIPPER_HXX + +#include <basegfx/polygon/b3dpolypolygon.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + // predefinitions + class B3DRange; + class B2DRange; + + namespace tools + { + /** define for deciding one of X,Y,Z directions + */ + enum B3DOrientation + { + B3DORIENTATION_X, // X-Axis + B3DORIENTATION_Y, // Y-Axis + B3DORIENTATION_Z // Z-Axis + }; + + // Clip given 3D polygon against a plane orthogonal to X,Y or Z axis. The plane is defined using the + // enum ePlaneOrthogonal which names the vector orthogonal to the plane, the fPlaneOffset gives the distance + // of the plane from the center (0.0). + // The value bClipPositive defines on which side the return value will be (true -> on positive side of plane). + // The switch bStroke decides if the polygon is interpreted as area (false) or strokes (true). + B3DPolyPolygon clipPolyPolygonOnOrthogonalPlane(const B3DPolyPolygon& rCandidate, B3DOrientation ePlaneOrthogonal, bool bClipPositive, double fPlaneOffset, bool bStroke); + + // version for Polygons + B3DPolyPolygon clipPolygonOnOrthogonalPlane(const B3DPolygon& rCandidate, B3DOrientation ePlaneOrthogonal, bool bClipPositive, double fPlaneOffset, bool bStroke); + + // Clip the given PolyPolygon against the given range. bInside defines if the result will contain the + // parts which are contained in the range or vice versa. + // The switch bStroke decides if the polygon is interpreted as area (false) or strokes (true). + B3DPolyPolygon clipPolyPolygonOnRange(const B3DPolyPolygon& rCandidate, const B3DRange& rRange, bool bInside, bool bStroke); + + // version for Polygons + B3DPolyPolygon clipPolygonOnRange(const B3DPolygon& rCandidate, const B3DRange& rRange, bool bInside, bool bStroke); + + // versions for B2DRange, clips only against X,Y + B3DPolyPolygon clipPolyPolygonOnRange(const B3DPolyPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke); + B3DPolyPolygon clipPolygonOnRange(const B3DPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke); + + // Clip the given PolyPolygon against given plane in 3D. The plane is defined by a plane normal and a point on the plane. + // The value bClipPositive defines on which side the return value will be (true -> on positive side of plane). + // The switch bStroke decides if the polygon is interpreted as area (false) or strokes (true). + B3DPolyPolygon clipPolyPolygonOnPlane(const B3DPolyPolygon& rCandidate, const B3DPoint& rPointOnPlane, const B3DVector& rPlaneNormal, bool bClipPositive, bool bStroke); + + // version for Polygons + B3DPolyPolygon clipPolygonOnPlane(const B3DPolygon& rCandidate, const B3DPoint& rPointOnPlane, const B3DVector& rPlaneNormal, bool bClipPositive, bool bStroke); + + } // end of namespace tools +} // end of namespace basegfx + +#endif /* _BGFX_POLYPOLYGON_B3DPOLYGONCLIPPER_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b3dpolygontools.hxx b/basegfx/inc/basegfx/polygon/b3dpolygontools.hxx new file mode 100644 index 000000000000..7945fc1bde00 --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b3dpolygontools.hxx @@ -0,0 +1,191 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B3DPOLYGONTOOLS_HXX +#define _BGFX_POLYGON_B3DPOLYGONTOOLS_HXX + +#include <basegfx/point/b3dpoint.hxx> +#include <basegfx/vector/b3dvector.hxx> +#include <basegfx/polygon/b3dpolypolygon.hxx> +#include <basegfx/vector/b2enums.hxx> +#include <vector> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + // predefinitions + class B3DPolygon; + class B3DRange; + + namespace tools + { + // B3DPolygon tools + + /** Check if given polygon is closed. This is kind of a + 'classic' method to support old polygon definitions. + Those old polygon definitions define the closed state + of the polygon using identical start and endpoints. This + method corrects this (removes double start/end points) + and sets the Closed()-state of the polygon correctly. + */ + void checkClosed(B3DPolygon& rCandidate); + + // 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); + sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B3DPolygon& rCandidate); + + // Get orientation of Polygon + B2VectorOrientation getOrientation(const B3DPolygon& rCandidate); + + // get size of polygon. Control vectors are included in that ranges. + B3DRange getRange(const B3DPolygon& rCandidate); + + // get normal vector of polygon + B3DVector getNormal(const B3DPolygon& rCandidate); + + // get normal vector of positive oriented polygon + B3DVector getPositiveOrientedNormal(const B3DPolygon& rCandidate); + + // get signed area of polygon + double getSignedArea(const B3DPolygon& rCandidate); + + // get area of polygon + double getArea(const B3DPolygon& rCandidate); + + // get signed area of polygon + double getSignedArea(const B3DPolygon& rCandidate); + + // get area of polygon + double getArea(const ::basegfx::B3DPolygon& rCandidate); + + // get length of polygon edge from point nIndex to nIndex + 1 + double getEdgeLength(const B3DPolygon& rCandidate, sal_uInt32 nIndex); + + // get length of polygon + double getLength(const B3DPolygon& rCandidate); + + // get position on polygon for absolute given distance. If + // length is given, it is assumed the correct polygon length, if 0.0 it is calculated + // using getLength(...) + B3DPoint getPositionAbsolute(const B3DPolygon& rCandidate, double fDistance, double fLength = 0.0); + + // get position on polygon for relative given distance in range [0.0 .. 1.0]. If + // length is given, it is assumed the correct polygon length, if 0.0 it is calculated + // using getLength(...) + B3DPoint getPositionRelative(const B3DPolygon& rCandidate, double fDistance, double fLength = 0.0); + + /** Apply given LineDashing to given polygon + + For a description see applyLineDashing in b2dpolygontoos.hxx + */ + void applyLineDashing( + const B3DPolygon& rCandidate, + const ::std::vector<double>& rDotDashArray, + B3DPolyPolygon* pLineTarget, + B3DPolyPolygon* pGapTarget = 0, + double fFullDashDotLen = 0.0); + + /** Create/replace normals for given 3d geometry with default normals from given center to outside. + rCandidate: the 3d geometry to change + rCenter: the center of the 3d geometry + */ + B3DPolygon applyDefaultNormalsSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter); + + /** invert normals for given 3d geometry. + */ + B3DPolygon invertNormals( const B3DPolygon& rCandidate); + + /** Create/replace texture coordinates for given 3d geometry with parallel projected one + rRange: the full range of the 3d geometry + If bChangeX, x texture coordinate will be recalculated. + If bChangeY, y texture coordinate will be recalculated. + */ + B3DPolygon applyDefaultTextureCoordinatesParallel( const B3DPolygon& rCandidate, const B3DRange& rRange, bool bChangeX = true, bool bChangeY = true); + + /** Create/replace texture coordinates for given 3d geometry with spherical one + rCenter: the centre of the used 3d geometry + If bChangeX, x texture coordinate will be recalculated. + If bChangeY, y texture coordinate will be recalculated. + */ + B3DPolygon applyDefaultTextureCoordinatesSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter, bool bChangeX = true, bool bChangeY = true); + + // 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 cylinder centered to the given edge, using radius + // fDistance, and the sphere around both points with radius fDistance. + bool isInEpsilonRange(const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, const B3DPoint& rTestPosition, double fDistance); + + // test if point is inside epsilon-range around the given Polygon. Can be used + // for HitTesting. The epsilon-range is defined to be the cylinder centered to + // the given edge, using radius fDistance, and the sphere around both points with radius fDistance. + bool isInEpsilonRange(const B3DPolygon& rCandidate, const B3DPoint& rTestPosition, double fDistance); + + // isInside tests for B3DPoint and other B3DPolygon. On border is not inside as long as + // not true is given in bWithBorder flag. + bool isInside(const B3DPolygon& rCandidate, const B3DPoint& rPoint, bool bWithBorder = false); + bool isInside(const B3DPolygon& rCandidate, const B3DPolygon& rPolygon, bool bWithBorder = false); + + // calculates if given point is on given line, taking care of the numerical epsilon + bool isPointOnLine(const B3DPoint& rStart, const B3DPoint& rEnd, const B3DPoint& rCandidate, bool bWithPoints = false); + + // calculates if given point is on given polygon, taking care of the numerical epsilon. Uses + // isPointOnLine internally + bool isPointOnPolygon(const B3DPolygon& rCandidate, const B3DPoint& rPoint, bool bWithPoints = true); + + // helper to get a fCut position between a plane (given with normal and a point) + // and a line given by start and end point + bool getCutBetweenLineAndPlane(const B3DVector& rPlaneNormal, const B3DPoint& rPlanePoint, const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, double& fCut); + + // helper to get a fCut position between a 3d Polygon + // and a line given by start and end point + bool getCutBetweenLineAndPolygon(const B3DPolygon& rCandidate, const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, double& fCut); + + ////////////////////////////////////////////////////////////////////// + // comparators with tolerance for 3D Polygons + bool equal(const B3DPolygon& rCandidateA, const B3DPolygon& rCandidateB, const double& rfSmallValue); + bool equal(const B3DPolygon& rCandidateA, const B3DPolygon& rCandidateB); + + /** snap some polygon coordinates to discrete coordinates + + This method allows to snap some polygon points to discrete (integer) values + which equals e.g. a snap to discrete coordinates. It will snap points of + horizontal and vertical edges + + @param rCandidate + The source polygon + + @return + The modified version of the source polygon + */ + B3DPolygon snapPointsOfHorizontalOrVerticalEdges(const B3DPolygon& rCandidate); + + } // end of namespace tools +} // end of namespace basegfx + +#endif /* _BGFX_POLYGON_B3DPOLYGONTOOLS_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b3dpolypolygon.hxx b/basegfx/inc/basegfx/polygon/b3dpolypolygon.hxx new file mode 100644 index 000000000000..76e0c2f844c0 --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b3dpolypolygon.hxx @@ -0,0 +1,125 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYGON_B3DPOLYPOLYGON_HXX +#define _BGFX_POLYGON_B3DPOLYPOLYGON_HXX + +#include <sal/types.h> +#include <o3tl/cow_wrapper.hxx> + +// predeclarations +class ImplB3DPolyPolygon; + +namespace basegfx +{ + class B3DPolygon; + class B3DHomMatrix; + class B2DHomMatrix; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class B3DPolyPolygon + { + public: + typedef o3tl::cow_wrapper< ImplB3DPolyPolygon > ImplType; + + private: + ImplType mpPolyPolygon; + + public: + B3DPolyPolygon(); + B3DPolyPolygon(const B3DPolyPolygon& rPolyPolygon); + explicit B3DPolyPolygon(const B3DPolygon& rPolygon); + ~B3DPolyPolygon(); + + // assignment operator + B3DPolyPolygon& operator=(const B3DPolyPolygon& rPolyPolygon); + + /// unshare this poly-polygon (and all included polygons) with all internally shared instances + void makeUnique(); + + // compare operators + bool operator==(const B3DPolyPolygon& rPolyPolygon) const; + bool operator!=(const B3DPolyPolygon& rPolyPolygon) const; + + // polygon interface + sal_uInt32 count() const; + + // B3DPolygon interface + B3DPolygon getB3DPolygon(sal_uInt32 nIndex) const; + void setB3DPolygon(sal_uInt32 nIndex, const B3DPolygon& rPolygon); + + // BColor interface + bool areBColorsUsed() const; + void clearBColors(); + + // Normals interface + void transformNormals(const B3DHomMatrix& rMatrix); + bool areNormalsUsed() const; + void clearNormals(); + + // TextureCoordinate interface + void transformTextureCoordiantes(const B2DHomMatrix& rMatrix); + bool areTextureCoordinatesUsed() const; + void clearTextureCoordinates(); + + // insert/append single polygon + void insert(sal_uInt32 nIndex, const B3DPolygon& rPolygon, sal_uInt32 nCount = 1); + void append(const B3DPolygon& rPolygon, sal_uInt32 nCount = 1); + + // insert/append multiple polygons + void insert(sal_uInt32 nIndex, const B3DPolyPolygon& rPolyPolygon); + void append(const B3DPolyPolygon& rPolyPolygon); + + // remove + void remove(sal_uInt32 nIndex, sal_uInt32 nCount = 1); + + // reset to empty state + void clear(); + + // closed state + bool isClosed() const; + void setClosed(bool bNew); + + // flip polygon direction + void flip(); + + // test if PolyPolygon has double points + bool hasDoublePoints() const; + + // remove double points, at the begin/end and follow-ups, too + void removeDoublePoints(); + + // apply transformation given in matrix form to the polygon + void transform(const basegfx::B3DHomMatrix& rMatrix); + }; +} // end of namespace basegfx + +#endif /* _BGFX_POLYGON_B3DPOLYPOLYGON_HXX */ diff --git a/basegfx/inc/basegfx/polygon/b3dpolypolygontools.hxx b/basegfx/inc/basegfx/polygon/b3dpolypolygontools.hxx new file mode 100644 index 000000000000..813be9839a9a --- /dev/null +++ b/basegfx/inc/basegfx/polygon/b3dpolypolygontools.hxx @@ -0,0 +1,154 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_POLYPOLYGON_B3DPOLYGONTOOLS_HXX +#define _BGFX_POLYPOLYGON_B3DPOLYGONTOOLS_HXX + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <vector> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b3dpoint.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + // predefinitions + class B3DPolyPolygon; + class B3DRange; + + namespace tools + { + // B3DPolyPolygon tools + + // get size of PolyPolygon. Control vectors are included in that ranges. + B3DRange getRange(const B3DPolyPolygon& rCandidate); + + /** Apply given LineDashing to given polyPolygon + + For a description see applyLineDashing in b2dpolygontoos.hxx + */ + void applyLineDashing( + const B3DPolyPolygon& rCandidate, + const ::std::vector<double>& rDotDashArray, + B3DPolyPolygon* pLineTarget, + B3DPolyPolygon* pGapTarget = 0, + double fFullDashDotLen = 0.0); + + /** Create a unit 3D line polyPolygon which defines a cube. + */ + B3DPolyPolygon createUnitCubePolyPolygon(); + + /** Create a unit 3D fill polyPolygon which defines a cube. + */ + B3DPolyPolygon createUnitCubeFillPolyPolygon(); + + /** Create a 3D line polyPolygon from a B3DRange which defines a cube. + */ + B3DPolyPolygon createCubePolyPolygonFromB3DRange( const B3DRange& rRange); + + /** Create a 3D fill polyPolygon from a B3DRange which defines a cube. + */ + B3DPolyPolygon createCubeFillPolyPolygonFromB3DRange( const B3DRange& rRange); + + /** Create a unit 3D line polyPolygon which defines a sphere with the given count of hor and ver segments. + Result will be centered at (0.0, 0.0, 0.0) and sized [-1.0 .. 1.0] in all dimensions. + If nHorSeg == 0 and/or nVerSeg == 0, a default will be calculated to have a step at least each 15 degrees. + With VerStart, VerStop and hor range in cartesian may be specified to create a partial sphere only. + */ + B3DPolyPolygon createUnitSpherePolyPolygon( + sal_uInt32 nHorSeg = 0L, sal_uInt32 nVerSeg = 0L, + double fVerStart = F_PI2, double fVerStop = -F_PI2, + double fHorStart = 0.0, double fHorStop = F_2PI); + + /** Create a 3D line polyPolygon from a B3DRange which defines a sphere with the given count of hor and ver segments. + If nHorSeg == 0 and/or nVerSeg == 0, a default will be calculated to have a step at least each 15 degrees. + With VerStart, VerStop and hor range in cartesian may be specified to create a partial sphere only. + */ + B3DPolyPolygon createSpherePolyPolygonFromB3DRange( + const B3DRange& rRange, + sal_uInt32 nHorSeg = 0L, sal_uInt32 nVerSeg = 0L, + double fVerStart = F_PI2, double fVerStop = -F_PI2, + double fHorStart = 0.0, double fHorStop = F_2PI); + + /** same as createUnitSpherePolyPolygon, but creates filled polygons (closed and oriented) + There is one extra, the bool bNormals defines if normals will be set, default is false + */ + B3DPolyPolygon createUnitSphereFillPolyPolygon( + sal_uInt32 nHorSeg = 0L, sal_uInt32 nVerSeg = 0L, + bool bNormals = false, + double fVerStart = F_PI2, double fVerStop = -F_PI2, + double fHorStart = 0.0, double fHorStop = F_2PI); + + /** same as createSpherePolyPolygonFromB3DRange, but creates filled polygons (closed and oriented) + There is one extra, the bool bNormals defines if normals will be set, default is false + */ + B3DPolyPolygon createSphereFillPolyPolygonFromB3DRange( + const B3DRange& rRange, + sal_uInt32 nHorSeg = 0L, sal_uInt32 nVerSeg = 0L, + bool bNormals = false, + double fVerStart = F_PI2, double fVerStop = -F_PI2, + double fHorStart = 0.0, double fHorStop = F_2PI); + + /** Create/replace normals for given 3d geometry with default normals from given center to outside. + rCandidate: the 3d geometry to change + rCenter: the center of the 3d geometry + */ + B3DPolyPolygon applyDefaultNormalsSphere( const B3DPolyPolygon& rCandidate, const B3DPoint& rCenter); + + /** invert normals for given 3d geometry. + */ + B3DPolyPolygon invertNormals( const B3DPolyPolygon& rCandidate); + + /** Create/replace texture coordinates for given 3d geometry with parallel projected one + rRange: the full range of the 3d geometry + If bChangeX, x texture coordinate will be recalculated. + If bChangeY, y texture coordinate will be recalculated. + */ + B3DPolyPolygon applyDefaultTextureCoordinatesParallel( const B3DPolyPolygon& rCandidate, const B3DRange& rRange, bool bChangeX = true, bool bChangeY = true); + + /** Create/replace texture coordinates for given 3d geometry with spherical one + rCenter: the centre of the used 3d geometry + If bChangeX, x texture coordinate will be recalculated. + If bChangeY, y texture coordinate will be recalculated. + */ + B3DPolyPolygon applyDefaultTextureCoordinatesSphere( const B3DPolyPolygon& rCandidate, const B3DPoint& rCenter, bool bChangeX = true, bool bChangeY = true); + + // isInside test for B3DPoint. On border is not inside as long as not true is given + // in bWithBorder flag. It is assumed that the orientations of the given polygon are correct. + bool isInside(const B3DPolyPolygon& rCandidate, const B3DPoint& rPoint, bool bWithBorder = false); + + ////////////////////////////////////////////////////////////////////// + // comparators with tolerance for 3D PolyPolygons + bool equal(const B3DPolyPolygon& rCandidateA, const B3DPolyPolygon& rCandidateB, const double& rfSmallValue); + bool equal(const B3DPolyPolygon& rCandidateA, const B3DPolyPolygon& rCandidateB); + + } // end of namespace tools +} // end of namespace basegfx + +#endif /* _BGFX_POLYPOLYGON_B3DPOLYGONTOOLS_HXX */ diff --git a/basegfx/inc/basegfx/range/b1drange.hxx b/basegfx/inc/basegfx/range/b1drange.hxx new file mode 100644 index 000000000000..eba1536f4ee6 --- /dev/null +++ b/basegfx/inc/basegfx/range/b1drange.hxx @@ -0,0 +1,165 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B1DRANGE_HXX +#define _BGFX_RANGE_B1DRANGE_HXX + +#include <basegfx/range/basicrange.hxx> + + +namespace basegfx +{ + class B1IRange; + + class B1DRange + { + ::basegfx::BasicRange< double, DoubleTraits > maRange; + + public: + B1DRange() + { + } + + explicit B1DRange(double fStartValue) + : maRange(fStartValue) + { + } + + B1DRange(double fStartValue1, double fStartValue2) + : maRange(fStartValue1) + { + expand(fStartValue2); + } + + B1DRange(const B1DRange& rRange) + : maRange(rRange.maRange) + { + } + + explicit B1DRange( const B1IRange& rRange ); + + bool isEmpty() const + { + return maRange.isEmpty(); + } + + void reset() + { + maRange.reset(); + } + + bool operator==( const B1DRange& rRange ) const + { + return (maRange == rRange.maRange); + } + + bool operator!=( const B1DRange& rRange ) const + { + return (maRange != rRange.maRange); + } + + B1DRange& operator=(const B1DRange& rRange) + { + maRange = rRange.maRange; + return *this; + } + + bool equal(const B1DRange& rRange) const + { + return (maRange.equal(rRange.maRange)); + } + + double getMinimum() const + { + return maRange.getMinimum(); + } + + double getMaximum() const + { + return maRange.getMaximum(); + } + + double getRange() const + { + return maRange.getRange(); + } + + double getCenter() const + { + return maRange.getCenter(); + } + + bool isInside(double fValue) const + { + return maRange.isInside(fValue); + } + + bool isInside(const B1DRange& rRange) const + { + return maRange.isInside(rRange.maRange); + } + + bool overlaps(const B1DRange& rRange) const + { + return maRange.overlaps(rRange.maRange); + } + + bool overlapsMore(const B1DRange& rRange) const + { + return maRange.overlapsMore(rRange.maRange); + } + + void expand(double fValue) + { + maRange.expand(fValue); + } + + void expand(const B1DRange& rRange) + { + maRange.expand(rRange.maRange); + } + + void intersect(const B1DRange& rRange) + { + maRange.intersect(rRange.maRange); + } + + void grow(double fValue) + { + maRange.grow(fValue); + } + }; + + /** Round double to nearest integer for 1D range + + @return the nearest integer for this range + */ + B1IRange fround(const B1DRange& rRange); +} // end of namespace basegfx + + +#endif /* _BGFX_RANGE_B1DRANGE_HXX */ diff --git a/basegfx/inc/basegfx/range/b1ibox.hxx b/basegfx/inc/basegfx/range/b1ibox.hxx new file mode 100644 index 000000000000..cb4ab8770a49 --- /dev/null +++ b/basegfx/inc/basegfx/range/b1ibox.hxx @@ -0,0 +1,143 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B1IBOX_HXX +#define _BGFX_RANGE_B1IBOX_HXX + +#include <basegfx/range/basicbox.hxx> + + +namespace basegfx +{ + class B1IBox + { + ::basegfx::BasicBox maRange; + + public: + B1IBox() + { + } + + explicit B1IBox(sal_Int32 nStartValue) + : maRange(nStartValue) + { + } + + B1IBox(sal_Int32 nStartValue1, sal_Int32 nStartValue2) + : maRange(nStartValue1) + { + expand(nStartValue2); + } + + B1IBox(const B1IBox& rBox) + : maRange(rBox.maRange) + { + } + + bool isEmpty() const + { + return maRange.isEmpty(); + } + + void reset() + { + maRange.reset(); + } + + bool operator==( const B1IBox& rBox ) const + { + return (maRange == rBox.maRange); + } + + bool operator!=( const B1IBox& rBox ) const + { + return (maRange != rBox.maRange); + } + + void operator=(const B1IBox& rBox) + { + maRange = rBox.maRange; + } + + sal_Int32 getMinimum() const + { + return maRange.getMinimum(); + } + + sal_Int32 getMaximum() const + { + return maRange.getMaximum(); + } + + Int32Traits::DifferenceType getRange() const + { + return maRange.getRange(); + } + + double getCenter() const + { + return maRange.getCenter(); + } + + bool isInside(sal_Int32 nValue) const + { + return maRange.isInside(nValue); + } + + bool isInside(const B1IBox& rBox) const + { + return maRange.isInside(rBox.maRange); + } + + bool overlaps(const B1IBox& rBox) const + { + return maRange.overlaps(rBox.maRange); + } + + void expand(sal_Int32 nValue) + { + maRange.expand(nValue); + } + + void expand(const B1IBox& rBox) + { + maRange.expand(rBox.maRange); + } + + void intersect(const B1IBox& rBox) + { + maRange.intersect(rBox.maRange); + } + + void grow(sal_Int32 nValue) + { + maRange.grow(nValue); + } + }; +} // end of namespace basegfx + +#endif /* _BGFX_RANGE_B1IBOX_HXX */ diff --git a/basegfx/inc/basegfx/range/b1irange.hxx b/basegfx/inc/basegfx/range/b1irange.hxx new file mode 100644 index 000000000000..6c65fbaebff4 --- /dev/null +++ b/basegfx/inc/basegfx/range/b1irange.hxx @@ -0,0 +1,144 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B1IRANGE_HXX +#define _BGFX_RANGE_B1IRANGE_HXX + +#include <basegfx/range/basicrange.hxx> + + +namespace basegfx +{ + class B1IRange + { + ::basegfx::BasicRange< sal_Int32, Int32Traits > maRange; + + public: + B1IRange() + { + } + + explicit B1IRange(sal_Int32 nStartValue) + : maRange(nStartValue) + { + } + + B1IRange(sal_Int32 nStartValue1, sal_Int32 nStartValue2) + : maRange(nStartValue1) + { + expand(nStartValue2); + } + + B1IRange(const B1IRange& rRange) + : maRange(rRange.maRange) + { + } + + bool isEmpty() const + { + return maRange.isEmpty(); + } + + void reset() + { + maRange.reset(); + } + + bool operator==( const B1IRange& rRange ) const + { + return (maRange == rRange.maRange); + } + + bool operator!=( const B1IRange& rRange ) const + { + return (maRange != rRange.maRange); + } + + B1IRange& operator=(const B1IRange& rRange) + { + maRange = rRange.maRange; + return *this; + } + + sal_Int32 getMinimum() const + { + return maRange.getMinimum(); + } + + sal_Int32 getMaximum() const + { + return maRange.getMaximum(); + } + + Int32Traits::DifferenceType getRange() const + { + return maRange.getRange(); + } + + double getCenter() const + { + return maRange.getCenter(); + } + + bool isInside(sal_Int32 nValue) const + { + return maRange.isInside(nValue); + } + + bool isInside(const B1IRange& rRange) const + { + return maRange.isInside(rRange.maRange); + } + + bool overlaps(const B1IRange& rRange) const + { + return maRange.overlaps(rRange.maRange); + } + + void expand(sal_Int32 nValue) + { + maRange.expand(nValue); + } + + void expand(const B1IRange& rRange) + { + maRange.expand(rRange.maRange); + } + + void intersect(const B1IRange& rRange) + { + maRange.intersect(rRange.maRange); + } + + void grow(sal_Int32 nValue) + { + maRange.grow(nValue); + } + }; +} // end of namespace basegfx + +#endif /* _BGFX_RANGE_B1IRANGE_HXX */ diff --git a/basegfx/inc/basegfx/range/b2dconnectedranges.hxx b/basegfx/inc/basegfx/range/b2dconnectedranges.hxx new file mode 100644 index 000000000000..6c41ede934cf --- /dev/null +++ b/basegfx/inc/basegfx/range/b2dconnectedranges.hxx @@ -0,0 +1,263 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B2DCONNECTEDRANGES_HXX +#define _BGFX_RANGE_B2DCONNECTEDRANGES_HXX + +#include <osl/diagnose.h> +#include <basegfx/range/b2drange.hxx> +#include <list> +#include <utility> +#include <algorithm> + + +namespace basegfx +{ + /** Calculate connected ranges from input ranges. + + This template constructs a list of connected ranges from the + given input ranges. That is, the output will contain a set of + ranges, itself containing a number of input ranges, which will + be mutually non-intersecting. + + Example: + <code> + ------------------- + | -------| + | | || + | --- | || + | | | -------| -------- + | | +--------- | | | + | --+ | | | | + | | | | -------- + | ---------- | + ------------------- + </code + + Here, the outer rectangles represent the output + ranges. Contained are the input rectangles that comprise these + output ranges. + + @tpl UserData + User data to be stored along with the range, to later identify + which range went into which connected component. Must be + assignable, default- and copy-constructible. + */ + template< typename UserData > class B2DConnectedRanges + { + public: + /// Type of the basic entity (rect + user data) + typedef ::std::pair< B2DRange, UserData > ComponentType; + typedef ::std::list< ComponentType > ComponentListType; + + /// List of (intersecting) components, plus overall bounds + struct ConnectedComponents + { + ComponentListType maComponentList; + B2DRange maTotalBounds; + }; + + typedef ::std::list< ConnectedComponents > ConnectedComponentsType; + + + /// Create the range calculator + B2DConnectedRanges() : + maDisjunctAggregatesList(), + maTotalBounds() + { + } + + /** Query total bounds of all added ranges. + + @return the union bound rect over all added ranges. + */ + B2DRange getBounds() const + { + return maTotalBounds; + } + + /** Add an additional range. + + This method integrates a new range into the connected + ranges lists. The method has a worst-case time complexity + of O(n^2), with n denoting the number of already added + ranges (typically, for well-behaved input, it is O(n) + though). + */ + void addRange( const B2DRange& rRange, + const UserData& rUserData ) + { + // check whether fast path is possible: if new range is + // outside accumulated total range, can add it as a + // separate component right away. + const bool bNotOutsideEverything( + maTotalBounds.overlaps( rRange ) ); + + // update own global bounds range + maTotalBounds.expand( rRange ); + + // assemble anything intersecting with rRange into + // this new connected component + ConnectedComponents aNewConnectedComponent; + + // as at least rRange will be a member of + // aNewConnectedComponent (will be added below), can + // preset the overall bounds here. + aNewConnectedComponent.maTotalBounds = rRange; + + + // + // STAGE 1: Search for intersecting maDisjunctAggregatesList entries + // ================================================================= + // + + // if rRange is empty, it will intersect with no + // maDisjunctAggregatesList member. Thus, we can safe us + // the check. + // if rRange is outside all other rectangle, skip here, + // too + if( bNotOutsideEverything && + !rRange.isEmpty() ) + { + typename ConnectedComponentsType::iterator aCurrAggregate; + typename ConnectedComponentsType::iterator aLastAggregate; + + // flag, determining whether we touched one or more of + // the maDisjunctAggregatesList entries. _If_ we did, + // we have to repeat the intersection process, because + // these changes might have generated new + // intersections. + bool bSomeAggregatesChanged; + + // loop, until bSomeAggregatesChanged stays false + do + { + // only continue loop if 'intersects' branch below was hit + bSomeAggregatesChanged = false; + + // iterate over all current members of maDisjunctAggregatesList + for( aCurrAggregate=maDisjunctAggregatesList.begin(), + aLastAggregate=maDisjunctAggregatesList.end(); + aCurrAggregate != aLastAggregate; ) + { + // first check if current component's bounds + // are empty. This ensures that distinct empty + // components are not merged into one + // aggregate. As a matter of fact, they have + // no position and size. + + if( !aCurrAggregate->maTotalBounds.isEmpty() && + aCurrAggregate->maTotalBounds.overlaps( + aNewConnectedComponent.maTotalBounds ) ) + { + // union the intersecting + // maDisjunctAggregatesList element into + // aNewConnectedComponent + + // calc union bounding box + aNewConnectedComponent.maTotalBounds.expand( aCurrAggregate->maTotalBounds ); + + // extract all aCurrAggregate components + // to aNewConnectedComponent + aNewConnectedComponent.maComponentList.splice( + aNewConnectedComponent.maComponentList.end(), + aCurrAggregate->maComponentList ); + + // remove and delete aCurrAggregate entry + // from list (we've gutted it's content + // above). list::erase() will update our + // iterator with the predecessor here. + aCurrAggregate = maDisjunctAggregatesList.erase( aCurrAggregate ); + + // at least one aggregate changed, need to rescan everything + bSomeAggregatesChanged = true; + } + else + { + aCurrAggregate++; + } + } + } + while( bSomeAggregatesChanged ); + } + + // + // STAGE 2: Add newly generated connected component list element + // ============================================================= + // + + // add new component to the end of the component list + aNewConnectedComponent.maComponentList.push_back( + ComponentType( rRange, rUserData ) ); + + // do some consistency checks (aka post conditions) + OSL_ENSURE( !aNewConnectedComponent.maComponentList.empty(), + "B2DConnectedRanges::addRange(): empty aggregate list" ); + OSL_ENSURE( !aNewConnectedComponent.maTotalBounds.isEmpty() || + (aNewConnectedComponent.maTotalBounds.isEmpty() && + aNewConnectedComponent.maComponentList.size() == 1), + "B2DConnectedRanges::addRange(): empty ranges must be solitary"); + + // add aNewConnectedComponent as a new entry to + // maDisjunctAggregatesList + maDisjunctAggregatesList.push_back( aNewConnectedComponent ); + } + + /** Apply a functor to each of the disjunct component + aggregates. + + @param aFunctor + Functor to apply. Must provide an operator( const ConnectedComponents& ). + + @return a copy of the functor, as applied to all aggregates. + */ + template< typename UnaryFunctor > UnaryFunctor forEachAggregate( UnaryFunctor aFunctor ) const + { + return ::std::for_each( maDisjunctAggregatesList.begin(), + maDisjunctAggregatesList.end(), + aFunctor ); + } + + private: + // default: disabled copy/assignment + B2DConnectedRanges(const B2DConnectedRanges&); + B2DConnectedRanges& operator=( const B2DConnectedRanges& ); + + /** Current list of disjunct sets of connected components + + Each entry corresponds to one of the top-level rectangles + in the drawing above. + */ + ConnectedComponentsType maDisjunctAggregatesList; + + /** Global bound rect over all added ranges. + */ + B2DRange maTotalBounds; + }; +} + +#endif /* _BGFX_RANGE_B2DCONNECTEDRANGES_HXX */ diff --git a/basegfx/inc/basegfx/range/b2dpolyrange.hxx b/basegfx/inc/basegfx/range/b2dpolyrange.hxx new file mode 100644 index 000000000000..2202869dc921 --- /dev/null +++ b/basegfx/inc/basegfx/range/b2dpolyrange.hxx @@ -0,0 +1,145 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: b2dmultirange.hxx,v $ + * $Revision: 1.6 $ + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B2DPOLYRANGE_HXX +#define _BGFX_RANGE_B2DPOLYRANGE_HXX + +#include <o3tl/cow_wrapper.hxx> +#include <boost/tuple/tuple.hpp> +#include <basegfx/vector/b2enums.hxx> + +namespace basegfx +{ + class B2DTuple; + class B2DRange; + class B2DPolyPolygon; + class ImplB2DPolyRange; + + /** Multiple ranges in one object. + + This class combines multiple ranges in one object, providing a + total, enclosing range for it. + + You can use this class e.g. when updating views containing + rectangular objects. Add each modified object to a + B2DMultiRange, then test each viewable object against + intersection with the multi range. + + Similar in spirit to the poly-polygon vs. polygon relationship. + + Note that comparable to polygons, a poly-range can also + contain 'holes' - this is encoded via polygon orientation at + the poly-polygon, and via explicit flags for the poly-range. + */ + class B2DPolyRange + { + public: + typedef boost::tuple<B2DRange,B2VectorOrientation> ElementType ; + + B2DPolyRange(); + ~B2DPolyRange(); + + /** Create a multi range with exactly one containing range + */ + explicit B2DPolyRange( const ElementType& rElement ); + B2DPolyRange( const B2DRange& rRange, B2VectorOrientation eOrient ); + B2DPolyRange( const B2DPolyRange& ); + B2DPolyRange& operator=( const B2DPolyRange& ); + + /// unshare this poly-range with all internally shared instances + void makeUnique(); + + bool operator==(const B2DPolyRange&) const; + bool operator!=(const B2DPolyRange&) const; + + /// Number of included ranges + sal_uInt32 count() const; + + ElementType getElement(sal_uInt32 nIndex) const; + void setElement(sal_uInt32 nIndex, const ElementType& rElement ); + void setElement(sal_uInt32 nIndex, const B2DRange& rRange, B2VectorOrientation eOrient ); + + // insert/append a single range + void insertElement(sal_uInt32 nIndex, const ElementType& rElement, sal_uInt32 nCount = 1); + void insertElement(sal_uInt32 nIndex, const B2DRange& rRange, B2VectorOrientation eOrient, sal_uInt32 nCount = 1); + void appendElement(const ElementType& rElement, sal_uInt32 nCount = 1); + void appendElement(const B2DRange& rRange, B2VectorOrientation eOrient, sal_uInt32 nCount = 1); + + // insert/append multiple ranges + void insertPolyRange(sal_uInt32 nIndex, const B2DPolyRange&); + void appendPolyRange(const B2DPolyRange&); + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount = 1); + void clear(); + + // flip range orientations - converts holes to solids, and vice versa + void flip(); + + /** Get overall range + + @return + The union range of all contained ranges + */ + B2DRange getBounds() const; + + /** Test whether given tuple is inside one or more of the + included ranges. Does *not* use overall range, but checks + individually. + */ + bool isInside( const B2DTuple& rTuple ) const; + + /** Test whether given range is inside one or more of the + included ranges. Does *not* use overall range, but checks + individually. + */ + bool isInside( const B2DRange& rRange ) const; + + /** Test whether given range overlaps one or more of the + included ranges. Does *not* use overall range, but checks + individually. + */ + bool overlaps( const B2DRange& rRange ) const; + + /** Request a poly-polygon with solved cross-overs + */ + B2DPolyPolygon solveCrossovers() const; + + // element iterators (same iterator validity conditions as for vector) + const B2DRange* begin() const; + const B2DRange* end() const; + B2DRange* begin(); + B2DRange* end(); + + private: + o3tl::cow_wrapper< ImplB2DPolyRange > mpImpl; + }; +} + +#endif /* _BGFX_RANGE_B2DPOLYRANGE_HXX */ diff --git a/basegfx/inc/basegfx/range/b2drange.hxx b/basegfx/inc/basegfx/range/b2drange.hxx new file mode 100644 index 000000000000..fc3a6fe53659 --- /dev/null +++ b/basegfx/inc/basegfx/range/b2drange.hxx @@ -0,0 +1,295 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B2DRANGE_HXX +#define _BGFX_RANGE_B2DRANGE_HXX + +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/range/basicrange.hxx> +#include <vector> + + +namespace basegfx +{ + // predeclarations + class B2IRange; + class B2DHomMatrix; + + class B2DRange + { + public: + typedef double ValueType; + typedef DoubleTraits TraitsType; + + B2DRange() + { + } + + explicit B2DRange(const B2DTuple& rTuple) + : maRangeX(rTuple.getX()), + maRangeY(rTuple.getY()) + { + } + + B2DRange(double x1, + double y1, + double x2, + double y2) + : maRangeX(x1), + maRangeY(y1) + { + maRangeX.expand(x2); + maRangeY.expand(y2); + } + + B2DRange(const B2DTuple& rTuple1, + const B2DTuple& rTuple2) + : maRangeX(rTuple1.getX()), + maRangeY(rTuple1.getY()) + { + expand( rTuple2 ); + } + + B2DRange(const B2DRange& rRange) + : maRangeX(rRange.maRangeX), + maRangeY(rRange.maRangeY) + { + } + + explicit B2DRange(const B2IRange& rRange); + + bool isEmpty() const + { + return ( + maRangeX.isEmpty() + || maRangeY.isEmpty() + ); + } + + void reset() + { + maRangeX.reset(); + maRangeY.reset(); + } + + bool operator==( const B2DRange& rRange ) const + { + return (maRangeX == rRange.maRangeX + && maRangeY == rRange.maRangeY); + } + + bool operator!=( const B2DRange& rRange ) const + { + return (maRangeX != rRange.maRangeX + || maRangeY != rRange.maRangeY); + } + + B2DRange& operator=(const B2DRange& rRange) + { + maRangeX = rRange.maRangeX; + maRangeY = rRange.maRangeY; + return *this; + } + + bool equal(const B2DRange& rRange) const + { + return (maRangeX.equal(rRange.maRangeX) + && maRangeY.equal(rRange.maRangeY)); + } + + double getMinX() const + { + return maRangeX.getMinimum(); + } + + double getMinY() const + { + return maRangeY.getMinimum(); + } + + double getMaxX() const + { + return maRangeX.getMaximum(); + } + + double getMaxY() const + { + return maRangeY.getMaximum(); + } + + double getWidth() const + { + return maRangeX.getRange(); + } + + double getHeight() const + { + return maRangeY.getRange(); + } + + B2DPoint getMinimum() const + { + return B2DPoint( + maRangeX.getMinimum(), + maRangeY.getMinimum() + ); + } + + B2DPoint getMaximum() const + { + return B2DPoint( + maRangeX.getMaximum(), + maRangeY.getMaximum() + ); + } + + B2DVector getRange() const + { + return B2DVector( + maRangeX.getRange(), + maRangeY.getRange() + ); + } + + B2DPoint getCenter() const + { + return B2DPoint( + maRangeX.getCenter(), + maRangeY.getCenter() + ); + } + + double getCenterX() const + { + return maRangeX.getCenter(); + } + + double getCenterY() const + { + return maRangeY.getCenter(); + } + + bool isInside(const B2DTuple& rTuple) const + { + return ( + maRangeX.isInside(rTuple.getX()) + && maRangeY.isInside(rTuple.getY()) + ); + } + + bool isInside(const B2DRange& rRange) const + { + return ( + maRangeX.isInside(rRange.maRangeX) + && maRangeY.isInside(rRange.maRangeY) + ); + } + + bool overlaps(const B2DRange& rRange) const + { + return ( + maRangeX.overlaps(rRange.maRangeX) + && maRangeY.overlaps(rRange.maRangeY) + ); + } + + bool overlapsMore(const B2DRange& rRange) const + { + return ( + maRangeX.overlapsMore(rRange.maRangeX) + && maRangeY.overlapsMore(rRange.maRangeY) + ); + } + + void expand(const B2DTuple& rTuple) + { + maRangeX.expand(rTuple.getX()); + maRangeY.expand(rTuple.getY()); + } + + void expand(const B2DRange& rRange) + { + maRangeX.expand(rRange.maRangeX); + maRangeY.expand(rRange.maRangeY); + } + + void intersect(const B2DRange& rRange) + { + maRangeX.intersect(rRange.maRangeX); + maRangeY.intersect(rRange.maRangeY); + } + + void grow(double fValue) + { + maRangeX.grow(fValue); + maRangeY.grow(fValue); + } + + void transform(const B2DHomMatrix& rMatrix); + + private: + typedef ::basegfx::BasicRange< ValueType, TraitsType > MyBasicRange; + + MyBasicRange maRangeX; + MyBasicRange maRangeY; + }; + + /** Round double to nearest integer for 2D range + + @return the nearest integer for this range + */ + B2IRange fround(const B2DRange& rRange); + + /** Compute the set difference of the two given ranges + + This method calculates the symmetric difference (aka XOR) + between the two given ranges, and returning the resulting + ranges. Thus, the result will contain all areas where one, but + not both ranges lie. + + @param o_rResult + Result vector. The up to four difference ranges are returned + within this vector + + @param rFirst + The first range + + @param rSecond + The second range + + @return the input vector + */ + ::std::vector< B2DRange >& computeSetDifference( ::std::vector< B2DRange >& o_rResult, + const B2DRange& rFirst, + const B2DRange& rSecond ); + +} // end of namespace basegfx + + +#endif /* _BGFX_RANGE_B2DRANGE_HXX */ diff --git a/basegfx/inc/basegfx/range/b2drangeclipper.hxx b/basegfx/inc/basegfx/range/b2drangeclipper.hxx new file mode 100644 index 000000000000..3285ffeaffe1 --- /dev/null +++ b/basegfx/inc/basegfx/range/b2drangeclipper.hxx @@ -0,0 +1,53 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: b2dmultirange.hxx,v $ + * $Revision: 1.6 $ + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B2DRANGECLIPPER_HXX +#define _BGFX_RANGE_B2DRANGECLIPPER_HXX + +#include <basegfx/range/b2dpolyrange.hxx> +#include <vector> + +namespace basegfx +{ + namespace tools + { + /** Extract poly-polygon w/o self-intersections from poly-range + + Similar to the solveCrossovers(const B2DPolyPolygon&) + method, this one calculates a self-intersection-free + poly-polygon with the same topology, and encoding + inside/outsidedness via polygon orientation and layering. + */ + B2DPolyPolygon solveCrossovers(const std::vector<B2DRange>& rRanges, + const std::vector<B2VectorOrientation>& rOrientations); + } +} + +#endif /* _BGFX_RANGE_B2DRANGECLIPPER_HXX */ diff --git a/basegfx/inc/basegfx/range/b2drectangle.hxx b/basegfx/inc/basegfx/range/b2drectangle.hxx new file mode 100644 index 000000000000..1fd2087f0bcd --- /dev/null +++ b/basegfx/inc/basegfx/range/b2drectangle.hxx @@ -0,0 +1,42 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B2DRECTANGLE_HXX +#define _BGFX_RANGE_B2DRECTANGLE_HXX + +#include <basegfx/range/b2drange.hxx> + +namespace basegfx +{ + // syntactic sugar: a B2DRange exactly models a Rectangle, thus, + // for interface clarity, we provide an alias name + + /// Alias name for interface clarity (not everybody is aware of the identity) + typedef B2DRange B2DRectangle; +} + +#endif /* _BGFX_RANGE_B2DRECTANGLE_HXX */ diff --git a/basegfx/inc/basegfx/range/b2ibox.hxx b/basegfx/inc/basegfx/range/b2ibox.hxx new file mode 100644 index 000000000000..a188c7e8abe6 --- /dev/null +++ b/basegfx/inc/basegfx/range/b2ibox.hxx @@ -0,0 +1,251 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B2IBOX_HXX +#define _BGFX_RANGE_B2IBOX_HXX + +#include <basegfx/point/b2ipoint.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/tuple/b2ituple.hxx> +#include <basegfx/tuple/b2i64tuple.hxx> +#include <basegfx/range/basicbox.hxx> +#include <vector> + + +namespace basegfx +{ + class B2IBox + { + public: + typedef sal_Int32 ValueType; + typedef Int32Traits TraitsType; + + B2IBox() + { + } + + explicit B2IBox(const B2ITuple& rTuple) + : maRangeX(rTuple.getX()), + maRangeY(rTuple.getY()) + { + } + + B2IBox(sal_Int32 x1, + sal_Int32 y1, + sal_Int32 x2, + sal_Int32 y2) : + maRangeX(x1), + maRangeY(y1) + { + maRangeX.expand(x2); + maRangeY.expand(y2); + } + + B2IBox(const B2ITuple& rTuple1, + const B2ITuple& rTuple2) : + maRangeX(rTuple1.getX()), + maRangeY(rTuple1.getY()) + { + expand( rTuple2 ); + } + + B2IBox(const B2IBox& rBox) : + maRangeX(rBox.maRangeX), + maRangeY(rBox.maRangeY) + { + } + + bool isEmpty() const + { + return maRangeX.isEmpty() || maRangeY.isEmpty(); + } + + void reset() + { + maRangeX.reset(); + maRangeY.reset(); + } + + bool operator==( const B2IBox& rBox ) const + { + return (maRangeX == rBox.maRangeX + && maRangeY == rBox.maRangeY); + } + + bool operator!=( const B2IBox& rBox ) const + { + return (maRangeX != rBox.maRangeX + || maRangeY != rBox.maRangeY); + } + + void operator=(const B2IBox& rBox) + { + maRangeX = rBox.maRangeX; + maRangeY = rBox.maRangeY; + } + + sal_Int32 getMinX() const + { + return maRangeX.getMinimum(); + } + + sal_Int32 getMinY() const + { + return maRangeY.getMinimum(); + } + + sal_Int32 getMaxX() const + { + return maRangeX.getMaximum(); + } + + sal_Int32 getMaxY() const + { + return maRangeY.getMaximum(); + } + + sal_Int64 getWidth() const + { + return maRangeX.getRange(); + } + + sal_Int64 getHeight() const + { + return maRangeY.getRange(); + } + + B2IPoint getMinimum() const + { + return B2IPoint( + maRangeX.getMinimum(), + maRangeY.getMinimum() + ); + } + + B2IPoint getMaximum() const + { + return B2IPoint( + maRangeX.getMaximum(), + maRangeY.getMaximum() + ); + } + + B2I64Tuple getRange() const + { + return B2I64Tuple( + maRangeX.getRange(), + maRangeY.getRange() + ); + } + + B2DPoint getCenter() const + { + return B2DPoint( + maRangeX.getCenter(), + maRangeY.getCenter() + ); + } + + bool isInside(const B2ITuple& rTuple) const + { + return ( + maRangeX.isInside(rTuple.getX()) + && maRangeY.isInside(rTuple.getY()) + ); + } + + bool isInside(const B2IBox& rBox) const + { + return ( + maRangeX.isInside(rBox.maRangeX) + && maRangeY.isInside(rBox.maRangeY) + ); + } + + bool overlaps(const B2IBox& rBox) const + { + return ( + maRangeX.overlaps(rBox.maRangeX) + && maRangeY.overlaps(rBox.maRangeY) + ); + } + + void expand(const B2ITuple& rTuple) + { + maRangeX.expand(rTuple.getX()); + maRangeY.expand(rTuple.getY()); + } + + void expand(const B2IBox& rBox) + { + maRangeX.expand(rBox.maRangeX); + maRangeY.expand(rBox.maRangeY); + } + + void intersect(const B2IBox& rBox) + { + maRangeX.intersect(rBox.maRangeX); + maRangeY.intersect(rBox.maRangeY); + } + + void grow(sal_Int32 nValue) + { + maRangeX.grow(nValue); + maRangeY.grow(nValue); + } + + private: + BasicBox maRangeX; + BasicBox maRangeY; + }; + + /** Compute the set difference of the two given boxes + + This method calculates the symmetric difference (aka XOR) + between the two given boxes, and returning the resulting + boxes. Thus, the result will contain all areas where one, but + not both boxes lie. + + @param o_rResult + Result vector. The up to four difference boxes are returned + within this vector + + @param rFirst + The first box + + @param rSecond + The second box + + @return the input vector + */ + ::std::vector< B2IBox >& computeSetDifference( ::std::vector< B2IBox >& o_rResult, + const B2IBox& rFirst, + const B2IBox& rSecond ); + +} // end of namespace basegfx + +#endif /* _BGFX_RANGE_B2IBOX_HXX */ diff --git a/basegfx/inc/basegfx/range/b2irange.hxx b/basegfx/inc/basegfx/range/b2irange.hxx new file mode 100644 index 000000000000..8d4690283da0 --- /dev/null +++ b/basegfx/inc/basegfx/range/b2irange.hxx @@ -0,0 +1,254 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B2IRANGE_HXX +#define _BGFX_RANGE_B2IRANGE_HXX + +#include <basegfx/point/b2ipoint.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/tuple/b2ituple.hxx> +#include <basegfx/tuple/b2i64tuple.hxx> +#include <basegfx/range/basicrange.hxx> +#include <vector> + + +namespace basegfx +{ + class B2IRange + { + public: + typedef sal_Int32 ValueType; + typedef Int32Traits TraitsType; + + B2IRange() + { + } + + explicit B2IRange(const B2ITuple& rTuple) + : maRangeX(rTuple.getX()), + maRangeY(rTuple.getY()) + { + } + + B2IRange(sal_Int32 x1, + sal_Int32 y1, + sal_Int32 x2, + sal_Int32 y2) + : maRangeX(x1), + maRangeY(y1) + { + maRangeX.expand(x2); + maRangeY.expand(y2); + } + + B2IRange(const B2ITuple& rTuple1, + const B2ITuple& rTuple2) + : maRangeX(rTuple1.getX()), + maRangeY(rTuple1.getY()) + { + expand( rTuple2 ); + } + + B2IRange(const B2IRange& rRange) + : maRangeX(rRange.maRangeX), + maRangeY(rRange.maRangeY) + { + } + + bool isEmpty() const + { + return maRangeX.isEmpty() || maRangeY.isEmpty(); + } + + void reset() + { + maRangeX.reset(); + maRangeY.reset(); + } + + bool operator==( const B2IRange& rRange ) const + { + return (maRangeX == rRange.maRangeX + && maRangeY == rRange.maRangeY); + } + + bool operator!=( const B2IRange& rRange ) const + { + return (maRangeX != rRange.maRangeX + || maRangeY != rRange.maRangeY); + } + + B2IRange& operator=(const B2IRange& rRange) + { + maRangeX = rRange.maRangeX; + maRangeY = rRange.maRangeY; + return *this; + } + + sal_Int32 getMinX() const + { + return maRangeX.getMinimum(); + } + + sal_Int32 getMinY() const + { + return maRangeY.getMinimum(); + } + + sal_Int32 getMaxX() const + { + return maRangeX.getMaximum(); + } + + sal_Int32 getMaxY() const + { + return maRangeY.getMaximum(); + } + + sal_Int64 getWidth() const + { + return maRangeX.getRange(); + } + + sal_Int64 getHeight() const + { + return maRangeY.getRange(); + } + + B2IPoint getMinimum() const + { + return B2IPoint( + maRangeX.getMinimum(), + maRangeY.getMinimum() + ); + } + + B2IPoint getMaximum() const + { + return B2IPoint( + maRangeX.getMaximum(), + maRangeY.getMaximum() + ); + } + + B2I64Tuple getRange() const + { + return B2I64Tuple( + maRangeX.getRange(), + maRangeY.getRange() + ); + } + + B2DPoint getCenter() const + { + return B2DPoint( + maRangeX.getCenter(), + maRangeY.getCenter() + ); + } + + bool isInside(const B2ITuple& rTuple) const + { + return ( + maRangeX.isInside(rTuple.getX()) + && maRangeY.isInside(rTuple.getY()) + ); + } + + bool isInside(const B2IRange& rRange) const + { + return ( + maRangeX.isInside(rRange.maRangeX) + && maRangeY.isInside(rRange.maRangeY) + ); + } + + bool overlaps(const B2IRange& rRange) const + { + return ( + maRangeX.overlaps(rRange.maRangeX) + && maRangeY.overlaps(rRange.maRangeY) + ); + } + + void expand(const B2ITuple& rTuple) + { + maRangeX.expand(rTuple.getX()); + maRangeY.expand(rTuple.getY()); + } + + void expand(const B2IRange& rRange) + { + maRangeX.expand(rRange.maRangeX); + maRangeY.expand(rRange.maRangeY); + } + + void intersect(const B2IRange& rRange) + { + maRangeX.intersect(rRange.maRangeX); + maRangeY.intersect(rRange.maRangeY); + } + + void grow(sal_Int32 nValue) + { + maRangeX.grow(nValue); + maRangeY.grow(nValue); + } + + private: + typedef ::basegfx::BasicRange< ValueType, TraitsType > MyBasicRange; + + MyBasicRange maRangeX; + MyBasicRange maRangeY; + }; + + /** Compute the set difference of the two given ranges + + This method calculates the symmetric difference (aka XOR) + between the two given ranges, and returning the resulting + ranges. Thus, the result will contain all areas where one, but + not both ranges lie. + + @param o_rResult + Result vector. The up to four difference ranges are returned + within this vector + + @param rFirst + The first range + + @param rSecond + The second range + + @return the input vector + */ + ::std::vector< B2IRange >& computeSetDifference( ::std::vector< B2IRange >& o_rResult, + const B2IRange& rFirst, + const B2IRange& rSecond ); + +} // end of namespace basegfx + +#endif /* _BGFX_RANGE_B2IRANGE_HXX */ diff --git a/basegfx/inc/basegfx/range/b2irectangle.hxx b/basegfx/inc/basegfx/range/b2irectangle.hxx new file mode 100644 index 000000000000..a9e46c82710c --- /dev/null +++ b/basegfx/inc/basegfx/range/b2irectangle.hxx @@ -0,0 +1,42 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B2IRECTANGLE_HXX +#define _BGFX_RANGE_B2IRECTANGLE_HXX + +#include <basegfx/range/b2irange.hxx> + +namespace basegfx +{ + // syntactic sugar: a B2IRange exactly models a Rectangle, thus, + // for interface clarity, we provide an alias name + + /// Alias name for interface clarity (not everybody is aware of the identity) + typedef B2IRange B2IRectangle; +} + +#endif /* _BGFX_RANGE_B2IRECTANGLE_HXX */ diff --git a/basegfx/inc/basegfx/range/b3drange.hxx b/basegfx/inc/basegfx/range/b3drange.hxx new file mode 100644 index 000000000000..2a2a42aa0718 --- /dev/null +++ b/basegfx/inc/basegfx/range/b3drange.hxx @@ -0,0 +1,302 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B3DRANGE_HXX +#define _BGFX_RANGE_B3DRANGE_HXX + +#include <basegfx/vector/b3dvector.hxx> +#include <basegfx/point/b3dpoint.hxx> +#include <basegfx/tuple/b3dtuple.hxx> +#include <basegfx/range/basicrange.hxx> + +namespace basegfx +{ + // predeclarations + class B3IRange; + class B3DHomMatrix; + + class B3DRange + { + typedef ::basegfx::BasicRange< double, DoubleTraits > MyBasicRange; + + MyBasicRange maRangeX; + MyBasicRange maRangeY; + MyBasicRange maRangeZ; + + public: + B3DRange() + { + } + + explicit B3DRange(const B3DTuple& rTuple) + : maRangeX(rTuple.getX()), + maRangeY(rTuple.getY()), + maRangeZ(rTuple.getZ()) + { + } + + B3DRange(double x1, + double y1, + double z1, + double x2, + double y2, + double z2) + : maRangeX(x1), + maRangeY(y1), + maRangeZ(z1) + { + maRangeX.expand(x2); + maRangeY.expand(y2); + maRangeZ.expand(z2); + } + + B3DRange(const B3DTuple& rTuple1, + const B3DTuple& rTuple2) + : maRangeX(rTuple1.getX()), + maRangeY(rTuple1.getY()), + maRangeZ(rTuple1.getZ()) + { + expand(rTuple2); + } + + B3DRange(const B3DRange& rRange) + : maRangeX(rRange.maRangeX), + maRangeY(rRange.maRangeY), + maRangeZ(rRange.maRangeZ) + { + } + + explicit B3DRange(const B3IRange& rRange); + + bool isEmpty() const + { + return ( + maRangeX.isEmpty() + || maRangeY.isEmpty() + || maRangeZ.isEmpty() + ); + } + + void reset() + { + maRangeX.reset(); + maRangeY.reset(); + maRangeZ.reset(); + } + + bool operator==( const B3DRange& rRange ) const + { + return (maRangeX == rRange.maRangeX + && maRangeY == rRange.maRangeY + && maRangeZ == rRange.maRangeZ); + } + + bool operator!=( const B3DRange& rRange ) const + { + return (maRangeX != rRange.maRangeX + || maRangeY != rRange.maRangeY + || maRangeZ != rRange.maRangeZ); + } + + B3DRange& operator=(const B3DRange& rRange) + { + maRangeX = rRange.maRangeX; + maRangeY = rRange.maRangeY; + maRangeZ = rRange.maRangeZ; + return *this; + } + + bool equal(const B3DRange& rRange) const + { + return (maRangeX.equal(rRange.maRangeX) + && maRangeY.equal(rRange.maRangeY) + && maRangeZ.equal(rRange.maRangeZ)); + } + + double getMinX() const + { + return maRangeX.getMinimum(); + } + + double getMinY() const + { + return maRangeY.getMinimum(); + } + + double getMinZ() const + { + return maRangeZ.getMinimum(); + } + + double getMaxX() const + { + return maRangeX.getMaximum(); + } + + double getMaxY() const + { + return maRangeY.getMaximum(); + } + + double getMaxZ() const + { + return maRangeZ.getMaximum(); + } + + double getWidth() const + { + return maRangeX.getRange(); + } + + double getHeight() const + { + return maRangeY.getRange(); + } + + double getDepth() const + { + return maRangeZ.getRange(); + } + + B3DPoint getMinimum() const + { + return B3DPoint( + maRangeX.getMinimum(), + maRangeY.getMinimum(), + maRangeZ.getMinimum() + ); + } + + B3DPoint getMaximum() const + { + return B3DPoint( + maRangeX.getMaximum(), + maRangeY.getMaximum(), + maRangeZ.getMaximum() + ); + } + + B3DVector getRange() const + { + return B3DVector( + maRangeX.getRange(), + maRangeY.getRange(), + maRangeZ.getRange() + ); + } + + B3DPoint getCenter() const + { + return B3DPoint( + maRangeX.getCenter(), + maRangeY.getCenter(), + maRangeZ.getCenter() + ); + } + + double getCenterX() const + { + return maRangeX.getCenter(); + } + + double getCenterY() const + { + return maRangeY.getCenter(); + } + + double getCenterZ() const + { + return maRangeZ.getCenter(); + } + + bool isInside(const B3DTuple& rTuple) const + { + return ( + maRangeX.isInside(rTuple.getX()) + && maRangeY.isInside(rTuple.getY()) + && maRangeZ.isInside(rTuple.getZ()) + ); + } + + bool isInside(const B3DRange& rRange) const + { + return ( + maRangeX.isInside(rRange.maRangeX) + && maRangeY.isInside(rRange.maRangeY) + && maRangeZ.isInside(rRange.maRangeZ) + ); + } + + bool overlaps(const B3DRange& rRange) const + { + return ( + maRangeX.overlaps(rRange.maRangeX) + && maRangeY.overlaps(rRange.maRangeY) + && maRangeZ.overlaps(rRange.maRangeZ) + ); + } + + void expand(const B3DTuple& rTuple) + { + maRangeX.expand(rTuple.getX()); + maRangeY.expand(rTuple.getY()); + maRangeZ.expand(rTuple.getZ()); + } + + void expand(const B3DRange& rRange) + { + maRangeX.expand(rRange.maRangeX); + maRangeY.expand(rRange.maRangeY); + maRangeZ.expand(rRange.maRangeZ); + } + + void intersect(const B3DRange& rRange) + { + maRangeX.intersect(rRange.maRangeX); + maRangeY.intersect(rRange.maRangeY); + maRangeZ.intersect(rRange.maRangeZ); + } + + void grow(double fValue) + { + maRangeX.grow(fValue); + maRangeY.grow(fValue); + maRangeZ.grow(fValue); + } + + void transform(const B3DHomMatrix& rMatrix); + }; + + /** Round double to nearest integer for 3D range + + @return the nearest integer for this range + */ + B3IRange fround(const B3DRange& rRange); +} // end of namespace basegfx + + +#endif /* _BGFX_RANGE_B3DRANGE_HXX */ diff --git a/basegfx/inc/basegfx/range/b3dvolume.hxx b/basegfx/inc/basegfx/range/b3dvolume.hxx new file mode 100644 index 000000000000..18163d5b6c34 --- /dev/null +++ b/basegfx/inc/basegfx/range/b3dvolume.hxx @@ -0,0 +1,42 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B3DBOX_HXX +#define _BGFX_RANGE_B3DBOX_HXX + +#include <basegfx/range/b3drange.hxx> + +namespace basegfx +{ + // syntactic sugar: a B3DRange exactly models a Volume in 3D, thus, + // for interface clarity, we provide an alias name + + /// Alias name for interface clarity (not everybody is aware of the identity) + typedef B3DRange B3DVolume; +} + +#endif /* _BGFX_RANGE_B3DBOX_HXX */ diff --git a/basegfx/inc/basegfx/range/b3ibox.hxx b/basegfx/inc/basegfx/range/b3ibox.hxx new file mode 100644 index 000000000000..f9693465a2bb --- /dev/null +++ b/basegfx/inc/basegfx/range/b3ibox.hxx @@ -0,0 +1,259 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B3IBOX_HXX +#define _BGFX_RANGE_B3IBOX_HXX + +#include <basegfx/point/b3ipoint.hxx> +#include <basegfx/point/b3dpoint.hxx> +#include <basegfx/tuple/b3ituple.hxx> +#include <basegfx/tuple/b3i64tuple.hxx> +#include <basegfx/range/basicbox.hxx> + +namespace basegfx +{ + class B3IBox + { + BasicBox maRangeX; + BasicBox maRangeY; + BasicBox maRangeZ; + + public: + B3IBox() + { + } + + explicit B3IBox(const B3ITuple& rTuple) : + maRangeX(rTuple.getX()), + maRangeY(rTuple.getY()), + maRangeZ(rTuple.getZ()) + { + } + + B3IBox(sal_Int32 x1, + sal_Int32 y1, + sal_Int32 z1, + sal_Int32 x2, + sal_Int32 y2, + sal_Int32 z2) : + maRangeX(x1), + maRangeY(y1), + maRangeZ(z1) + { + maRangeX.expand(x2); + maRangeY.expand(y2); + maRangeZ.expand(z2); + } + + B3IBox(const B3ITuple& rTuple1, + const B3ITuple& rTuple2) : + maRangeX(rTuple1.getX()), + maRangeY(rTuple1.getY()), + maRangeZ(rTuple1.getZ()) + { + expand(rTuple2); + } + + B3IBox(const B3IBox& rBox) : + maRangeX(rBox.maRangeX), + maRangeY(rBox.maRangeY), + maRangeZ(rBox.maRangeZ) + { + } + + bool isEmpty() const + { + return maRangeX.isEmpty() || maRangeY.isEmpty() || maRangeZ.isEmpty(); + } + + void reset() + { + maRangeX.reset(); + maRangeY.reset(); + maRangeZ.reset(); + } + + bool operator==( const B3IBox& rBox ) const + { + return (maRangeX == rBox.maRangeX + && maRangeY == rBox.maRangeY + && maRangeZ == rBox.maRangeZ); + } + + bool operator!=( const B3IBox& rBox ) const + { + return (maRangeX != rBox.maRangeX + || maRangeY != rBox.maRangeY + || maRangeZ != rBox.maRangeZ); + } + + void operator=(const B3IBox& rBox) + { + maRangeX = rBox.maRangeX; + maRangeY = rBox.maRangeY; + maRangeZ = rBox.maRangeZ; + } + + sal_Int32 getMinX() const + { + return maRangeX.getMinimum(); + } + + sal_Int32 getMinY() const + { + return maRangeY.getMinimum(); + } + + sal_Int32 getMinZ() const + { + return maRangeZ.getMinimum(); + } + + sal_Int32 getMaxX() const + { + return maRangeX.getMaximum(); + } + + sal_Int32 getMaxY() const + { + return maRangeY.getMaximum(); + } + + sal_Int32 getMaxZ() const + { + return maRangeZ.getMaximum(); + } + + sal_Int64 getWidth() const + { + return maRangeX.getRange(); + } + + sal_Int64 getHeight() const + { + return maRangeY.getRange(); + } + + sal_Int64 getDepth() const + { + return maRangeZ.getRange(); + } + + B3IPoint getMinimum() const + { + return B3IPoint( + maRangeX.getMinimum(), + maRangeY.getMinimum(), + maRangeZ.getMinimum() + ); + } + + B3IPoint getMaximum() const + { + return B3IPoint( + maRangeX.getMaximum(), + maRangeY.getMaximum(), + maRangeZ.getMaximum() + ); + } + + B3I64Tuple getRange() const + { + return B3I64Tuple( + maRangeX.getRange(), + maRangeY.getRange(), + maRangeZ.getRange() + ); + } + + B3DPoint getCenter() const + { + return B3DPoint( + maRangeX.getCenter(), + maRangeY.getCenter(), + maRangeZ.getCenter() + ); + } + + bool isInside(const B3ITuple& rTuple) const + { + return ( + maRangeX.isInside(rTuple.getX()) + && maRangeY.isInside(rTuple.getY()) + && maRangeZ.isInside(rTuple.getZ()) + ); + } + + bool isInside(const B3IBox& rBox) const + { + return ( + maRangeX.isInside(rBox.maRangeX) + && maRangeY.isInside(rBox.maRangeY) + && maRangeZ.isInside(rBox.maRangeZ) + ); + } + + bool overlaps(const B3IBox& rBox) const + { + return ( + maRangeX.overlaps(rBox.maRangeX) + && maRangeY.overlaps(rBox.maRangeY) + && maRangeZ.overlaps(rBox.maRangeZ) + ); + } + + void expand(const B3ITuple& rTuple) + { + maRangeX.expand(rTuple.getX()); + maRangeY.expand(rTuple.getY()); + maRangeZ.expand(rTuple.getZ()); + } + + void expand(const B3IBox& rBox) + { + maRangeX.expand(rBox.maRangeX); + maRangeY.expand(rBox.maRangeY); + maRangeZ.expand(rBox.maRangeZ); + } + + void intersect(const B3IBox& rBox) + { + maRangeX.intersect(rBox.maRangeX); + maRangeY.intersect(rBox.maRangeY); + maRangeZ.intersect(rBox.maRangeZ); + } + + void grow(sal_Int32 nValue) + { + maRangeX.grow(nValue); + maRangeY.grow(nValue); + maRangeZ.grow(nValue); + } + }; +} // end of namespace basegfx + +#endif /* _BGFX_RANGE_B3IBOX_HXX */ diff --git a/basegfx/inc/basegfx/range/b3irange.hxx b/basegfx/inc/basegfx/range/b3irange.hxx new file mode 100644 index 000000000000..e2b9a08c3093 --- /dev/null +++ b/basegfx/inc/basegfx/range/b3irange.hxx @@ -0,0 +1,262 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B3IRANGE_HXX +#define _BGFX_RANGE_B3IRANGE_HXX + +#include <basegfx/point/b3ipoint.hxx> +#include <basegfx/point/b3dpoint.hxx> +#include <basegfx/tuple/b3ituple.hxx> +#include <basegfx/tuple/b3i64tuple.hxx> +#include <basegfx/range/basicrange.hxx> + +namespace basegfx +{ + class B3IRange + { + typedef ::basegfx::BasicRange< sal_Int32, Int32Traits > MyBasicRange; + + MyBasicRange maRangeX; + MyBasicRange maRangeY; + MyBasicRange maRangeZ; + + public: + B3IRange() + { + } + + explicit B3IRange(const B3ITuple& rTuple) + : maRangeX(rTuple.getX()), + maRangeY(rTuple.getY()), + maRangeZ(rTuple.getZ()) + { + } + + B3IRange(sal_Int32 x1, + sal_Int32 y1, + sal_Int32 z1, + sal_Int32 x2, + sal_Int32 y2, + sal_Int32 z2) + : maRangeX(x1), + maRangeY(y1), + maRangeZ(z1) + { + maRangeX.expand(x2); + maRangeY.expand(y2); + maRangeZ.expand(z2); + } + + B3IRange(const B3ITuple& rTuple1, + const B3ITuple& rTuple2) + : maRangeX(rTuple1.getX()), + maRangeY(rTuple1.getY()), + maRangeZ(rTuple1.getZ()) + { + expand(rTuple2); + } + + B3IRange(const B3IRange& rRange) + : maRangeX(rRange.maRangeX), + maRangeY(rRange.maRangeY), + maRangeZ(rRange.maRangeZ) + { + } + + bool isEmpty() const + { + return maRangeX.isEmpty() || maRangeY.isEmpty() || maRangeZ.isEmpty(); + } + + void reset() + { + maRangeX.reset(); + maRangeY.reset(); + maRangeZ.reset(); + } + + bool operator==( const B3IRange& rRange ) const + { + return (maRangeX == rRange.maRangeX + && maRangeY == rRange.maRangeY + && maRangeZ == rRange.maRangeZ); + } + + bool operator!=( const B3IRange& rRange ) const + { + return (maRangeX != rRange.maRangeX + || maRangeY != rRange.maRangeY + || maRangeZ != rRange.maRangeZ); + } + + B3IRange& operator=(const B3IRange& rRange) + { + maRangeX = rRange.maRangeX; + maRangeY = rRange.maRangeY; + maRangeZ = rRange.maRangeZ; + return *this; + } + + sal_Int32 getMinX() const + { + return maRangeX.getMinimum(); + } + + sal_Int32 getMinY() const + { + return maRangeY.getMinimum(); + } + + sal_Int32 getMinZ() const + { + return maRangeZ.getMinimum(); + } + + sal_Int32 getMaxX() const + { + return maRangeX.getMaximum(); + } + + sal_Int32 getMaxY() const + { + return maRangeY.getMaximum(); + } + + sal_Int32 getMaxZ() const + { + return maRangeZ.getMaximum(); + } + + sal_Int64 getWidth() const + { + return maRangeX.getRange(); + } + + sal_Int64 getHeight() const + { + return maRangeY.getRange(); + } + + sal_Int64 getDepth() const + { + return maRangeZ.getRange(); + } + + B3IPoint getMinimum() const + { + return B3IPoint( + maRangeX.getMinimum(), + maRangeY.getMinimum(), + maRangeZ.getMinimum() + ); + } + + B3IPoint getMaximum() const + { + return B3IPoint( + maRangeX.getMaximum(), + maRangeY.getMaximum(), + maRangeZ.getMaximum() + ); + } + + B3I64Tuple getRange() const + { + return B3I64Tuple( + maRangeX.getRange(), + maRangeY.getRange(), + maRangeZ.getRange() + ); + } + + B3DPoint getCenter() const + { + return B3DPoint( + maRangeX.getCenter(), + maRangeY.getCenter(), + maRangeZ.getCenter() + ); + } + + bool isInside(const B3ITuple& rTuple) const + { + return ( + maRangeX.isInside(rTuple.getX()) + && maRangeY.isInside(rTuple.getY()) + && maRangeZ.isInside(rTuple.getZ()) + ); + } + + bool isInside(const B3IRange& rRange) const + { + return ( + maRangeX.isInside(rRange.maRangeX) + && maRangeY.isInside(rRange.maRangeY) + && maRangeZ.isInside(rRange.maRangeZ) + ); + } + + bool overlaps(const B3IRange& rRange) const + { + return ( + maRangeX.overlaps(rRange.maRangeX) + && maRangeY.overlaps(rRange.maRangeY) + && maRangeZ.overlaps(rRange.maRangeZ) + ); + } + + void expand(const B3ITuple& rTuple) + { + maRangeX.expand(rTuple.getX()); + maRangeY.expand(rTuple.getY()); + maRangeZ.expand(rTuple.getZ()); + } + + void expand(const B3IRange& rRange) + { + maRangeX.expand(rRange.maRangeX); + maRangeY.expand(rRange.maRangeY); + maRangeZ.expand(rRange.maRangeZ); + } + + void intersect(const B3IRange& rRange) + { + maRangeX.intersect(rRange.maRangeX); + maRangeY.intersect(rRange.maRangeY); + maRangeZ.intersect(rRange.maRangeZ); + } + + void grow(sal_Int32 nValue) + { + maRangeX.grow(nValue); + maRangeY.grow(nValue); + maRangeZ.grow(nValue); + } + }; +} // end of namespace basegfx + +#endif /* _BGFX_RANGE_B3IRANGE_HXX */ diff --git a/basegfx/inc/basegfx/range/b3ivolume.hxx b/basegfx/inc/basegfx/range/b3ivolume.hxx new file mode 100644 index 000000000000..0250a9050251 --- /dev/null +++ b/basegfx/inc/basegfx/range/b3ivolume.hxx @@ -0,0 +1,42 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_B3IBOX_HXX +#define _BGFX_RANGE_B3IBOX_HXX + +#include <basegfx/range/b3irange.hxx> + +namespace basegfx +{ + // syntactic sugar: a B3IRange exactly models a Box in 3D, thus, + // for interface clarity, we provide an alias name + + /// Alias name for interface clarity (not everybody is aware of the identity) + typedef B3IRange B3IBox; +} + +#endif /* _BGFX_RANGE_B3IBOX_HXX */ diff --git a/basegfx/inc/basegfx/range/basicbox.hxx b/basegfx/inc/basegfx/range/basicbox.hxx new file mode 100644 index 000000000000..0a5274bc9bc9 --- /dev/null +++ b/basegfx/inc/basegfx/range/basicbox.hxx @@ -0,0 +1,136 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_BASICBOX_HXX +#define _BGFX_RANGE_BASICBOX_HXX + +#include <basegfx/range/basicrange.hxx> + + +namespace basegfx +{ + /** Specialization of BasicRange, handling the inside predicates + differently. + + This template considers the rightmost and bottommost border as + <em>outside</em> of the range, in contrast to BasicRange, + which considers them inside. + */ + class BasicBox : public BasicRange< sal_Int32, Int32Traits > + { + typedef BasicRange< sal_Int32, Int32Traits > Base; + public: + BasicBox() : + Base() + { + } + + BasicBox( sal_Int32 nValue ) : + Base( nValue ) + { + } + + BasicBox(const BasicBox& rBox) : + Base( rBox ) + { + } + + double getCenter() const + { + if(isEmpty()) + { + return 0.0; + } + else + { + return ((mnMaximum + mnMinimum - 1.0) / 2.0); + } + } + + using Base::isInside; + + bool isInside(sal_Int32 nValue) const + { + if(isEmpty()) + { + return false; + } + else + { + return (nValue >= mnMinimum) && (nValue < mnMaximum); + } + } + + using Base::overlaps; + + bool overlaps(const BasicBox& rBox) const + { + if(isEmpty()) + { + return false; + } + else + { + if(rBox.isEmpty()) + { + return false; + } + else + { + return !((rBox.mnMaximum <= mnMinimum) || (rBox.mnMinimum >= mnMaximum)); + } + } + } + + void grow(sal_Int32 nValue) + { + if(!isEmpty()) + { + bool bLessThanZero(nValue < 0); + + if(nValue > 0 || bLessThanZero) + { + mnMinimum -= nValue; + mnMaximum += nValue; + + if(bLessThanZero) + { + // test if range did collapse + if(mnMinimum > mnMaximum) + { + // if yes, collapse to center + mnMinimum = mnMaximum = ((mnMaximum + mnMinimum - 1) / 2); + } + } + } + } + } + }; + +} // end of namespace basegfx + +#endif /* _BGFX_RANGE_BASICBOX_HXX */ diff --git a/basegfx/inc/basegfx/range/basicrange.hxx b/basegfx/inc/basegfx/range/basicrange.hxx new file mode 100644 index 000000000000..578e36824cf3 --- /dev/null +++ b/basegfx/inc/basegfx/range/basicrange.hxx @@ -0,0 +1,297 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_BASICRANGE_HXX +#define _BGFX_RANGE_BASICRANGE_HXX + +#include <sal/types.h> +#include <float.h> +#include <basegfx/numeric/ftools.hxx> + + +namespace basegfx +{ + template< typename T, typename Traits > class BasicRange + { + protected: + T mnMinimum; + T mnMaximum; + + public: + typedef T ValueType; + typedef Traits TraitsType; + + BasicRange() : + mnMinimum(Traits::maxVal()), + mnMaximum(Traits::minVal()) + { + } + + BasicRange( T nValue ) : + mnMinimum(nValue), + mnMaximum(nValue) + { + } + + BasicRange(const BasicRange& rRange) : + mnMinimum(rRange.mnMinimum), + mnMaximum(rRange.mnMaximum) + { + } + + void reset() + { + mnMinimum = Traits::maxVal(); + mnMaximum = Traits::minVal(); + } + + bool isEmpty() const + { + return Traits::maxVal() == mnMinimum; + } + + T getMinimum() const { return mnMinimum; } + T getMaximum() const { return mnMaximum; } + + double getCenter() const + { + if(isEmpty()) + { + return 0.0; + } + else + { + return ((mnMaximum + mnMinimum) / 2.0); + } + } + + bool isInside(T nValue) const + { + if(isEmpty()) + { + return false; + } + else + { + return (nValue >= mnMinimum) && (nValue <= mnMaximum); + } + } + + bool isInside(const BasicRange& rRange) const + { + if(isEmpty()) + { + return false; + } + else + { + if(rRange.isEmpty()) + { + return false; + } + else + { + return (rRange.mnMinimum >= mnMinimum) && (rRange.mnMaximum <= mnMaximum); + } + } + } + + bool overlaps(const BasicRange& rRange) const + { + if(isEmpty()) + { + return false; + } + else + { + if(rRange.isEmpty()) + { + return false; + } + else + { + return !((rRange.mnMaximum < mnMinimum) || (rRange.mnMinimum > mnMaximum)); + } + } + } + + bool overlapsMore(const BasicRange& rRange) const + { + if(isEmpty() || rRange.isEmpty()) + return false; + // returns true if the overlap is more than just a touching at the limits + return ((rRange.mnMaximum > mnMinimum) && (rRange.mnMinimum < mnMaximum)); + } + + bool operator==( const BasicRange& rRange ) const + { + return (mnMinimum == rRange.mnMinimum && mnMaximum == rRange.mnMaximum); + } + + bool operator!=( const BasicRange& rRange ) const + { + return (mnMinimum != rRange.mnMinimum || mnMaximum != rRange.mnMaximum); + } + + BasicRange& operator=(const BasicRange& rRange) + { + mnMinimum = rRange.mnMinimum; + mnMaximum = rRange.mnMaximum; + return *this; + } + + bool equal(const BasicRange& rRange) const + { + return ( + fTools::equal(mnMinimum, rRange.mnMinimum) && + fTools::equal(mnMaximum, rRange.mnMaximum)); + } + + void expand(T nValue) + { + if(isEmpty()) + { + mnMinimum = mnMaximum = nValue; + } + else + { + if(nValue < mnMinimum) + { + mnMinimum = nValue; + } + + if(nValue > mnMaximum) + { + mnMaximum = nValue; + } + } + } + + void expand(const BasicRange& rRange) + { + if(isEmpty()) + { + mnMinimum = rRange.mnMinimum; + mnMaximum = rRange.mnMaximum; + } + else + { + if(!rRange.isEmpty()) + { + if(rRange.mnMinimum < mnMinimum) + { + mnMinimum = rRange.mnMinimum; + } + + if(rRange.mnMaximum > mnMaximum) + { + mnMaximum = rRange.mnMaximum; + } + } + } + } + + void intersect(const BasicRange& rRange) + { + // here, overlaps also tests all isEmpty() conditions already. + if( !overlaps( rRange ) ) + { + reset(); + } + else + { + if(rRange.mnMinimum > mnMinimum) + { + mnMinimum = rRange.mnMinimum; + } + + if(rRange.mnMaximum < mnMaximum) + { + mnMaximum = rRange.mnMaximum; + } + } + } + + void grow(T nValue) + { + if(!isEmpty()) + { + bool bLessThanZero(nValue < 0); + + if(nValue > 0 || bLessThanZero) + { + mnMinimum -= nValue; + mnMaximum += nValue; + + if(bLessThanZero) + { + // test if range did collapse + if(mnMinimum > mnMaximum) + { + // if yes, collapse to center + mnMinimum = mnMaximum = (mnMinimum + mnMaximum) / 2; + } + } + } + } + } + + typename Traits::DifferenceType getRange() const + { + if(isEmpty()) + { + return Traits::neutral(); + } + else + { + return (mnMaximum - mnMinimum); + } + } + }; + + // some pre-fabricated traits + struct DoubleTraits + { + static double minVal() { return DBL_MIN; }; + static double maxVal() { return DBL_MAX; }; + static double neutral() { return 0.0; }; + + typedef double DifferenceType; + }; + + struct Int32Traits + { + static sal_Int32 minVal() { return SAL_MIN_INT32; }; + static sal_Int32 maxVal() { return SAL_MAX_INT32; }; + static sal_Int32 neutral() { return 0L; }; + + typedef sal_Int64 DifferenceType; + }; + +} // end of namespace basegfx + +#endif /* _BGFX_RANGE_BASICRANGE_HXX */ diff --git a/basegfx/inc/basegfx/range/rangeexpander.hxx b/basegfx/inc/basegfx/range/rangeexpander.hxx new file mode 100644 index 000000000000..f8d2ac9c6b68 --- /dev/null +++ b/basegfx/inc/basegfx/range/rangeexpander.hxx @@ -0,0 +1,83 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RANGE_RANGEEXPANDER_HXX +#define _BGFX_RANGE_RANGEEXPANDER_HXX + +#include <basegfx/range/b1drange.hxx> +#include <basegfx/range/b1irange.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/range/b2irange.hxx> +#include <basegfx/range/b3drange.hxx> +#include <basegfx/range/b3irange.hxx> + +namespace basegfx +{ + /** Generic functor for expanding a range with a number of other + ranges. + + Since *Range::expand() is overloaded, straight-forward + application of ::boost::bind and friends fails (because of + ambiguities). Thus, this functor template can be used, to + expand the given range with a number of other ranges, passed + in at the function operator. + + @tpl RangeType + Range type to operate with. Preferrably, one of B1*Range, + B2*Range, or B3*Range. + */ + template< typename RangeType > class RangeExpander + { + public: + typedef RangeType ValueType; + typedef void result_type; + + explicit RangeExpander( ValueType& rBounds ) : + mrBounds( rBounds ) + { + } + + void operator()( const ValueType& rBounds ) + { + mrBounds.expand( rBounds ); + } + + private: + ValueType& mrBounds; + }; + + typedef RangeExpander< B1DRange > B1DRangeExpander; + typedef RangeExpander< B1IRange > B1IRangeExpander; + typedef RangeExpander< B2DRange > B2DRangeExpander; + typedef RangeExpander< B2IRange > B2IRangeExpander; + typedef RangeExpander< B3DRange > B3DRangeExpander; + typedef RangeExpander< B3IRange > B3IRangeExpander; + +} // end of namespace basegfx + + +#endif /* _BGFX_RANGE_RANGEEXPANDER_HXX */ diff --git a/basegfx/inc/basegfx/raster/bpixelraster.hxx b/basegfx/inc/basegfx/raster/bpixelraster.hxx new file mode 100644 index 000000000000..c28adcdb2ef7 --- /dev/null +++ b/basegfx/inc/basegfx/raster/bpixelraster.hxx @@ -0,0 +1,116 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RASTER_BPIXELRASTER_HXX +#define _BGFX_RASTER_BPIXELRASTER_HXX + +#include <algorithm> +#include <sal/types.h> +#include <basegfx/pixel/bpixel.hxx> +#include <rtl/memory.h> + +////////////////////////////////////////////////////////////////////////////// +// predeclarations + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class BPixelRaster + { + private: + // do not allow copy constructor and assignment operator + BPixelRaster(const BPixelRaster&); + BPixelRaster& operator=(const BPixelRaster&); + + protected: + sal_uInt32 mnWidth; + sal_uInt32 mnHeight; + sal_uInt32 mnCount; + BPixel* mpContent; + + public: + // reset + void reset() + { + rtl_zeroMemory(mpContent, sizeof(BPixel) * mnCount); + } + + // constructor/destructor + BPixelRaster(sal_uInt32 nWidth, sal_uInt32 nHeight) + : mnWidth(nWidth), + mnHeight(nHeight), + mnCount(nWidth * nHeight), + mpContent(new BPixel[mnCount]) + { + reset(); + } + + ~BPixelRaster() + { + delete [] mpContent; + } + + // coordinate calcs between X/Y and span + sal_uInt32 getIndexFromXY(sal_uInt32 nX, sal_uInt32 nY) const { return (nX + (nY * mnWidth)); } + sal_uInt32 getXFromIndex(sal_uInt32 nIndex) const { return (nIndex % mnWidth); } + sal_uInt32 getYFromIndex(sal_uInt32 nIndex) const { return (nIndex / mnWidth); } + + // data access read + sal_uInt32 getWidth() const { return mnWidth; } + sal_uInt32 getHeight() const { return mnHeight; } + sal_uInt32 getCount() const { return mnCount; } + + // data access read only + const BPixel& getBPixel(sal_uInt32 nIndex) const + { +#ifdef DBG_UTIL + if(nIndex >= mnCount) + { + OSL_ENSURE(false, "getBPixel: Access out of range (!)"); + return BPixel::getEmptyBPixel(); + } +#endif + return mpContent[nIndex]; + } + + // data access read/write + BPixel& getBPixel(sal_uInt32 nIndex) + { +#ifdef DBG_UTIL + if(nIndex >= mnCount) + { + OSL_ENSURE(false, "getBPixel: Access out of range (!)"); + return mpContent[0L]; + } +#endif + return mpContent[nIndex]; + } + }; +} // end of namespace basegfx + +#endif /* _BGFX_RASTER_BPIXELRASTER_HXX */ diff --git a/basegfx/inc/basegfx/raster/bzpixelraster.hxx b/basegfx/inc/basegfx/raster/bzpixelraster.hxx new file mode 100644 index 000000000000..2c0905a78397 --- /dev/null +++ b/basegfx/inc/basegfx/raster/bzpixelraster.hxx @@ -0,0 +1,96 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RASTER_BZPIXELRASTER_HXX +#define _BGFX_RASTER_BZPIXELRASTER_HXX + +#include <basegfx/raster/bpixelraster.hxx> +#include <rtl/memory.h> + +////////////////////////////////////////////////////////////////////////////// +// predeclarations + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class BZPixelRaster : public BPixelRaster + { + protected: + // additionally, host a ZBuffer + sal_uInt16* mpZBuffer; + + public: + // reset + void resetZ() + { + reset(); + rtl_zeroMemory(mpZBuffer, sizeof(sal_uInt16) * mnCount); + } + + // constructor/destructor + BZPixelRaster(sal_uInt32 nWidth, sal_uInt32 nHeight) + : BPixelRaster(nWidth, nHeight), + mpZBuffer(new sal_uInt16[mnCount]) + { + rtl_zeroMemory(mpZBuffer, sizeof(sal_uInt16) * mnCount); + } + + ~BZPixelRaster() + { + delete [] mpZBuffer; + } + + // data access read only + const sal_uInt16& getZ(sal_uInt32 nIndex) const + { +#ifdef DBG_UTIL + if(nIndex >= mnCount) + { + OSL_ENSURE(false, "getZ: Access out of range (!)"); + return mpZBuffer[0L]; + } +#endif + return mpZBuffer[nIndex]; + } + + // data access read/write + sal_uInt16& getZ(sal_uInt32 nIndex) + { +#ifdef DBG_UTIL + if(nIndex >= mnCount) + { + OSL_ENSURE(false, "getZ: Access out of range (!)"); + return mpZBuffer[0L]; + } +#endif + return mpZBuffer[nIndex]; + } + }; +} // end of namespace basegfx + +#endif /* _BGFX_RASTER_BZPIXELRASTER_HXX */ diff --git a/basegfx/inc/basegfx/raster/rasterconvert3d.hxx b/basegfx/inc/basegfx/raster/rasterconvert3d.hxx new file mode 100644 index 000000000000..fb777482246d --- /dev/null +++ b/basegfx/inc/basegfx/raster/rasterconvert3d.hxx @@ -0,0 +1,345 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_RASTER_RASTERCONVERT3D_HXX +#define _BGFX_RASTER_RASTERCONVERT3D_HXX + +#include <sal/types.h> +#include <vector> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/vector/b3dvector.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> + +////////////////////////////////////////////////////////////////////////////// +// predeclarations + +namespace basegfx +{ + class B3DPolygon; + class B3DPolyPolygon; +} + +////////////////////////////////////////////////////////////////////////////// +// interpolators for double precision + +namespace basegfx +{ + class ip_single + { + private: + double mfVal; + double mfInc; + + public: + ip_single() + : mfVal(0.0), + mfInc(0.0) + {} + + ip_single(double fVal, double fInc) + : mfVal(fVal), + mfInc(fInc) + {} + + double getVal() const { return mfVal; } + double getInc() const { return mfInc; } + + void increment(double fStep) { mfVal += fStep * mfInc; } + }; +} // end of namespace basegfx + +namespace basegfx +{ + class ip_double + { + private: + ip_single maX; + ip_single maY; + + public: + ip_double() + : maX(), + maY() + {} + + ip_double(double fXVal, double fXInc, double fYVal, double fYInc) + : maX(fXVal, fXInc), + maY(fYVal, fYInc) + {} + + const ip_single& getX() const { return maX; } + const ip_single& getY() const { return maY; } + + void increment(double fStep) { maX.increment(fStep); maY.increment(fStep); } + }; +} // end of namespace basegfx + +namespace basegfx +{ + class ip_triple + { + private: + ip_single maX; + ip_single maY; + ip_single maZ; + + public: + ip_triple() + : maX(), + maY(), + maZ() + {} + + ip_triple(double fXVal, double fXInc, double fYVal, double fYInc, double fZVal, double fZInc) + : maX(fXVal, fXInc), + maY(fYVal, fYInc), + maZ(fZVal, fZInc) + {} + + const ip_single& getX() const { return maX; } + const ip_single& getY() const { return maY; } + const ip_single& getZ() const { return maZ; } + + void increment(double fStep) { maX.increment(fStep); maY.increment(fStep); maZ.increment(fStep); } + }; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// InterpolatorProvider3D to have a common source for allocating interpolators +// which may then be addressed using the index to the vectors + +namespace basegfx +{ + #define SCANLINE_EMPTY_INDEX (0xffffffff) + + class InterpolatorProvider3D + { + private: + ::std::vector< ip_triple > maColorInterpolators; + ::std::vector< ip_triple > maNormalInterpolators; + ::std::vector< ip_double > maTextureInterpolators; + ::std::vector< ip_triple > maInverseTextureInterpolators; + + protected: + sal_uInt32 addColorInterpolator(const BColor& rA, const BColor& rB, double fInvYDelta) + { + B3DVector aDelta(rB.getRed() - rA.getRed(), rB.getGreen() - rA.getGreen(), rB.getBlue() - rA.getBlue()); + aDelta *= fInvYDelta; + maColorInterpolators.push_back(ip_triple(rA.getRed(), aDelta.getX(), rA.getGreen(), aDelta.getY(), rA.getBlue(), aDelta.getZ())); + return (maColorInterpolators.size() - 1L); + } + + sal_uInt32 addNormalInterpolator(const B3DVector& rA, const B3DVector& rB, double fInvYDelta) + { + B3DVector aDelta(rB.getX() - rA.getX(), rB.getY() - rA.getY(), rB.getZ() - rA.getZ()); + aDelta *= fInvYDelta; + maNormalInterpolators.push_back(ip_triple(rA.getX(), aDelta.getX(), rA.getY(), aDelta.getY(), rA.getZ(), aDelta.getZ())); + return (maNormalInterpolators.size() - 1L); + } + + sal_uInt32 addTextureInterpolator(const B2DPoint& rA, const B2DPoint& rB, double fInvYDelta) + { + B2DVector aDelta(rB.getX() - rA.getX(), rB.getY() - rA.getY()); + aDelta *= fInvYDelta; + maTextureInterpolators.push_back(ip_double(rA.getX(), aDelta.getX(), rA.getY(), aDelta.getY())); + return (maTextureInterpolators.size() - 1L); + } + + sal_uInt32 addInverseTextureInterpolator(const B2DPoint& rA, const B2DPoint& rB, double fZEyeA, double fZEyeB, double fInvYDelta) + { + const double fInvZEyeA(fTools::equalZero(fZEyeA) ? fZEyeA : 1.0 / fZEyeA); + const double fInvZEyeB(fTools::equalZero(fZEyeB) ? fZEyeB : 1.0 / fZEyeB); + const B2DPoint aInvA(rA * fInvZEyeA); + const B2DPoint aInvB(rB * fInvZEyeB); + double fZDelta(fInvZEyeB - fInvZEyeA); + B2DVector aDelta(aInvB.getX() - aInvA.getX(), aInvB.getY() - aInvA.getY()); + + fZDelta *= fInvYDelta; + aDelta *= fInvYDelta; + + maInverseTextureInterpolators.push_back(ip_triple(aInvA.getX(), aDelta.getX(), aInvA.getY(), aDelta.getY(), fInvZEyeA, fZDelta)); + return (maInverseTextureInterpolators.size() - 1L); + } + + void reset() + { + maColorInterpolators.clear(); + maNormalInterpolators.clear(); + maTextureInterpolators.clear(); + maInverseTextureInterpolators.clear(); + } + + public: + InterpolatorProvider3D() {} + + ::std::vector< ip_triple >& getColorInterpolators() { return maColorInterpolators; } + ::std::vector< ip_triple >& getNormalInterpolators() { return maNormalInterpolators; } + ::std::vector< ip_double >& getTextureInterpolators() { return maTextureInterpolators; } + ::std::vector< ip_triple >& getInverseTextureInterpolators() { return maInverseTextureInterpolators; } + }; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// RasterConversionLineEntry3D for Raterconversion of 3D PolyPolygons + +namespace basegfx +{ + class RasterConversionLineEntry3D + { + private: + ip_single maX; + ip_single maZ; + sal_Int32 mnY; + sal_uInt32 mnCount; + + sal_uInt32 mnColorIndex; + sal_uInt32 mnNormalIndex; + sal_uInt32 mnTextureIndex; + sal_uInt32 mnInverseTextureIndex; + + public: + RasterConversionLineEntry3D(const double& rfX, const double& rfDeltaX, const double& rfZ, const double& rfDeltaZ, sal_Int32 nY, sal_uInt32 nCount) + : maX(rfX, rfDeltaX), + maZ(rfZ, rfDeltaZ), + mnY(nY), + mnCount(nCount), + mnColorIndex(SCANLINE_EMPTY_INDEX), + mnNormalIndex(SCANLINE_EMPTY_INDEX), + mnTextureIndex(SCANLINE_EMPTY_INDEX), + mnInverseTextureIndex(SCANLINE_EMPTY_INDEX) + {} + + void setColorIndex(sal_uInt32 nIndex) { mnColorIndex = nIndex; } + void setNormalIndex(sal_uInt32 nIndex) { mnNormalIndex = nIndex; } + void setTextureIndex(sal_uInt32 nIndex) { mnTextureIndex = nIndex; } + void setInverseTextureIndex(sal_uInt32 nIndex) { mnInverseTextureIndex = nIndex; } + + bool operator<(const RasterConversionLineEntry3D& rComp) const + { + if(mnY == rComp.mnY) + { + return maX.getVal() < rComp.maX.getVal(); + } + + return mnY < rComp.mnY; + } + + bool decrementRasterConversionLineEntry3D(sal_uInt32 nStep) + { + if(nStep >= mnCount) + { + return false; + } + else + { + mnCount -= nStep; + return true; + } + } + + void incrementRasterConversionLineEntry3D(sal_uInt32 nStep, InterpolatorProvider3D& rProvider) + { + const double fStep((double)nStep); + maX.increment(fStep); + maZ.increment(fStep); + mnY += nStep; + + if(SCANLINE_EMPTY_INDEX != mnColorIndex) + { + rProvider.getColorInterpolators()[mnColorIndex].increment(fStep); + } + + if(SCANLINE_EMPTY_INDEX != mnNormalIndex) + { + rProvider.getNormalInterpolators()[mnNormalIndex].increment(fStep); + } + + if(SCANLINE_EMPTY_INDEX != mnTextureIndex) + { + rProvider.getTextureInterpolators()[mnTextureIndex].increment(fStep); + } + + if(SCANLINE_EMPTY_INDEX != mnInverseTextureIndex) + { + rProvider.getInverseTextureInterpolators()[mnInverseTextureIndex].increment(fStep); + } + } + + // data read access + const ip_single& getX() const { return maX; } + sal_Int32 getY() const { return mnY; } + const ip_single& getZ() const { return maZ; } + sal_uInt32 getColorIndex() const { return mnColorIndex; } + sal_uInt32 getNormalIndex() const { return mnNormalIndex; } + sal_uInt32 getTextureIndex() const { return mnTextureIndex; } + sal_uInt32 getInverseTextureIndex() const { return mnInverseTextureIndex; } + }; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// the basic RaterConverter itself. Only one method needs to be overloaded. The +// class itself is strictly virtual + +namespace basegfx +{ + class RasterConverter3D : public InterpolatorProvider3D + { + private: + // the line entries for an area conversion run + ::std::vector< RasterConversionLineEntry3D > maLineEntries; + + struct lineComparator + { + bool operator()(const RasterConversionLineEntry3D* pA, const RasterConversionLineEntry3D* pB) + { + OSL_ENSURE(pA && pB, "lineComparator: empty pointer (!)"); + return pA->getX().getVal() < pB->getX().getVal(); + } + }; + + void addArea(const B3DPolygon& rFill, const B3DHomMatrix* pViewToEye); + void addArea(const B3DPolyPolygon& rFill, const B3DHomMatrix* pViewToEye); + void addEdge(const B3DPolygon& rFill, sal_uInt32 a, sal_uInt32 b, const B3DHomMatrix* pViewToEye); + + void rasterconvertB3DArea(sal_Int32 nStartLine, sal_Int32 nStopLine); + void rasterconvertB3DEdge(const B3DPolygon& rLine, sal_uInt32 nA, sal_uInt32 nB, sal_Int32 nStartLine, sal_Int32 nStopLine, sal_uInt16 nLineWidth); + + virtual void processLineSpan(const RasterConversionLineEntry3D& rA, const RasterConversionLineEntry3D& rB, sal_Int32 nLine, sal_uInt32 nSpanCount) = 0; + + public: + RasterConverter3D(); + virtual ~RasterConverter3D(); + + void rasterconvertB3DPolyPolygon(const B3DPolyPolygon& rFill, const B3DHomMatrix* pViewToEye, sal_Int32 nStartLine, sal_Int32 nStopLine); + void rasterconvertB3DPolygon(const B3DPolygon& rLine, sal_Int32 nStartLine, sal_Int32 nStopLine, sal_uInt16 nLineWidth); + }; +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +#endif /* _BGFX_RASTER_RASTERCONVERT3D_HXX */ diff --git a/basegfx/inc/basegfx/tools/b2dclipstate.hxx b/basegfx/inc/basegfx/tools/b2dclipstate.hxx new file mode 100644 index 000000000000..7d336d8cb48e --- /dev/null +++ b/basegfx/inc/basegfx/tools/b2dclipstate.hxx @@ -0,0 +1,119 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: rectcliptools.hxx,v $ + * $Revision: 1.3 $ + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TOOLS_CLIPSTATE_HXX +#define _BGFX_TOOLS_CLIPSTATE_HXX + +#include <sal/types.h> +#include <o3tl/cow_wrapper.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class B2DRange; + class B2DPolyRange; + class B2DPolygon; + class B2DPolyPolygon; + + namespace tools + { + class ImplB2DClipState; + + /** This class provides an optimized, symbolic clip state for graphical output + + Having a current 'clip' state is a common attribute of + almost all graphic output APIs, most of which internally + represent it via a list of rectangular bands. In contrast, + this implementation purely uses symbolic clips, but in a + quite efficient manner, deferring actual evaluation until + a clip representation is requested, and using faster code + paths for common special cases (like all-rectangle clips) + */ + class B2DClipState + { + public: + typedef o3tl::cow_wrapper< ImplB2DClipState > ImplType; + + private: + ImplType mpImpl; + + public: + /// Init clip, in 'cleared' state - everything is visible + B2DClipState(); + ~B2DClipState(); + B2DClipState( const B2DClipState& ); + explicit B2DClipState( const B2DRange& ); + explicit B2DClipState( const B2DPolygon& ); + explicit B2DClipState( const B2DPolyPolygon& ); + B2DClipState& operator=( const B2DClipState& ); + + /// unshare this poly-range with all internally shared instances + void makeUnique(); + + /// Set clip to 'null' - nothing is visible + void makeNull(); + /// returns true when clip is 'null' - nothing is visible + bool isNull() const; + + /// Set clip 'cleared' - everything is visible + void makeClear(); + /// returns true when clip is 'cleared' - everything is visible + bool isCleared() const; + + bool operator==(const B2DClipState&) const; + bool operator!=(const B2DClipState&) const; + + void unionRange(const B2DRange& ); + void unionPolygon(const B2DPolygon& ); + void unionPolyPolygon(const B2DPolyPolygon& ); + void unionClipState(const B2DClipState& ); + + void intersectRange(const B2DRange& ); + void intersectPolygon(const B2DPolygon& ); + void intersectPolyPolygon(const B2DPolyPolygon& ); + void intersectClipState(const B2DClipState& ); + + void subtractRange(const B2DRange& ); + void subtractPolygon(const B2DPolygon& ); + void subtractPolyPolygon(const B2DPolyPolygon& ); + void subtractClipState(const B2DClipState& ); + + void xorRange(const B2DRange& ); + void xorPolygon(const B2DPolygon& ); + void xorPolyPolygon(const B2DPolyPolygon& ); + void xorClipState(const B2DClipState& ); + + B2DPolyPolygon getClipPoly() const; + }; + } +} + +#endif // _BGFX_TOOLS_CLIPSTATE_HXX diff --git a/basegfx/inc/basegfx/tools/canvastools.hxx b/basegfx/inc/basegfx/tools/canvastools.hxx new file mode 100755 index 000000000000..1903518d991b --- /dev/null +++ b/basegfx/inc/basegfx/tools/canvastools.hxx @@ -0,0 +1,221 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TOOLS_CANVASTOOLS_HXX +#define _BGFX_TOOLS_CANVASTOOLS_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> + + +namespace com { namespace sun { namespace star { namespace geometry +{ + struct AffineMatrix2D; + struct AffineMatrix3D; + struct Matrix2D; + struct RealPoint2D; + struct RealSize2D; + struct RealRectangle2D; + struct RealRectangle3D; + struct IntegerPoint2D; + struct IntegerSize2D; + struct IntegerRectangle2D; + struct RealBezierSegment2D; +} } } } + +namespace com { namespace sun { namespace star { namespace rendering +{ + class XGraphicDevice; + class XPolyPolygon2D; +} } } } + +namespace com { namespace sun { namespace star { namespace awt +{ + struct Point; + struct Size; + struct Rectangle; +} } } } + +namespace basegfx +{ + class B2DHomMatrix; + class B3DHomMatrix; + class B2DVector; + class B2DPoint; + class B2DRange; + class B3DRange; + class B2IVector; + class B2IPoint; + class B2IRange; + class B2DPolygon; + class B2DPolyPolygon; + + namespace unotools + { + // Polygon conversions + // =================================================================== + + ::com::sun::star::uno::Reference< ::com::sun::star::rendering::XPolyPolygon2D > + xPolyPolygonFromB2DPolygon( const ::com::sun::star::uno::Reference< + ::com::sun::star::rendering::XGraphicDevice >& xGraphicDevice, + const ::basegfx::B2DPolygon& rPoly ); + + ::com::sun::star::uno::Reference< ::com::sun::star::rendering::XPolyPolygon2D > + xPolyPolygonFromB2DPolyPolygon( const ::com::sun::star::uno::Reference< + ::com::sun::star::rendering::XGraphicDevice >& xGraphicDevice, + const ::basegfx::B2DPolyPolygon& rPolyPoly ); + + + ::com::sun::star::uno::Sequence< + ::com::sun::star::uno::Sequence< + ::com::sun::star::geometry::RealBezierSegment2D > > + bezierSequenceSequenceFromB2DPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly ); + + ::com::sun::star::uno::Sequence< + ::com::sun::star::uno::Sequence< + ::com::sun::star::geometry::RealPoint2D > > + pointSequenceSequenceFromB2DPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly ); + + ::basegfx::B2DPolygon polygonFromPoint2DSequence( + const ::com::sun::star::uno::Sequence< + ::com::sun::star::geometry::RealPoint2D >& rPoints ); + + ::basegfx::B2DPolyPolygon polyPolygonFromPoint2DSequenceSequence( + const ::com::sun::star::uno::Sequence< + ::com::sun::star::uno::Sequence< ::com::sun::star::geometry::RealPoint2D > >& rPoints ); + + ::basegfx::B2DPolygon polygonFromBezier2DSequence( + const ::com::sun::star::uno::Sequence< + ::com::sun::star::geometry::RealBezierSegment2D >& rPoints ); + + ::basegfx::B2DPolyPolygon polyPolygonFromBezier2DSequenceSequence( + const ::com::sun::star::uno::Sequence< + ::com::sun::star::uno::Sequence< ::com::sun::star::geometry::RealBezierSegment2D > >& rPoints ); + + ::basegfx::B2DPolyPolygon b2DPolyPolygonFromXPolyPolygon2D( + const ::com::sun::star::uno::Reference< + ::com::sun::star::rendering::XPolyPolygon2D >& rPoly ); + + // Matrix conversions + // =================================================================== + + ::com::sun::star::geometry::AffineMatrix2D& + affineMatrixFromHomMatrix( ::com::sun::star::geometry::AffineMatrix2D& matrix, + const ::basegfx::B2DHomMatrix& transform); + + ::com::sun::star::geometry::AffineMatrix3D& affineMatrixFromHomMatrix3D( + ::com::sun::star::geometry::AffineMatrix3D& matrix, + const ::basegfx::B3DHomMatrix& transform); + + ::basegfx::B2DHomMatrix& + homMatrixFromAffineMatrix( ::basegfx::B2DHomMatrix& transform, + const ::com::sun::star::geometry::AffineMatrix2D& matrix ); + + ::basegfx::B2DHomMatrix homMatrixFromAffineMatrix( const ::com::sun::star::geometry::AffineMatrix2D& matrix ); + ::basegfx::B3DHomMatrix homMatrixFromAffineMatrix3D( const ::com::sun::star::geometry::AffineMatrix3D& matrix ); + + ::com::sun::star::geometry::Matrix2D& + matrixFromHomMatrix( ::com::sun::star::geometry::Matrix2D& matrix, + const ::basegfx::B2DHomMatrix& transform); + + ::basegfx::B2DHomMatrix& + homMatrixFromMatrix( ::basegfx::B2DHomMatrix& transform, + const ::com::sun::star::geometry::Matrix2D& matrix ); + + // Geometry conversions + // =================================================================== + + ::com::sun::star::geometry::RealSize2D size2DFromB2DSize( const ::basegfx::B2DVector& ); + ::com::sun::star::geometry::RealPoint2D point2DFromB2DPoint( const ::basegfx::B2DPoint& ); + ::com::sun::star::geometry::RealRectangle2D rectangle2DFromB2DRectangle( const ::basegfx::B2DRange& ); + ::com::sun::star::geometry::RealRectangle3D rectangle3DFromB3DRectangle( const ::basegfx::B3DRange& ); + + ::basegfx::B2DVector b2DSizeFromRealSize2D( const ::com::sun::star::geometry::RealSize2D& ); + ::basegfx::B2DPoint b2DPointFromRealPoint2D( const ::com::sun::star::geometry::RealPoint2D& ); + ::basegfx::B2DRange b2DRectangleFromRealRectangle2D( const ::com::sun::star::geometry::RealRectangle2D& ); + ::basegfx::B3DRange b3DRectangleFromRealRectangle3D( const ::com::sun::star::geometry::RealRectangle3D& ); + + ::com::sun::star::geometry::IntegerSize2D integerSize2DFromB2ISize( const ::basegfx::B2IVector& ); + ::com::sun::star::geometry::IntegerPoint2D integerPoint2DFromB2IPoint( const ::basegfx::B2IPoint& ); + ::com::sun::star::geometry::IntegerRectangle2D integerRectangle2DFromB2IRectangle( const ::basegfx::B2IRange& ); + + ::basegfx::B2IVector b2ISizeFromIntegerSize2D( const ::com::sun::star::geometry::IntegerSize2D& ); + ::basegfx::B2IPoint b2IPointFromIntegerPoint2D( const ::com::sun::star::geometry::IntegerPoint2D& ); + ::basegfx::B2IRange b2IRectangleFromIntegerRectangle2D( const ::com::sun::star::geometry::IntegerRectangle2D& ); + + ::com::sun::star::awt::Size awtSizeFromB2ISize( const ::basegfx::B2IVector& ); + ::com::sun::star::awt::Point awtPointFromB2IPoint( const ::basegfx::B2IPoint& ); + ::com::sun::star::awt::Rectangle awtRectangleFromB2IRectangle( const ::basegfx::B2IRange& ); + + ::basegfx::B2IVector b2ISizeFromAwtSize( const ::com::sun::star::awt::Size& ); + ::basegfx::B2IPoint b2IPointFromAwtPoint( const ::com::sun::star::awt::Point& ); + ::basegfx::B2IRange b2IRectangleFromAwtRectangle( const ::com::sun::star::awt::Rectangle& ); + + // Geometry comparisons + // =================================================================== + + bool RealSize2DAreEqual( const ::com::sun::star::geometry::RealSize2D& rA, const ::com::sun::star::geometry::RealSize2D& rB ); + bool RealPoint2DAreEqual( const ::com::sun::star::geometry::RealPoint2D& rA, const ::com::sun::star::geometry::RealPoint2D& rB ); + bool RealRectangle2DAreEqual( const ::com::sun::star::geometry::RealRectangle2D& rA, const ::com::sun::star::geometry::RealRectangle2D& rB ); + bool RealRectangle3DAreEqual( const ::com::sun::star::geometry::RealRectangle3D& rA, const ::com::sun::star::geometry::RealRectangle3D& rB ); + bool AffineMatrix2DAreEqual( const ::com::sun::star::geometry::AffineMatrix2D& rA, const ::com::sun::star::geometry::AffineMatrix2D& rB ); + + bool IntegerSize2DAreEqual( const ::com::sun::star::geometry::IntegerSize2D& rA, const ::com::sun::star::geometry::IntegerSize2D& rB ); + bool IntegerPoint2DAreEqual( const ::com::sun::star::geometry::IntegerPoint2D& rA, const ::com::sun::star::geometry::IntegerPoint2D& rB ); + bool IntegerRectangle2DAreEqual( const ::com::sun::star::geometry::IntegerRectangle2D& rA, const ::com::sun::star::geometry::IntegerRectangle2D& rB ); + + bool awtSizeAreEqual( const ::com::sun::star::awt::Size& rA, const ::com::sun::star::awt::Size& rB ); + bool awtPointAreEqual( const ::com::sun::star::awt::Point& rA, const ::com::sun::star::awt::Point& rB ); + bool awtRectangleAreEqual( const ::com::sun::star::awt::Rectangle& rA, const ::com::sun::star::awt::Rectangle& rB ); + + /** Return smalltest integer range, which completely contains + given floating point range. + + @param rRange + Input range. Values must be within the representable + bounds of sal_Int32 + + @return the closest integer range, which completely + contains rRange. + */ + ::basegfx::B2IRange b2ISurroundingRangeFromB2DRange( const ::basegfx::B2DRange& rRange ); + + /** Return smalltest B2DRange with integer values, which + completely contains given floating point range. + + @param rRange + Input range. + + @return the closest B2DRange with integer coordinates, + which completely contains rRange. + */ + ::basegfx::B2DRange b2DSurroundingIntegerRangeFromB2DRange( const ::basegfx::B2DRange& rRange ); + + } +} + +#endif /* _BGFX_TOOLS_CANVASTOOLS_HXX */ diff --git a/basegfx/inc/basegfx/tools/debugplotter.hxx b/basegfx/inc/basegfx/tools/debugplotter.hxx new file mode 100644 index 000000000000..ece104af3553 --- /dev/null +++ b/basegfx/inc/basegfx/tools/debugplotter.hxx @@ -0,0 +1,107 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TOOLS_DEBUGPLOTTER_HXX +#define _BGFX_TOOLS_DEBUGPLOTTER_HXX + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <rtl/string.hxx> +#include <boost/utility.hpp> // for noncopyable +#include <vector> +#include <utility> +#include <iostream> + + +namespace basegfx +{ + class B2DCubicBezier; + + /** Generates debug output for various basegfx data types. + + Use this class to produce debug (trace) output for various + basegfx geometry data types. By default, this class outputs + via OSL_TRACE (i.e. to stderr), and uses the gnuplot output + format. + + To be able to generate one coherent block of output, this + class delays actual writing to its destructor + */ + class DebugPlotter : private ::boost::noncopyable + { + public: + /** Create new debug output object + + @param pTitle + Title of the debug output, will appear in trace output + */ + explicit DebugPlotter( const sal_Char* pTitle ); + + /** Create new debug output object + + @param pTitle + Title of the debug output, will appear in trace output + + @param rOutputStream + Stream to write output to. Must stay valid over the + lifetime of this object! + */ + DebugPlotter( const sal_Char* pTitle, + ::std::ostream& rOutputStream ); + + ~DebugPlotter(); + + void plot( const B2DPoint& rPoint, + const sal_Char* pTitle ); + void plot( const B2DVector& rVec, + const sal_Char* pTitle ); + void plot( const B2DCubicBezier& rBezier, + const sal_Char* pTitle ); + void plot( const B2DRange& rRange, + const sal_Char* pTitle ); + void plot( const B2DPolygon& rPoly, + const sal_Char* pTitle ); + void plot( const B2DPolyPolygon& rPoly, + const sal_Char* pTitle ); + + private: + void print( const sal_Char* ); + + ::rtl::OString maTitle; + ::std::vector< ::std::pair< B2DPoint, ::rtl::OString > > maPoints; + ::std::vector< ::std::pair< B2DVector, ::rtl::OString > > maVectors; + ::std::vector< ::std::pair< B2DRange, ::rtl::OString > > maRanges; + ::std::vector< ::std::pair< B2DPolygon, ::rtl::OString > > maPolygons; + + ::std::ostream* mpOutputStream; + }; +} + +#endif /* _BGFX_TOOLS_DEBUGPLOTTER_HXX */ diff --git a/basegfx/inc/basegfx/tools/gradienttools.hxx b/basegfx/inc/basegfx/tools/gradienttools.hxx new file mode 100644 index 000000000000..2f436fa1d717 --- /dev/null +++ b/basegfx/inc/basegfx/tools/gradienttools.hxx @@ -0,0 +1,410 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TOOLS_GRADIENTTOOLS_HXX +#define _BGFX_TOOLS_GRADIENTTOOLS_HXX + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <vector> +#include <algorithm> + +namespace basegfx +{ + /** Gradient definition as used in ODF 1.2 + + This struct collects all data necessary for rendering ODF + 1.2-compatible gradients. Use the createXXXODFGradientInfo() + methods below for initializing from ODF attributes. + */ + struct ODFGradientInfo + { + /** transformation mapping from [0,1]^2 texture coordinate + space to [0,1]^2 shape coordinate space + */ + B2DHomMatrix maTextureTransform; + + /** transformation mapping from [0,1]^2 shape coordinate space + to [0,1]^2 texture coordinate space. This is the + transformation commonly used to create gradients from a + scanline rasterizer (put shape u/v coordinates into it, get + texture s/t coordinates out of it) + */ + B2DHomMatrix maBackTextureTransform; + + /** Aspect ratio of the gradient. Only used in drawinglayer + for generating nested gradient polygons currently. Already + catered for in the transformations above. + */ + double mfAspectRatio; + + /** Requested gradient steps to render. See the + implementations of the getXXXGradientAlpha() methods below, + the semantic differs slightly for the different gradient + types. + */ + sal_uInt32 mnSteps; + }; + + namespace tools + { + /** Create matrix for ODF's linear gradient definition + + Note that odf linear gradients are varying in y direction. + + @param o_rGradientInfo + Receives the calculated texture transformation matrix (for + use with standard [0,1]x[0,1] texture coordinates) + + @param rTargetArea + Output area, needed for aspect ratio calculations and + texture transformation + + @param nSteps + Number of gradient steps (from ODF) + + @param fBorder + Width of gradient border (from ODF) + + @param fAngle + Gradient angle (from ODF) + */ + ODFGradientInfo& createLinearODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + sal_uInt32 nSteps, + double fBorder, + double fAngle); + + /** Calculate linear gradient blend value + + This method generates you the lerp alpha value for + blending linearly between gradient start and end color, + according to the formula (startCol*(1.0-alpha) + endCol*alpha) + + @param rUV + Current uv coordinate. Values outside [0,1] will be + clamped. Assumes gradient color varies along the y axis. + + @param rGradInfo + Gradient info, for transformation and number of steps + */ + inline double getLinearGradientAlpha(const B2DPoint& rUV, + const ODFGradientInfo& rGradInfo ) + { + const B2DPoint aCoor(rGradInfo.maBackTextureTransform * rUV); + const double t(clamp(aCoor.getY(), 0.0, 1.0)); + const sal_uInt32 nSteps(rGradInfo.mnSteps); + + if(nSteps > 2L && nSteps < 128L) + return floor(t * nSteps) / double(nSteps + 1L); + + return t; + } + + /** Create matrix for ODF's axial gradient definition + + Note that odf axial gradients are varying in y + direction. Note further that you can map the axial + gradient to a linear gradient (in case you want or need to + avoid an extra gradient renderer), by using + createLinearODFGradientInfo() instead, shifting the + resulting texture transformation by 0.5 to the top and + appending the same stop colors again, but mirrored. + + @param o_rGradientInfo + Receives the calculated texture transformation matrix (for + use with standard [0,1]x[0,1] texture coordinates) + + @param rTargetArea + Output area, needed for aspect ratio calculations and + texture transformation + + @param nSteps + Number of gradient steps (from ODF) + + @param fBorder + Width of gradient border (from ODF) + + @param fAngle + Gradient angle (from ODF) + */ + ODFGradientInfo& createAxialODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + sal_uInt32 nSteps, + double fBorder, + double fAngle); + + /** Calculate axial gradient blend value + + This method generates you the lerp alpha value for + blending linearly between gradient start and end color, + according to the formula (startCol*(1.0-alpha) + endCol*alpha) + + @param rUV + Current uv coordinate. Values outside [0,1] will be + clamped. Assumes gradient color varies along the y axis. + + @param rGradInfo + Gradient info, for transformation and number of steps + */ + inline double getAxialGradientAlpha(const B2DPoint& rUV, + const ODFGradientInfo& rGradInfo ) + { + const B2DPoint aCoor(rGradInfo.maBackTextureTransform * rUV); + const double t(clamp(fabs(aCoor.getY()), 0.0, 1.0)); + const sal_uInt32 nSteps(rGradInfo.mnSteps); + const double fInternalSteps((nSteps * 2L) - 1L); + + if(nSteps > 2L && nSteps < 128L) + return floor(((t * fInternalSteps) + 1.0) / 2.0) / double(nSteps - 1L); + + return t; + } + + /** Create matrix for ODF's radial gradient definition + + @param o_rGradientInfo + Receives the calculated texture transformation matrix (for + use with standard [0,1]x[0,1] texture coordinates) + + @param rTargetArea + Output area, needed for aspect ratio calculations and + texture transformation + + @param rOffset + Gradient offset value (from ODF) + + @param nSteps + Number of gradient steps (from ODF) + + @param fBorder + Width of gradient border (from ODF) + + @param fAngle + Gradient angle (from ODF) + */ + ODFGradientInfo& createRadialODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + const B2DVector& rOffset, + sal_uInt32 nSteps, + double fBorder); + + /** Calculate radial gradient blend value + + This method generates you the lerp alpha value for + blending linearly between gradient start and end color, + according to the formula (startCol*(1.0-alpha) + endCol*alpha) + + @param rUV + Current uv coordinate. Values outside [0,1] will be + clamped. + + @param rGradInfo + Gradient info, for transformation and number of steps + */ + inline double getRadialGradientAlpha(const B2DPoint& rUV, + const ODFGradientInfo& rGradInfo ) + { + const B2DPoint aCoor(rGradInfo.maBackTextureTransform * rUV); + const double fDist( + clamp(aCoor.getX() * aCoor.getX() + aCoor.getY() * aCoor.getY(), + 0.0, + 1.0)); + + const double t(1.0 - sqrt(fDist)); + const sal_uInt32 nSteps(rGradInfo.mnSteps); + + if(nSteps > 2L && nSteps < 128L) + return floor(t * nSteps) / double(nSteps - 1L); + + return t; + } + + /** Create matrix for ODF's elliptical gradient definition + + @param o_rGradientInfo + Receives the calculated texture transformation matrix (for + use with standard [0,1]x[0,1] texture coordinates) + + @param rTargetArea + Output area, needed for aspect ratio calculations and + texture transformation + + @param rOffset + Gradient offset value (from ODF) + + @param nSteps + Number of gradient steps (from ODF) + + @param fBorder + Width of gradient border (from ODF) + + @param fAngle + Gradient angle (from ODF) + */ + ODFGradientInfo& createEllipticalODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + const B2DVector& rOffset, + sal_uInt32 nSteps, + double fBorder, + double fAngle); + + /** Calculate elliptical gradient blend value + + This method generates you the lerp alpha value for + blending linearly between gradient start and end color, + according to the formula (startCol*(1.0-alpha) + endCol*alpha) + + @param rUV + Current uv coordinate. Values outside [0,1] will be + clamped. + + @param rGradInfo + Gradient info, for transformation and number of steps + */ + inline double getEllipticalGradientAlpha(const B2DPoint& rUV, + const ODFGradientInfo& rGradInfo ) + { + return getRadialGradientAlpha(rUV,rGradInfo); // only matrix setup differs + } + + /** Create matrix for ODF's square gradient definition + + @param o_rGradientInfo + Receives the calculated texture transformation matrix (for + use with standard [0,1]x[0,1] texture coordinates) + + @param rTargetArea + Output area, needed for aspect ratio calculations and + texture transformation + + @param rOffset + Gradient offset value (from ODF) + + @param nSteps + Number of gradient steps (from ODF) + + @param fBorder + Width of gradient border (from ODF) + + @param fAngle + Gradient angle (from ODF) + */ + ODFGradientInfo& createSquareODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + const B2DVector& rOffset, + sal_uInt32 nSteps, + double fBorder, + double fAngle); + + /** Calculate square gradient blend value + + This method generates you the lerp alpha value for + blending linearly between gradient start and end color, + according to the formula (startCol*(1.0-alpha) + endCol*alpha) + + @param rUV + Current uv coordinate. Values outside [0,1] will be + clamped. + + @param rGradInfo + Gradient info, for transformation and number of steps + */ + inline double getSquareGradientAlpha(const B2DPoint& rUV, + const ODFGradientInfo& rGradInfo ) + { + const B2DPoint aCoor(rGradInfo.maBackTextureTransform * rUV); + const double fAbsX(fabs(aCoor.getX())); + const double fAbsY(fabs(aCoor.getY())); + + if(fTools::moreOrEqual(fAbsX, 1.0) || fTools::moreOrEqual(fAbsY, 1.0)) + return 0.0; + + const double t(1.0 - (fAbsX > fAbsY ? fAbsX : fAbsY)); + const sal_uInt32 nSteps(rGradInfo.mnSteps); + + if(nSteps > 2L && nSteps < 128L) + return floor(t * nSteps) / double(nSteps - 1L); + + return t; + } + + /** Create matrix for ODF's rectangular gradient definition + + @param o_rGradientInfo + Receives the calculated texture transformation matrix (for + use with standard [0,1]x[0,1] texture coordinates) + + @param rTargetArea + Output area, needed for aspect ratio calculations and + texture transformation + + @param rOffset + Gradient offset value (from ODF) + + @param nSteps + Number of gradient steps (from ODF) + + @param fBorder + Width of gradient border (from ODF) + + @param fAngle + Gradient angle (from ODF) + */ + ODFGradientInfo& createRectangularODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + const B2DVector& rOffset, + sal_uInt32 nSteps, + double fBorder, + double fAngle); + + /** Calculate rectangular gradient blend value + + This method generates you the lerp alpha value for + blending linearly between gradient start and end color, + according to the formula (startCol*(1.0-alpha) + endCol*alpha) + + @param rUV + Current uv coordinate. Values outside [0,1] will be + clamped. + + @param rGradInfo + Gradient info, for transformation and number of steps + */ + inline double getRectangularGradientAlpha(const B2DPoint& rUV, + const ODFGradientInfo& rGradInfo ) + { + return getSquareGradientAlpha(rUV, rGradInfo); // only matrix setup differs + } + } +} + +#endif diff --git a/basegfx/inc/basegfx/tools/keystoplerp.hxx b/basegfx/inc/basegfx/tools/keystoplerp.hxx new file mode 100644 index 000000000000..a54b3485b1a1 --- /dev/null +++ b/basegfx/inc/basegfx/tools/keystoplerp.hxx @@ -0,0 +1,100 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: canvastools.hxx,v $ + * $Revision: 1.10 $ + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TOOLS_KEYSTOPLERP_HXX +#define _BGFX_TOOLS_KEYSTOPLERP_HXX + +#include <basegfx/numeric/ftools.hxx> +#include <vector> + +namespace com{ namespace sun{ namespace star{ namespace uno { + template<typename T> class Sequence; +}}}} + +namespace basegfx +{ + namespace tools + { + /** Lerp in a vector of key stops + + This class holds a key stop vector and provides the + functionality to lerp inside it. Useful e.g. for + multi-stop gradients, or the SMIL key time activity. + + For those, given a global [0,1] lerp alpha, one need to + find the suitable bucket index from key stop vector, and + then calculate the relative alpha between the two buckets + found. + */ + class KeyStopLerp + { + public: + typedef std::pair<std::ptrdiff_t,double> ResultType; + + /** Create lerper with given vector of stops + + @param rKeyStops + + Vector of stops, must contain at least two elements + (though preferrably more, otherwise you probably don't + need key stop lerping in the first place). All + elements must be of monotonically increasing value. + */ + explicit KeyStopLerp( const std::vector<double>& rKeyStops ); + + /** Create lerper with given sequence of stops + + @param rKeyStops + + Sequence of stops, must contain at least two elements + (though preferrably more, otherwise you probably don't + need key stop lerping in the first place). All + elements must be of monotonically increasing value. + */ + explicit KeyStopLerp( const ::com::sun::star::uno::Sequence<double>& rKeyStops ); + + /** Find two nearest bucket index & interpolate + + @param fAlpha + Find bucket index i, with keyStops[i] < fAlpha <= + keyStops[i+1]. Return new alpha value in [0,1), + proportional to fAlpha's position between keyStops[i] + and keyStops[i+1] + */ + ResultType lerp(double fAlpha) const; + + private: + std::vector<double> maKeyStops; + mutable std::ptrdiff_t mnLastIndex; + }; + } +} + +#endif diff --git a/basegfx/inc/basegfx/tools/lerp.hxx b/basegfx/inc/basegfx/tools/lerp.hxx new file mode 100644 index 000000000000..590ef34c2009 --- /dev/null +++ b/basegfx/inc/basegfx/tools/lerp.hxx @@ -0,0 +1,60 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: lerp.hxx,v $ + * $Revision: 1.6 $ + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TOOLS_LERP_HXX +#define _BGFX_TOOLS_LERP_HXX + +#include <sal/types.h> + +namespace basegfx +{ + namespace tools + { + /** Generic linear interpolator + + @tpl ValueType + Must have operator+ and operator* defined, and should + have value semantics. + + @param t + As usual, t must be in the [0,1] range + */ + template< typename ValueType > ValueType lerp( const ValueType& rFrom, + const ValueType& rTo, + double t ) + { + // This is only to suppress a double->int warning. All other + // types should be okay here. + return static_cast<ValueType>( (1.0-t)*rFrom + t*rTo ); + } + } +} + +#endif /* _BGFX_TOOLS_LERP_HXX */ diff --git a/basegfx/inc/basegfx/tools/rectcliptools.hxx b/basegfx/inc/basegfx/tools/rectcliptools.hxx new file mode 100644 index 000000000000..ae7c057666d9 --- /dev/null +++ b/basegfx/inc/basegfx/tools/rectcliptools.hxx @@ -0,0 +1,88 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TOOLS_RECTCLIPTOOLS_HXX +#define _BGFX_TOOLS_RECTCLIPTOOLS_HXX + +#include <sal/types.h> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + namespace RectClipFlags + { + static const sal_uInt32 LEFT = (sal_Int32)0x01; + static const sal_uInt32 RIGHT = (sal_Int32)0x02; + static const sal_uInt32 TOP = (sal_Int32)0x04; + static const sal_uInt32 BOTTOM = (sal_Int32)0x08; + } + + /** Calc clip mask for Cohen-Sutherland rectangle clip + + This function returns a clip mask used for the + Cohen-Sutherland rectangle clip method, where one or more + of the lower four bits are set, if the given point is + outside one or more of the four half planes defining the + rectangle (see RectClipFlags for possible values) + */ + template< class Point, class Rect > inline + sal_uInt32 getCohenSutherlandClipFlags( const Point& rP, + const Rect& rR ) + { + // maxY | minY | maxX | minX + sal_uInt32 clip = (rP.getX() < rR.getMinX()) << 0; + clip |= (rP.getX() > rR.getMaxX()) << 1; + clip |= (rP.getY() < rR.getMinY()) << 2; + clip |= (rP.getY() > rR.getMaxY()) << 3; + return clip; + } + + /** Determine number of clip planes hit by given clip mask + + This method returns the number of one bits in the four + least significant bits of the argument, which amounts to + the number of clip planes hit within the + getCohenSutherlandClipFlags() method. + */ + inline sal_uInt32 getNumberOfClipPlanes( sal_uInt32 nFlags ) + { + // classic bit count algo, see e.g. Reingold, Nievergelt, + // Deo: Combinatorial Algorithms, Theory and Practice, + // Prentice-Hall 1977 + nFlags = (nFlags & 0x05) + ((nFlags >> 1) & 0x05); + nFlags = (nFlags & 0x03) + (nFlags >> 2); // no need for & + // 0x03, can't + // overflow + return nFlags; + } + } +} + +#endif // _BGFX_TOOLS_RECTCLIPTOOLS_HXX diff --git a/basegfx/inc/basegfx/tools/tools.hxx b/basegfx/inc/basegfx/tools/tools.hxx new file mode 100644 index 000000000000..896fd22c162d --- /dev/null +++ b/basegfx/inc/basegfx/tools/tools.hxx @@ -0,0 +1,131 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TOOLS_TOOLS_HXX +#define _BGFX_TOOLS_TOOLS_HXX + +#include <sal/types.h> + +namespace basegfx +{ + class B2DPoint; + class B2DRange; + + namespace tools + { + /** Liang-Barsky 2D line clipping algorithm + + This function clips a line given by two points against the + given rectangle. The resulting line is returned in the + given points. + + @param io_rStart + Start point of the line. On return, contains the clipped + start point. + + @param io_rEnd + End point of the line. On return, contains the clipped + end point. + + @param rClipRect + The rectangle to clip against + + @return true, when at least part of the line is visible + after the clip, false otherwise + */ + bool liangBarskyClip2D( ::basegfx::B2DPoint& io_rStart, + ::basegfx::B2DPoint& io_rEnd, + const ::basegfx::B2DRange& rClipRect ); + + /** Expand given parallelogram, such that it extends beyond + bound rect in a given direction. + + This method is useful when e.g. generating one-dimensional + gradients, such as linear or axial gradients: those + gradients vary only in one direction, the other has + constant color. Most of the time, those gradients extends + infinitely in the direction with the constant color, but + practically, one always has a limiting bound rect into + which the gradient is painted. The method at hand now + extends a given parallelogram (e.g. the transformed + bounding box of a gradient) virtually into infinity to the + top and to the bottom (i.e. normal to the line io_rLeftTop + io_rRightTop), such that the given rectangle is guaranteed + to be covered in that direction. + + @attention There might be some peculiarities with this + method, that might limit its usage to the described + gradients. One of them is the fact that when determining + how far the parallelogram has to be extended to the top or + the bottom, the upper and lower border are assumed to be + infinite lines. + + @param io_rLeftTop + Left, top edge of the parallelogramm. Note that this need + not be the left, top edge geometrically, it's just used + when determining the extension direction. Thus, it's + perfectly legal to affine-transform a rectangle, and given + the transformed point here. On method return, this + parameter will contain the adapted output. + + @param io_rLeftBottom + Left, bottom edge of the parallelogramm. Note that this need + not be the left, bottom edge geometrically, it's just used + when determining the extension direction. Thus, it's + perfectly legal to affine-transform a rectangle, and given + the transformed point here. On method return, this + parameter will contain the adapted output. + + @param io_rRightTop + Right, top edge of the parallelogramm. Note that this need + not be the right, top edge geometrically, it's just used + when determining the extension direction. Thus, it's + perfectly legal to affine-transform a rectangle, and given + the transformed point here. On method return, this + parameter will contain the adapted output. + + @param io_rRightBottom + Right, bottom edge of the parallelogramm. Note that this need + not be the right, bottom edge geometrically, it's just used + when determining the extension direction. Thus, it's + perfectly legal to affine-transform a rectangle, and given + the transformed point here. On method return, this + parameter will contain the adapted output. + + @param rFitTarget + The rectangle to fit the parallelogram into. + */ + void infiniteLineFromParallelogram( ::basegfx::B2DPoint& io_rLeftTop, + ::basegfx::B2DPoint& io_rLeftBottom, + ::basegfx::B2DPoint& io_rRightTop, + ::basegfx::B2DPoint& io_rRightBottom, + const ::basegfx::B2DRange& rFitTarget ); + + } +} + +#endif /* _BGFX_TOOLS_TOOLS_HXX */ diff --git a/basegfx/inc/basegfx/tools/unopolypolygon.hxx b/basegfx/inc/basegfx/tools/unopolypolygon.hxx new file mode 100755 index 000000000000..8a4044e1ca4b --- /dev/null +++ b/basegfx/inc/basegfx/tools/unopolypolygon.hxx @@ -0,0 +1,112 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef INCLUDED_BASEGFX_UNOPOLYPOLYGON_HXX +#define INCLUDED_BASEGFX_UNOPOLYPOLYGON_HXX + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase3.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/FillRule.hpp> +#include <com/sun/star/rendering/XLinePolyPolygon2D.hpp> +#include <com/sun/star/rendering/XBezierPolyPolygon2D.hpp> +#include <basegfx/polygon/b2dpolypolygon.hxx> + + +namespace basegfx +{ +namespace unotools +{ + typedef ::cppu::WeakComponentImplHelper3< + ::com::sun::star::rendering::XLinePolyPolygon2D, + ::com::sun::star::rendering::XBezierPolyPolygon2D, + ::com::sun::star::lang::XServiceInfo > UnoPolyPolygonBase; + + class UnoPolyPolygon : private cppu::BaseMutex, + public UnoPolyPolygonBase + { + public: + explicit UnoPolyPolygon( const B2DPolyPolygon& ); + + // XPolyPolygon2D + virtual void SAL_CALL addPolyPolygon( const ::com::sun::star::geometry::RealPoint2D& position, const ::com::sun::star::uno::Reference< ::com::sun::star::rendering::XPolyPolygon2D >& polyPolygon ) throw (::com::sun::star::lang::IllegalArgumentException, ::com::sun::star::uno::RuntimeException); + virtual ::sal_Int32 SAL_CALL getNumberOfPolygons( ) throw (::com::sun::star::uno::RuntimeException); + virtual ::sal_Int32 SAL_CALL getNumberOfPolygonPoints( ::sal_Int32 polygon ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + virtual ::com::sun::star::rendering::FillRule SAL_CALL getFillRule( ) throw (::com::sun::star::uno::RuntimeException); + virtual void SAL_CALL setFillRule( ::com::sun::star::rendering::FillRule fillRule ) throw (::com::sun::star::uno::RuntimeException); + virtual ::sal_Bool SAL_CALL isClosed( ::sal_Int32 index ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + virtual void SAL_CALL setClosed( ::sal_Int32 index, ::sal_Bool closedState ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + + // XLinePolyPolygon2D + virtual ::com::sun::star::uno::Sequence< ::com::sun::star::uno::Sequence< ::com::sun::star::geometry::RealPoint2D > > SAL_CALL getPoints( ::sal_Int32 nPolygonIndex, ::sal_Int32 nNumberOfPolygons, ::sal_Int32 nPointIndex, ::sal_Int32 nNumberOfPoints ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + virtual void SAL_CALL setPoints( const ::com::sun::star::uno::Sequence< ::com::sun::star::uno::Sequence< ::com::sun::star::geometry::RealPoint2D > >& points, ::sal_Int32 nPolygonIndex ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + virtual ::com::sun::star::geometry::RealPoint2D SAL_CALL getPoint( ::sal_Int32 nPolygonIndex, ::sal_Int32 nPointIndex ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + virtual void SAL_CALL setPoint( const ::com::sun::star::geometry::RealPoint2D& point, ::sal_Int32 nPolygonIndex, ::sal_Int32 nPointIndex ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + + // XBezierPolyPolygon2D + virtual ::com::sun::star::uno::Sequence< ::com::sun::star::uno::Sequence< ::com::sun::star::geometry::RealBezierSegment2D > > SAL_CALL getBezierSegments( ::sal_Int32 nPolygonIndex, ::sal_Int32 nNumberOfPolygons, ::sal_Int32 nPointIndex, ::sal_Int32 nNumberOfPoints ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + virtual void SAL_CALL setBezierSegments( const ::com::sun::star::uno::Sequence< ::com::sun::star::uno::Sequence< ::com::sun::star::geometry::RealBezierSegment2D > >& points, ::sal_Int32 nPolygonIndex ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + virtual ::com::sun::star::geometry::RealBezierSegment2D SAL_CALL getBezierSegment( ::sal_Int32 nPolygonIndex, ::sal_Int32 nPointIndex ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + virtual void SAL_CALL setBezierSegment( const ::com::sun::star::geometry::RealBezierSegment2D& point, ::sal_Int32 nPolygonIndex, ::sal_Int32 nPointIndex ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + + // XServiceInfo + virtual ::rtl::OUString SAL_CALL getImplementationName() throw( ::com::sun::star::uno::RuntimeException ); + virtual sal_Bool SAL_CALL supportsService( const ::rtl::OUString& ServiceName ) throw( ::com::sun::star::uno::RuntimeException ); + virtual ::com::sun::star::uno::Sequence< ::rtl::OUString > SAL_CALL getSupportedServiceNames() throw( ::com::sun::star::uno::RuntimeException ); + + B2DPolyPolygon getPolyPolygon() const; + + protected: + /// Check whether index is a valid polygon index + void checkIndex( sal_Int32 nIndex ) const // throw (::com::sun::star::lang::IndexOutOfBoundsException); + { + if( nIndex < 0 || nIndex >= static_cast<sal_Int32>(maPolyPoly.count()) ) + throw ::com::sun::star::lang::IndexOutOfBoundsException(); + } + + B2DPolyPolygon getSubsetPolyPolygon( sal_Int32 nPolygonIndex, + sal_Int32 nNumberOfPolygons, + sal_Int32 nPointIndex, + sal_Int32 nNumberOfPoints ) const; + + /// Get cow copy of internal polygon. not thread-safe outside this object. + B2DPolyPolygon getPolyPolygonUnsafe() const; + + /// Called whenever internal polypolygon gets modified + virtual void modifying() const {} + + private: + UnoPolyPolygon( const UnoPolyPolygon& ); + UnoPolyPolygon& operator=( const UnoPolyPolygon& ); + + B2DPolyPolygon maPolyPoly; + ::com::sun::star::rendering::FillRule meFillRule; + }; +} +} + +#endif /* INCLUDED_BASEGFX_UNOPOLYPOLYGON_HXX */ diff --git a/basegfx/inc/basegfx/tuple/b2dtuple.hxx b/basegfx/inc/basegfx/tuple/b2dtuple.hxx new file mode 100644 index 000000000000..71dd227ac736 --- /dev/null +++ b/basegfx/inc/basegfx/tuple/b2dtuple.hxx @@ -0,0 +1,359 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TUPLE_B2DTUPLE_HXX +#define _BGFX_TUPLE_B2DTUPLE_HXX + +#include <sal/types.h> +#include <basegfx/numeric/ftools.hxx> + +namespace basegfx +{ + // predeclarations + class B2ITuple; + + /** Base class for all Points/Vectors with two double values + + This class provides all methods common to Point + avd Vector classes which are derived from here. + + @derive Use this class to implement Points or Vectors + which are based on two double values + */ + class B2DTuple + { + protected: + double mfX; + double mfY; + + public: + /** Create a 2D Tuple + + The tuple is initialized to (0.0, 0.0) + */ + B2DTuple() + : mfX(0.0), + mfY(0.0) + {} + + /** Create a 2D Tuple + + @param fX + This parameter is used to initialize the X-coordinate + of the 2D Tuple. + + @param fY + This parameter is used to initialize the Y-coordinate + of the 2D Tuple. + */ + B2DTuple(double fX, double fY) + : mfX( fX ), + mfY( fY ) + {} + + /** Create a copy of a 2D Tuple + + @param rTup + The 2D Tuple which will be copied. + */ + B2DTuple(const B2DTuple& rTup) + : mfX( rTup.mfX ), + mfY( rTup.mfY ) + {} + + /** Create a copy of a 2D integer Tuple + + @param rTup + The 2D Tuple which will be copied. + */ + explicit B2DTuple(const B2ITuple& rTup); + + ~B2DTuple() + {} + + /// Get X-Coordinate of 2D Tuple + double getX() const + { + return mfX; + } + + /// Get Y-Coordinate of 2D Tuple + double getY() const + { + return mfY; + } + + /// Set X-Coordinate of 2D Tuple + void setX(double fX) + { + mfX = fX; + } + + /// Set Y-Coordinate of 2D Tuple + void setY(double fY) + { + mfY = fY; + } + + /// Array-access to 2D Tuple + const double& operator[] (int nPos) const + { + // Here, normally one if(...) should be used. In the assumption that + // both double members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mfX; return mfY; + return *((&mfX) + nPos); + } + + /// Array-access to 2D Tuple + double& operator[] (int nPos) + { + // Here, normally one if(...) should be used. In the assumption that + // both double members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mfX; return mfY; + return *((&mfX) + nPos); + } + + // comparators with tolerance + ////////////////////////////////////////////////////////////////////// + + bool equalZero() const + { + return (this == &getEmptyTuple() || + (fTools::equalZero(mfX) && fTools::equalZero(mfY))); + } + + bool equalZero(const double& rfSmallValue) const + { + return (this == &getEmptyTuple() || + (fTools::equalZero(mfX, rfSmallValue) && fTools::equalZero(mfY, rfSmallValue))); + } + + bool equal(const B2DTuple& rTup) const + { + return ( + this == &rTup || + (fTools::equal(mfX, rTup.mfX) && + fTools::equal(mfY, rTup.mfY))); + } + + bool equal(const B2DTuple& rTup, const double& rfSmallValue) const + { + return ( + this == &rTup || + (fTools::equal(mfX, rTup.mfX, rfSmallValue) && + fTools::equal(mfY, rTup.mfY, rfSmallValue))); + } + + // operators + ////////////////////////////////////////////////////////////////////// + + B2DTuple& operator+=( const B2DTuple& rTup ) + { + mfX += rTup.mfX; + mfY += rTup.mfY; + return *this; + } + + B2DTuple& operator-=( const B2DTuple& rTup ) + { + mfX -= rTup.mfX; + mfY -= rTup.mfY; + return *this; + } + + B2DTuple& operator/=( const B2DTuple& rTup ) + { + mfX /= rTup.mfX; + mfY /= rTup.mfY; + return *this; + } + + B2DTuple& operator*=( const B2DTuple& rTup ) + { + mfX *= rTup.mfX; + mfY *= rTup.mfY; + return *this; + } + + B2DTuple& operator*=(double t) + { + mfX *= t; + mfY *= t; + return *this; + } + + B2DTuple& operator/=(double t) + { + const double fVal(1.0 / t); + mfX *= fVal; + mfY *= fVal; + return *this; + } + + B2DTuple operator-(void) const + { + return B2DTuple(-mfX, -mfY); + } + + bool operator==( const B2DTuple& rTup ) const + { + return equal(rTup); + } + + bool operator!=( const B2DTuple& rTup ) const + { + return !equal(rTup); + } + + B2DTuple& operator=( const B2DTuple& rTup ) + { + mfX = rTup.mfX; + mfY = rTup.mfY; + return *this; + } + + void correctValues(const double fCompareValue = 0.0); + + static const B2DTuple& getEmptyTuple(); + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + inline B2DTuple minimum(const B2DTuple& rTupA, const B2DTuple& rTupB) + { + B2DTuple aMin( + (rTupB.getX() < rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() < rTupA.getY()) ? rTupB.getY() : rTupA.getY()); + return aMin; + } + + inline B2DTuple maximum(const B2DTuple& rTupA, const B2DTuple& rTupB) + { + B2DTuple aMax( + (rTupB.getX() > rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() > rTupA.getY()) ? rTupB.getY() : rTupA.getY()); + return aMax; + } + + inline B2DTuple absolute(const B2DTuple& rTup) + { + B2DTuple aAbs( + (0.0 > rTup.getX()) ? -rTup.getX() : rTup.getX(), + (0.0 > rTup.getY()) ? -rTup.getY() : rTup.getY()); + return aAbs; + } + + inline B2DTuple interpolate(const B2DTuple& rOld1, const B2DTuple& rOld2, double t) + { + B2DTuple aInt( + ((rOld2.getX() - rOld1.getX()) * t) + rOld1.getX(), + ((rOld2.getY() - rOld1.getY()) * t) + rOld1.getY()); + return aInt; + } + + inline B2DTuple average(const B2DTuple& rOld1, const B2DTuple& rOld2) + { + B2DTuple aAvg( + (rOld1.getX() + rOld2.getX()) * 0.5, + (rOld1.getY() + rOld2.getY()) * 0.5); + return aAvg; + } + + inline B2DTuple average(const B2DTuple& rOld1, const B2DTuple& rOld2, const B2DTuple& rOld3) + { + B2DTuple aAvg( + (rOld1.getX() + rOld2.getX() + rOld3.getX()) * (1.0 / 3.0), + (rOld1.getY() + rOld2.getY() + rOld3.getY()) * (1.0 / 3.0)); + return aAvg; + } + + inline B2DTuple operator+(const B2DTuple& rTupA, const B2DTuple& rTupB) + { + B2DTuple aSum(rTupA); + aSum += rTupB; + return aSum; + } + + inline B2DTuple operator-(const B2DTuple& rTupA, const B2DTuple& rTupB) + { + B2DTuple aSub(rTupA); + aSub -= rTupB; + return aSub; + } + + inline B2DTuple operator/(const B2DTuple& rTupA, const B2DTuple& rTupB) + { + B2DTuple aDiv(rTupA); + aDiv /= rTupB; + return aDiv; + } + + inline B2DTuple operator*(const B2DTuple& rTupA, const B2DTuple& rTupB) + { + B2DTuple aMul(rTupA); + aMul *= rTupB; + return aMul; + } + + inline B2DTuple operator*(const B2DTuple& rTup, double t) + { + B2DTuple aNew(rTup); + aNew *= t; + return aNew; + } + + inline B2DTuple operator*(double t, const B2DTuple& rTup) + { + B2DTuple aNew(rTup); + aNew *= t; + return aNew; + } + + inline B2DTuple operator/(const B2DTuple& rTup, double t) + { + B2DTuple aNew(rTup); + aNew /= t; + return aNew; + } + + inline B2DTuple operator/(double t, const B2DTuple& rTup) + { + B2DTuple aNew(t, t); + B2DTuple aTmp(rTup); + aNew /= aTmp; + return aNew; + } + + /** Round double to nearest integer for 2D tuple + + @return the nearest integer for this tuple + */ + B2ITuple fround(const B2DTuple& rTup); +} // end of namespace basegfx + +#endif /* _BGFX_TUPLE_B2DTUPLE_HXX */ diff --git a/basegfx/inc/basegfx/tuple/b2i64tuple.hxx b/basegfx/inc/basegfx/tuple/b2i64tuple.hxx new file mode 100644 index 000000000000..9c813c07a994 --- /dev/null +++ b/basegfx/inc/basegfx/tuple/b2i64tuple.hxx @@ -0,0 +1,312 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TUPLE_B2I64TUPLE_HXX +#define _BGFX_TUPLE_B2I64TUPLE_HXX + +#include <sal/types.h> +#include <basegfx/tuple/b2dtuple.hxx> + + +namespace basegfx +{ + /** Base class for all Points/Vectors with two sal_Int64 values + + This class provides all methods common to Point + avd Vector classes which are derived from here. + + @derive Use this class to implement Points or Vectors + which are based on two sal_Int64 values + */ + class B2I64Tuple + { + protected: + sal_Int64 mnX; + sal_Int64 mnY; + + public: + /** Create a 2D Tuple + + The tuple is initialized to (0, 0) + */ + B2I64Tuple() + : mnX(0), + mnY(0) + {} + + /** Create a 2D Tuple + + @param fX + This parameter is used to initialize the X-coordinate + of the 2D Tuple. + + @param fY + This parameter is used to initialize the Y-coordinate + of the 2D Tuple. + */ + B2I64Tuple(sal_Int64 fX, sal_Int64 fY) + : mnX( fX ), + mnY( fY ) + {} + + /** Create a copy of a 2D Tuple + + @param rTup + The 2D Tuple which will be copied. + */ + B2I64Tuple(const B2I64Tuple& rTup) + : mnX( rTup.mnX ), + mnY( rTup.mnY ) + {} + + ~B2I64Tuple() + {} + + /// Get X-Coordinate of 2D Tuple + sal_Int64 getX() const + { + return mnX; + } + + /// Get Y-Coordinate of 2D Tuple + sal_Int64 getY() const + { + return mnY; + } + + /// Set X-Coordinate of 2D Tuple + void setX(sal_Int64 fX) + { + mnX = fX; + } + + /// Set Y-Coordinate of 2D Tuple + void setY(sal_Int64 fY) + { + mnY = fY; + } + + /// Array-access to 2D Tuple + const sal_Int64& operator[] (int nPos) const + { + // Here, normally one if(...) should be used. In the assumption that + // both sal_Int64 members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mnX; return mnY; + return *((&mnX) + nPos); + } + + /// Array-access to 2D Tuple + sal_Int64& operator[] (int nPos) + { + // Here, normally one if(...) should be used. In the assumption that + // both sal_Int64 members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mnX; return mnY; + return *((&mnX) + nPos); + } + + // operators + ////////////////////////////////////////////////////////////////////// + + B2I64Tuple& operator+=( const B2I64Tuple& rTup ) + { + mnX += rTup.mnX; + mnY += rTup.mnY; + return *this; + } + + B2I64Tuple& operator-=( const B2I64Tuple& rTup ) + { + mnX -= rTup.mnX; + mnY -= rTup.mnY; + return *this; + } + + B2I64Tuple& operator/=( const B2I64Tuple& rTup ) + { + mnX /= rTup.mnX; + mnY /= rTup.mnY; + return *this; + } + + B2I64Tuple& operator*=( const B2I64Tuple& rTup ) + { + mnX *= rTup.mnX; + mnY *= rTup.mnY; + return *this; + } + + B2I64Tuple& operator*=(sal_Int64 t) + { + mnX *= t; + mnY *= t; + return *this; + } + + B2I64Tuple& operator/=(sal_Int64 t) + { + mnX /= t; + mnY /= t; + return *this; + } + + B2I64Tuple operator-(void) const + { + return B2I64Tuple(-mnX, -mnY); + } + + bool equalZero() const { return mnX == 0 && mnY == 0; } + + bool operator==( const B2I64Tuple& rTup ) const + { + return this == &rTup || (rTup.mnX == mnX && rTup.mnY == mnY); + } + + bool operator!=( const B2I64Tuple& rTup ) const + { + return !(*this == rTup); + } + + B2I64Tuple& operator=( const B2I64Tuple& rTup ) + { + mnX = rTup.mnX; + mnY = rTup.mnY; + return *this; + } + + static const B2I64Tuple& getEmptyTuple(); + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + inline B2I64Tuple minimum(const B2I64Tuple& rTupA, const B2I64Tuple& rTupB) + { + B2I64Tuple aMin( + (rTupB.getX() < rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() < rTupA.getY()) ? rTupB.getY() : rTupA.getY()); + return aMin; + } + + inline B2I64Tuple maximum(const B2I64Tuple& rTupA, const B2I64Tuple& rTupB) + { + B2I64Tuple aMax( + (rTupB.getX() > rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() > rTupA.getY()) ? rTupB.getY() : rTupA.getY()); + return aMax; + } + + inline B2I64Tuple absolute(const B2I64Tuple& rTup) + { + B2I64Tuple aAbs( + (0 > rTup.getX()) ? -rTup.getX() : rTup.getX(), + (0 > rTup.getY()) ? -rTup.getY() : rTup.getY()); + return aAbs; + } + + inline B2DTuple interpolate(const B2I64Tuple& rOld1, const B2I64Tuple& rOld2, double t) + { + B2DTuple aInt( + ((rOld2.getX() - rOld1.getX()) * t) + rOld1.getX(), + ((rOld2.getY() - rOld1.getY()) * t) + rOld1.getY()); + return aInt; + } + + inline B2DTuple average(const B2I64Tuple& rOld1, const B2I64Tuple& rOld2) + { + B2DTuple aAvg( + (rOld1.getX() + rOld2.getX()) * 0.5, + (rOld1.getY() + rOld2.getY()) * 0.5); + return aAvg; + } + + inline B2DTuple average(const B2I64Tuple& rOld1, const B2I64Tuple& rOld2, const B2I64Tuple& rOld3) + { + B2DTuple aAvg( + (rOld1.getX() + rOld2.getX() + rOld3.getX()) * (1.0 / 3.0), + (rOld1.getY() + rOld2.getY() + rOld3.getY()) * (1.0 / 3.0)); + return aAvg; + } + + inline B2I64Tuple operator+(const B2I64Tuple& rTupA, const B2I64Tuple& rTupB) + { + B2I64Tuple aSum(rTupA); + aSum += rTupB; + return aSum; + } + + inline B2I64Tuple operator-(const B2I64Tuple& rTupA, const B2I64Tuple& rTupB) + { + B2I64Tuple aSub(rTupA); + aSub -= rTupB; + return aSub; + } + + inline B2I64Tuple operator/(const B2I64Tuple& rTupA, const B2I64Tuple& rTupB) + { + B2I64Tuple aDiv(rTupA); + aDiv /= rTupB; + return aDiv; + } + + inline B2I64Tuple operator*(const B2I64Tuple& rTupA, const B2I64Tuple& rTupB) + { + B2I64Tuple aMul(rTupA); + aMul *= rTupB; + return aMul; + } + + inline B2I64Tuple operator*(const B2I64Tuple& rTup, sal_Int64 t) + { + B2I64Tuple aNew(rTup); + aNew *= t; + return aNew; + } + + inline B2I64Tuple operator*(sal_Int64 t, const B2I64Tuple& rTup) + { + B2I64Tuple aNew(rTup); + aNew *= t; + return aNew; + } + + inline B2I64Tuple operator/(const B2I64Tuple& rTup, sal_Int64 t) + { + B2I64Tuple aNew(rTup); + aNew /= t; + return aNew; + } + + inline B2I64Tuple operator/(sal_Int64 t, const B2I64Tuple& rTup) + { + B2I64Tuple aNew(t, t); + B2I64Tuple aTmp(rTup); + aNew /= aTmp; + return aNew; + } +} // end of namespace basegfx + +#endif /* _BGFX_TUPLE_B2I64TUPLE_HXX */ diff --git a/basegfx/inc/basegfx/tuple/b2ituple.hxx b/basegfx/inc/basegfx/tuple/b2ituple.hxx new file mode 100644 index 000000000000..da29b5509dec --- /dev/null +++ b/basegfx/inc/basegfx/tuple/b2ituple.hxx @@ -0,0 +1,237 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TUPLE_B2ITUPLE_HXX +#define _BGFX_TUPLE_B2ITUPLE_HXX + +#include <sal/types.h> + + +namespace basegfx +{ + /** Base class for all Points/Vectors with two sal_Int32 values + + This class provides all methods common to Point + avd Vector classes which are derived from here. + + @derive Use this class to implement Points or Vectors + which are based on two sal_Int32 values + */ + class B2ITuple + { + protected: + sal_Int32 mnX; + sal_Int32 mnY; + + public: + /** Create a 2D Tuple + + The tuple is initialized to (0, 0) + */ + B2ITuple() + : mnX(0), + mnY(0) + {} + + /** Create a 2D Tuple + + @param fX + This parameter is used to initialize the X-coordinate + of the 2D Tuple. + + @param fY + This parameter is used to initialize the Y-coordinate + of the 2D Tuple. + */ + B2ITuple(sal_Int32 fX, sal_Int32 fY) + : mnX( fX ), + mnY( fY ) + {} + + /** Create a copy of a 2D Tuple + + @param rTup + The 2D Tuple which will be copied. + */ + B2ITuple(const B2ITuple& rTup) + : mnX( rTup.mnX ), + mnY( rTup.mnY ) + {} + + ~B2ITuple() + {} + + /// Get X-Coordinate of 2D Tuple + sal_Int32 getX() const + { + return mnX; + } + + /// Get Y-Coordinate of 2D Tuple + sal_Int32 getY() const + { + return mnY; + } + + /// Set X-Coordinate of 2D Tuple + void setX(sal_Int32 fX) + { + mnX = fX; + } + + /// Set Y-Coordinate of 2D Tuple + void setY(sal_Int32 fY) + { + mnY = fY; + } + + /// Array-access to 2D Tuple + const sal_Int32& operator[] (int nPos) const + { + // Here, normally one if(...) should be used. In the assumption that + // both sal_Int32 members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mnX; return mnY; + return *((&mnX) + nPos); + } + + /// Array-access to 2D Tuple + sal_Int32& operator[] (int nPos) + { + // Here, normally one if(...) should be used. In the assumption that + // both sal_Int32 members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mnX; return mnY; + return *((&mnX) + nPos); + } + + // operators + ////////////////////////////////////////////////////////////////////// + + B2ITuple& operator+=( const B2ITuple& rTup ) + { + mnX += rTup.mnX; + mnY += rTup.mnY; + return *this; + } + + B2ITuple& operator-=( const B2ITuple& rTup ) + { + mnX -= rTup.mnX; + mnY -= rTup.mnY; + return *this; + } + + B2ITuple& operator/=( const B2ITuple& rTup ) + { + mnX /= rTup.mnX; + mnY /= rTup.mnY; + return *this; + } + + B2ITuple& operator*=( const B2ITuple& rTup ) + { + mnX *= rTup.mnX; + mnY *= rTup.mnY; + return *this; + } + + B2ITuple& operator*=(sal_Int32 t) + { + mnX *= t; + mnY *= t; + return *this; + } + + B2ITuple& operator/=(sal_Int32 t) + { + mnX /= t; + mnY /= t; + return *this; + } + + B2ITuple operator-(void) const + { + return B2ITuple(-mnX, -mnY); + } + + bool equalZero() const { return mnX == 0 && mnY == 0; } + + bool operator==( const B2ITuple& rTup ) const + { + return this == &rTup || (rTup.mnX == mnX && rTup.mnY == mnY); + } + + bool operator!=( const B2ITuple& rTup ) const + { + return !(*this == rTup); + } + + B2ITuple& operator=( const B2ITuple& rTup ) + { + mnX = rTup.mnX; + mnY = rTup.mnY; + return *this; + } + + static const B2ITuple& getEmptyTuple(); + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + class B2DTuple; + + B2ITuple minimum(const B2ITuple& rTupA, const B2ITuple& rTupB); + + B2ITuple maximum(const B2ITuple& rTupA, const B2ITuple& rTupB); + + B2ITuple absolute(const B2ITuple& rTup); + + B2DTuple interpolate(const B2ITuple& rOld1, const B2ITuple& rOld2, double t); + + B2DTuple average(const B2ITuple& rOld1, const B2ITuple& rOld2); + + B2DTuple average(const B2ITuple& rOld1, const B2ITuple& rOld2, const B2ITuple& rOld3); + + B2ITuple operator+(const B2ITuple& rTupA, const B2ITuple& rTupB); + + B2ITuple operator-(const B2ITuple& rTupA, const B2ITuple& rTupB); + + B2ITuple operator/(const B2ITuple& rTupA, const B2ITuple& rTupB); + + B2ITuple operator*(const B2ITuple& rTupA, const B2ITuple& rTupB); + + B2ITuple operator*(const B2ITuple& rTup, sal_Int32 t); + + B2ITuple operator*(sal_Int32 t, const B2ITuple& rTup); + + B2ITuple operator/(const B2ITuple& rTup, sal_Int32 t); + + B2ITuple operator/(sal_Int32 t, const B2ITuple& rTup); + +} // end of namespace basegfx + +#endif /* _BGFX_TUPLE_B2ITUPLE_HXX */ diff --git a/basegfx/inc/basegfx/tuple/b3dtuple.hxx b/basegfx/inc/basegfx/tuple/b3dtuple.hxx new file mode 100644 index 000000000000..11fb797ff0ff --- /dev/null +++ b/basegfx/inc/basegfx/tuple/b3dtuple.hxx @@ -0,0 +1,433 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TUPLE_B3DTUPLE_HXX +#define _BGFX_TUPLE_B3DTUPLE_HXX + +#include <sal/types.h> +#include <basegfx/numeric/ftools.hxx> + +namespace basegfx +{ + // predeclarations + class B3ITuple; + + /** Base class for all Points/Vectors with three double values + + This class provides all methods common to Point + avd Vector classes which are derived from here. + + @derive Use this class to implement Points or Vectors + which are based on three double values + */ + class B3DTuple + { + protected: + double mfX; + double mfY; + double mfZ; + + public: + /** Create a 3D Tuple + + The tuple is initialized to (0.0, 0.0, 0.0) + */ + B3DTuple() + : mfX(0.0), + mfY(0.0), + mfZ(0.0) + {} + + /** Create a 3D Tuple + + @param fX + This parameter is used to initialize the X-coordinate + of the 3D Tuple. + + @param fY + This parameter is used to initialize the Y-coordinate + of the 3D Tuple. + + @param fZ + This parameter is used to initialize the Z-coordinate + of the 3D Tuple. + */ + B3DTuple(double fX, double fY, double fZ) + : mfX(fX), + mfY(fY), + mfZ(fZ) + {} + + /** Create a copy of a 3D Tuple + + @param rTup + The 3D Tuple which will be copied. + */ + B3DTuple(const B3DTuple& rTup) + : mfX( rTup.mfX ), + mfY( rTup.mfY ), + mfZ( rTup.mfZ ) + {} + + /** Create a copy of a 3D integer Tuple + + @param rTup + The 3D Tuple which will be copied. + */ + explicit B3DTuple(const B3ITuple& rTup); + + ~B3DTuple() + {} + + /// get X-Coordinate of 3D Tuple + double getX() const + { + return mfX; + } + + /// get Y-Coordinate of 3D Tuple + double getY() const + { + return mfY; + } + + /// get Z-Coordinate of 3D Tuple + double getZ() const + { + return mfZ; + } + + /// set X-Coordinate of 3D Tuple + void setX(double fX) + { + mfX = fX; + } + + /// set Y-Coordinate of 3D Tuple + void setY(double fY) + { + mfY = fY; + } + + /// set Z-Coordinate of 3D Tuple + void setZ(double fZ) + { + mfZ = fZ; + } + + /// Array-access to 3D Tuple + const double& operator[] (int nPos) const + { + // Here, normally two if(...)'s should be used. In the assumption that + // both double members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mfX; if(1 == nPos) return mfY; return mfZ; + return *((&mfX) + nPos); + } + + /// Array-access to 3D Tuple + double& operator[] (int nPos) + { + // Here, normally two if(...)'s should be used. In the assumption that + // both double members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mfX; if(1 == nPos) return mfY; return mfZ; + return *((&mfX) + nPos); + } + + // comparators with tolerance + ////////////////////////////////////////////////////////////////////// + + bool equalZero() const + { + return (this == &getEmptyTuple() || + (::basegfx::fTools::equalZero(mfX) + && ::basegfx::fTools::equalZero(mfY) + && ::basegfx::fTools::equalZero(mfZ))); + } + + bool equalZero(const double& rfSmallValue) const + { + return (this == &getEmptyTuple() || + (::basegfx::fTools::equalZero(mfX, rfSmallValue) + && ::basegfx::fTools::equalZero(mfY, rfSmallValue) + && ::basegfx::fTools::equalZero(mfZ, rfSmallValue))); + } + + bool equal(const B3DTuple& rTup) const + { + return ( + this == &rTup || + (::basegfx::fTools::equal(mfX, rTup.mfX) && + ::basegfx::fTools::equal(mfY, rTup.mfY) && + ::basegfx::fTools::equal(mfZ, rTup.mfZ))); + } + + bool equal(const B3DTuple& rTup, const double& rfSmallValue) const + { + return ( + this == &rTup || + (::basegfx::fTools::equal(mfX, rTup.mfX, rfSmallValue) && + ::basegfx::fTools::equal(mfY, rTup.mfY, rfSmallValue) && + ::basegfx::fTools::equal(mfZ, rTup.mfZ, rfSmallValue))); + } + + // operators + ////////////////////////////////////////////////////////////////////// + + B3DTuple& operator+=( const B3DTuple& rTup ) + { + mfX += rTup.mfX; + mfY += rTup.mfY; + mfZ += rTup.mfZ; + return *this; + } + + B3DTuple& operator-=( const B3DTuple& rTup ) + { + mfX -= rTup.mfX; + mfY -= rTup.mfY; + mfZ -= rTup.mfZ; + return *this; + } + + B3DTuple& operator/=( const B3DTuple& rTup ) + { + mfX /= rTup.mfX; + mfY /= rTup.mfY; + mfZ /= rTup.mfZ; + return *this; + } + + B3DTuple& operator*=( const B3DTuple& rTup ) + { + mfX *= rTup.mfX; + mfY *= rTup.mfY; + mfZ *= rTup.mfZ; + return *this; + } + + B3DTuple& operator*=(double t) + { + mfX *= t; + mfY *= t; + mfZ *= t; + return *this; + } + + B3DTuple& operator/=(double t) + { + const double fVal(1.0 / t); + mfX *= fVal; + mfY *= fVal; + mfZ *= fVal; + return *this; + } + + B3DTuple operator-(void) const + { + return B3DTuple(-mfX, -mfY, -mfZ); + } + + bool operator==( const B3DTuple& rTup ) const + { + return equal(rTup); + } + + bool operator!=( const B3DTuple& rTup ) const + { + return !equal(rTup); + } + + B3DTuple& operator=( const B3DTuple& rTup ) + { + mfX = rTup.mfX; + mfY = rTup.mfY; + mfZ = rTup.mfZ; + return *this; + } + + void correctValues(const double fCompareValue = 0.0) + { + if(0.0 == fCompareValue) + { + if(::basegfx::fTools::equalZero(mfX)) + { + mfX = 0.0; + } + + if(::basegfx::fTools::equalZero(mfY)) + { + mfY = 0.0; + } + + if(::basegfx::fTools::equalZero(mfZ)) + { + mfZ = 0.0; + } + } + else + { + if(::basegfx::fTools::equal(mfX, fCompareValue)) + { + mfX = fCompareValue; + } + + if(::basegfx::fTools::equal(mfY, fCompareValue)) + { + mfY = fCompareValue; + } + + if(::basegfx::fTools::equal(mfZ, fCompareValue)) + { + mfZ = fCompareValue; + } + } + } + + static const B3DTuple& getEmptyTuple(); + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + inline B3DTuple minimum(const B3DTuple& rTupA, const B3DTuple& rTupB) + { + B3DTuple aMin( + (rTupB.getX() < rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() < rTupA.getY()) ? rTupB.getY() : rTupA.getY(), + (rTupB.getZ() < rTupA.getZ()) ? rTupB.getZ() : rTupA.getZ()); + return aMin; + } + + inline B3DTuple maximum(const B3DTuple& rTupA, const B3DTuple& rTupB) + { + B3DTuple aMax( + (rTupB.getX() > rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() > rTupA.getY()) ? rTupB.getY() : rTupA.getY(), + (rTupB.getZ() > rTupA.getZ()) ? rTupB.getZ() : rTupA.getZ()); + return aMax; + } + + inline B3DTuple absolute(const B3DTuple& rTup) + { + B3DTuple aAbs( + (0.0 > rTup.getX()) ? -rTup.getX() : rTup.getX(), + (0.0 > rTup.getY()) ? -rTup.getY() : rTup.getY(), + (0.0 > rTup.getZ()) ? -rTup.getZ() : rTup.getZ()); + return aAbs; + } + + inline B3DTuple interpolate(const B3DTuple& rOld1, const B3DTuple& rOld2, double t) + { + B3DTuple aInt( + ((rOld2.getX() - rOld1.getX()) * t) + rOld1.getX(), + ((rOld2.getY() - rOld1.getY()) * t) + rOld1.getY(), + ((rOld2.getZ() - rOld1.getZ()) * t) + rOld1.getZ()); + return aInt; + } + + inline B3DTuple average(const B3DTuple& rOld1, const B3DTuple& rOld2) + { + B3DTuple aAvg( + (rOld1.getX() + rOld2.getX()) * 0.5, + (rOld1.getY() + rOld2.getY()) * 0.5, + (rOld1.getZ() + rOld2.getZ()) * 0.5); + return aAvg; + } + + inline B3DTuple average(const B3DTuple& rOld1, const B3DTuple& rOld2, const B3DTuple& rOld3) + { + B3DTuple aAvg( + (rOld1.getX() + rOld2.getX() + rOld3.getX()) * (1.0 / 3.0), + (rOld1.getY() + rOld2.getY() + rOld3.getY()) * (1.0 / 3.0), + (rOld1.getZ() + rOld2.getZ() + rOld3.getZ()) * (1.0 / 3.0)); + return aAvg; + } + + inline B3DTuple operator+(const B3DTuple& rTupA, const B3DTuple& rTupB) + { + B3DTuple aSum(rTupA); + aSum += rTupB; + return aSum; + } + + inline B3DTuple operator-(const B3DTuple& rTupA, const B3DTuple& rTupB) + { + B3DTuple aSub(rTupA); + aSub -= rTupB; + return aSub; + } + + inline B3DTuple operator/(const B3DTuple& rTupA, const B3DTuple& rTupB) + { + B3DTuple aDiv(rTupA); + aDiv /= rTupB; + return aDiv; + } + + inline B3DTuple operator*(const B3DTuple& rTupA, const B3DTuple& rTupB) + { + B3DTuple aMul(rTupA); + aMul *= rTupB; + return aMul; + } + + inline B3DTuple operator*(const B3DTuple& rTup, double t) + { + B3DTuple aNew(rTup); + aNew *= t; + return aNew; + } + + inline B3DTuple operator*(double t, const B3DTuple& rTup) + { + B3DTuple aNew(rTup); + aNew *= t; + return aNew; + } + + inline B3DTuple operator/(const B3DTuple& rTup, double t) + { + B3DTuple aNew(rTup); + aNew /= t; + return aNew; + } + + inline B3DTuple operator/(double t, const B3DTuple& rTup) + { + B3DTuple aNew(rTup); + aNew /= t; + return aNew; + } + + /** Round double to nearest integer for 3D tuple + + @return the nearest integer for this tuple + */ + B3ITuple fround(const B3DTuple& rTup); +} // end of namespace basegfx + +#endif /* _BGFX_TUPLE_B3DTUPLE_HXX */ diff --git a/basegfx/inc/basegfx/tuple/b3i64tuple.hxx b/basegfx/inc/basegfx/tuple/b3i64tuple.hxx new file mode 100644 index 000000000000..a2d754fe4f79 --- /dev/null +++ b/basegfx/inc/basegfx/tuple/b3i64tuple.hxx @@ -0,0 +1,349 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TUPLE_B3I64TUPLE_HXX +#define _BGFX_TUPLE_B3I64TUPLE_HXX + +#include <sal/types.h> +#include <basegfx/tuple/b3dtuple.hxx> + + +namespace basegfx +{ + /** Base class for all Points/Vectors with three sal_Int64 values + + This class provides all methods common to Point + avd Vector classes which are derived from here. + + @derive Use this class to implement Points or Vectors + which are based on three sal_Int64 values + */ + class B3I64Tuple + { + protected: + sal_Int64 mnX; + sal_Int64 mnY; + sal_Int64 mnZ; + + public: + /** Create a 3D Tuple + + The tuple is initialized to (0, 0, 0) + */ + B3I64Tuple() + : mnX(0), + mnY(0), + mnZ(0) + {} + + /** Create a 3D Tuple + + @param nX + This parameter is used to initialize the X-coordinate + of the 3D Tuple. + + @param nY + This parameter is used to initialize the Y-coordinate + of the 3D Tuple. + + @param nZ + This parameter is used to initialize the Z-coordinate + of the 3D Tuple. + */ + B3I64Tuple(sal_Int64 nX, sal_Int64 nY, sal_Int64 nZ) + : mnX(nX), + mnY(nY), + mnZ(nZ) + {} + + /** Create a copy of a 3D Tuple + + @param rTup + The 3D Tuple which will be copied. + */ + B3I64Tuple(const B3I64Tuple& rTup) + : mnX( rTup.mnX ), + mnY( rTup.mnY ), + mnZ( rTup.mnZ ) + {} + + ~B3I64Tuple() + {} + + /// get X-Coordinate of 3D Tuple + sal_Int64 getX() const + { + return mnX; + } + + /// get Y-Coordinate of 3D Tuple + sal_Int64 getY() const + { + return mnY; + } + + /// get Z-Coordinate of 3D Tuple + sal_Int64 getZ() const + { + return mnZ; + } + + /// set X-Coordinate of 3D Tuple + void setX(sal_Int64 nX) + { + mnX = nX; + } + + /// set Y-Coordinate of 3D Tuple + void setY(sal_Int64 nY) + { + mnY = nY; + } + + /// set Z-Coordinate of 3D Tuple + void setZ(sal_Int64 nZ) + { + mnZ = nZ; + } + + /// Array-access to 3D Tuple + const sal_Int64& operator[] (int nPos) const + { + // Here, normally two if(...)'s should be used. In the assumption that + // both sal_Int64 members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mnX; if(1 == nPos) return mnY; return mnZ; + return *((&mnX) + nPos); + } + + /// Array-access to 3D Tuple + sal_Int64& operator[] (int nPos) + { + // Here, normally two if(...)'s should be used. In the assumption that + // both sal_Int64 members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mnX; if(1 == nPos) return mnY; return mnZ; + return *((&mnX) + nPos); + } + + // operators + ////////////////////////////////////////////////////////////////////// + + B3I64Tuple& operator+=( const B3I64Tuple& rTup ) + { + mnX += rTup.mnX; + mnY += rTup.mnY; + mnZ += rTup.mnZ; + return *this; + } + + B3I64Tuple& operator-=( const B3I64Tuple& rTup ) + { + mnX -= rTup.mnX; + mnY -= rTup.mnY; + mnZ -= rTup.mnZ; + return *this; + } + + B3I64Tuple& operator/=( const B3I64Tuple& rTup ) + { + mnX /= rTup.mnX; + mnY /= rTup.mnY; + mnZ /= rTup.mnZ; + return *this; + } + + B3I64Tuple& operator*=( const B3I64Tuple& rTup ) + { + mnX *= rTup.mnX; + mnY *= rTup.mnY; + mnZ *= rTup.mnZ; + return *this; + } + + B3I64Tuple& operator*=(sal_Int64 t) + { + mnX *= t; + mnY *= t; + mnZ *= t; + return *this; + } + + B3I64Tuple& operator/=(sal_Int64 t) + { + mnX /= t; + mnY /= t; + mnZ /= t; + return *this; + } + + B3I64Tuple operator-(void) const + { + return B3I64Tuple(-mnX, -mnY, -mnZ); + } + + bool equalZero() const + { + return (this == &getEmptyTuple() || + (mnX == 0 && mnY == 0 && mnZ == 0)); + } + + bool operator==( const B3I64Tuple& rTup ) const + { + return this == &rTup || (rTup.mnX == mnX && rTup.mnY == mnY && rTup.mnZ == mnZ); + } + + bool operator!=( const B3I64Tuple& rTup ) const + { + return !(*this == rTup); + } + + B3I64Tuple& operator=( const B3I64Tuple& rTup ) + { + mnX = rTup.mnX; + mnY = rTup.mnY; + mnZ = rTup.mnZ; + return *this; + } + + static const B3I64Tuple& getEmptyTuple(); + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + inline B3I64Tuple minimum(const B3I64Tuple& rTupA, const B3I64Tuple& rTupB) + { + B3I64Tuple aMin( + (rTupB.getX() < rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() < rTupA.getY()) ? rTupB.getY() : rTupA.getY(), + (rTupB.getZ() < rTupA.getZ()) ? rTupB.getZ() : rTupA.getZ()); + return aMin; + } + + inline B3I64Tuple maximum(const B3I64Tuple& rTupA, const B3I64Tuple& rTupB) + { + B3I64Tuple aMax( + (rTupB.getX() > rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() > rTupA.getY()) ? rTupB.getY() : rTupA.getY(), + (rTupB.getZ() > rTupA.getZ()) ? rTupB.getZ() : rTupA.getZ()); + return aMax; + } + + inline B3I64Tuple absolute(const B3I64Tuple& rTup) + { + B3I64Tuple aAbs( + (0 > rTup.getX()) ? -rTup.getX() : rTup.getX(), + (0 > rTup.getY()) ? -rTup.getY() : rTup.getY(), + (0 > rTup.getZ()) ? -rTup.getZ() : rTup.getZ()); + return aAbs; + } + + inline B3DTuple interpolate(const B3I64Tuple& rOld1, const B3I64Tuple& rOld2, double t) + { + B3DTuple aInt( + ((rOld2.getX() - rOld1.getX()) * t) + rOld1.getX(), + ((rOld2.getY() - rOld1.getY()) * t) + rOld1.getY(), + ((rOld2.getZ() - rOld1.getZ()) * t) + rOld1.getZ()); + return aInt; + } + + inline B3DTuple average(const B3I64Tuple& rOld1, const B3I64Tuple& rOld2) + { + B3DTuple aAvg( + (rOld1.getX() + rOld2.getX()) * 0.5, + (rOld1.getY() + rOld2.getY()) * 0.5, + (rOld1.getZ() + rOld2.getZ()) * 0.5); + return aAvg; + } + + inline B3DTuple average(const B3I64Tuple& rOld1, const B3I64Tuple& rOld2, const B3I64Tuple& rOld3) + { + B3DTuple aAvg( + (rOld1.getX() + rOld2.getX() + rOld3.getX()) * (1.0 / 3.0), + (rOld1.getY() + rOld2.getY() + rOld3.getY()) * (1.0 / 3.0), + (rOld1.getZ() + rOld2.getZ() + rOld3.getZ()) * (1.0 / 3.0)); + return aAvg; + } + + inline B3I64Tuple operator+(const B3I64Tuple& rTupA, const B3I64Tuple& rTupB) + { + B3I64Tuple aSum(rTupA); + aSum += rTupB; + return aSum; + } + + inline B3I64Tuple operator-(const B3I64Tuple& rTupA, const B3I64Tuple& rTupB) + { + B3I64Tuple aSub(rTupA); + aSub -= rTupB; + return aSub; + } + + inline B3I64Tuple operator/(const B3I64Tuple& rTupA, const B3I64Tuple& rTupB) + { + B3I64Tuple aDiv(rTupA); + aDiv /= rTupB; + return aDiv; + } + + inline B3I64Tuple operator*(const B3I64Tuple& rTupA, const B3I64Tuple& rTupB) + { + B3I64Tuple aMul(rTupA); + aMul *= rTupB; + return aMul; + } + + inline B3I64Tuple operator*(const B3I64Tuple& rTup, sal_Int64 t) + { + B3I64Tuple aNew(rTup); + aNew *= t; + return aNew; + } + + inline B3I64Tuple operator*(sal_Int64 t, const B3I64Tuple& rTup) + { + B3I64Tuple aNew(rTup); + aNew *= t; + return aNew; + } + + inline B3I64Tuple operator/(const B3I64Tuple& rTup, sal_Int64 t) + { + B3I64Tuple aNew(rTup); + aNew /= t; + return aNew; + } + + inline B3I64Tuple operator/(sal_Int64 t, const B3I64Tuple& rTup) + { + B3I64Tuple aNew(t, t, t); + B3I64Tuple aTmp(rTup); + aNew /= aTmp; + return aNew; + } +} // end of namespace basegfx + +#endif /* _BGFX_TUPLE_B3I64TUPLE_HXX */ diff --git a/basegfx/inc/basegfx/tuple/b3ituple.hxx b/basegfx/inc/basegfx/tuple/b3ituple.hxx new file mode 100644 index 000000000000..644ae07b6545 --- /dev/null +++ b/basegfx/inc/basegfx/tuple/b3ituple.hxx @@ -0,0 +1,349 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_TUPLE_B3ITUPLE_HXX +#define _BGFX_TUPLE_B3ITUPLE_HXX + +#include <sal/types.h> +#include <basegfx/tuple/b3dtuple.hxx> + + +namespace basegfx +{ + /** Base class for all Points/Vectors with three sal_Int32 values + + This class provides all methods common to Point + avd Vector classes which are derived from here. + + @derive Use this class to implement Points or Vectors + which are based on three sal_Int32 values + */ + class B3ITuple + { + protected: + sal_Int32 mnX; + sal_Int32 mnY; + sal_Int32 mnZ; + + public: + /** Create a 3D Tuple + + The tuple is initialized to (0, 0, 0) + */ + B3ITuple() + : mnX(0), + mnY(0), + mnZ(0) + {} + + /** Create a 3D Tuple + + @param nX + This parameter is used to initialize the X-coordinate + of the 3D Tuple. + + @param nY + This parameter is used to initialize the Y-coordinate + of the 3D Tuple. + + @param nZ + This parameter is used to initialize the Z-coordinate + of the 3D Tuple. + */ + B3ITuple(sal_Int32 nX, sal_Int32 nY, sal_Int32 nZ) + : mnX(nX), + mnY(nY), + mnZ(nZ) + {} + + /** Create a copy of a 3D Tuple + + @param rTup + The 3D Tuple which will be copied. + */ + B3ITuple(const B3ITuple& rTup) + : mnX( rTup.mnX ), + mnY( rTup.mnY ), + mnZ( rTup.mnZ ) + {} + + ~B3ITuple() + {} + + /// get X-Coordinate of 3D Tuple + sal_Int32 getX() const + { + return mnX; + } + + /// get Y-Coordinate of 3D Tuple + sal_Int32 getY() const + { + return mnY; + } + + /// get Z-Coordinate of 3D Tuple + sal_Int32 getZ() const + { + return mnZ; + } + + /// set X-Coordinate of 3D Tuple + void setX(sal_Int32 nX) + { + mnX = nX; + } + + /// set Y-Coordinate of 3D Tuple + void setY(sal_Int32 nY) + { + mnY = nY; + } + + /// set Z-Coordinate of 3D Tuple + void setZ(sal_Int32 nZ) + { + mnZ = nZ; + } + + /// Array-access to 3D Tuple + const sal_Int32& operator[] (int nPos) const + { + // Here, normally two if(...)'s should be used. In the assumption that + // both sal_Int32 members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mnX; if(1 == nPos) return mnY; return mnZ; + return *((&mnX) + nPos); + } + + /// Array-access to 3D Tuple + sal_Int32& operator[] (int nPos) + { + // Here, normally two if(...)'s should be used. In the assumption that + // both sal_Int32 members can be accessed as an array a shortcut is used here. + // if(0 == nPos) return mnX; if(1 == nPos) return mnY; return mnZ; + return *((&mnX) + nPos); + } + + // operators + ////////////////////////////////////////////////////////////////////// + + B3ITuple& operator+=( const B3ITuple& rTup ) + { + mnX += rTup.mnX; + mnY += rTup.mnY; + mnZ += rTup.mnZ; + return *this; + } + + B3ITuple& operator-=( const B3ITuple& rTup ) + { + mnX -= rTup.mnX; + mnY -= rTup.mnY; + mnZ -= rTup.mnZ; + return *this; + } + + B3ITuple& operator/=( const B3ITuple& rTup ) + { + mnX /= rTup.mnX; + mnY /= rTup.mnY; + mnZ /= rTup.mnZ; + return *this; + } + + B3ITuple& operator*=( const B3ITuple& rTup ) + { + mnX *= rTup.mnX; + mnY *= rTup.mnY; + mnZ *= rTup.mnZ; + return *this; + } + + B3ITuple& operator*=(sal_Int32 t) + { + mnX *= t; + mnY *= t; + mnZ *= t; + return *this; + } + + B3ITuple& operator/=(sal_Int32 t) + { + mnX /= t; + mnY /= t; + mnZ /= t; + return *this; + } + + B3ITuple operator-(void) const + { + return B3ITuple(-mnX, -mnY, -mnZ); + } + + bool equalZero() const + { + return (this == &getEmptyTuple() || + (mnX == 0 && mnY == 0 && mnZ == 0)); + } + + bool operator==( const B3ITuple& rTup ) const + { + return this == &rTup || (rTup.mnX == mnX && rTup.mnY == mnY && rTup.mnZ == mnZ); + } + + bool operator!=( const B3ITuple& rTup ) const + { + return !(*this == rTup); + } + + B3ITuple& operator=( const B3ITuple& rTup ) + { + mnX = rTup.mnX; + mnY = rTup.mnY; + mnZ = rTup.mnZ; + return *this; + } + + static const B3ITuple& getEmptyTuple(); + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + inline B3ITuple minimum(const B3ITuple& rTupA, const B3ITuple& rTupB) + { + B3ITuple aMin( + (rTupB.getX() < rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() < rTupA.getY()) ? rTupB.getY() : rTupA.getY(), + (rTupB.getZ() < rTupA.getZ()) ? rTupB.getZ() : rTupA.getZ()); + return aMin; + } + + inline B3ITuple maximum(const B3ITuple& rTupA, const B3ITuple& rTupB) + { + B3ITuple aMax( + (rTupB.getX() > rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() > rTupA.getY()) ? rTupB.getY() : rTupA.getY(), + (rTupB.getZ() > rTupA.getZ()) ? rTupB.getZ() : rTupA.getZ()); + return aMax; + } + + inline B3ITuple absolute(const B3ITuple& rTup) + { + B3ITuple aAbs( + (0 > rTup.getX()) ? -rTup.getX() : rTup.getX(), + (0 > rTup.getY()) ? -rTup.getY() : rTup.getY(), + (0 > rTup.getZ()) ? -rTup.getZ() : rTup.getZ()); + return aAbs; + } + + inline B3DTuple interpolate(const B3ITuple& rOld1, const B3ITuple& rOld2, double t) + { + B3DTuple aInt( + ((rOld2.getX() - rOld1.getX()) * t) + rOld1.getX(), + ((rOld2.getY() - rOld1.getY()) * t) + rOld1.getY(), + ((rOld2.getZ() - rOld1.getZ()) * t) + rOld1.getZ()); + return aInt; + } + + inline B3DTuple average(const B3ITuple& rOld1, const B3ITuple& rOld2) + { + B3DTuple aAvg( + (rOld1.getX() + rOld2.getX()) * 0.5, + (rOld1.getY() + rOld2.getY()) * 0.5, + (rOld1.getZ() + rOld2.getZ()) * 0.5); + return aAvg; + } + + inline B3DTuple average(const B3ITuple& rOld1, const B3ITuple& rOld2, const B3ITuple& rOld3) + { + B3DTuple aAvg( + (rOld1.getX() + rOld2.getX() + rOld3.getX()) * (1.0 / 3.0), + (rOld1.getY() + rOld2.getY() + rOld3.getY()) * (1.0 / 3.0), + (rOld1.getZ() + rOld2.getZ() + rOld3.getZ()) * (1.0 / 3.0)); + return aAvg; + } + + inline B3ITuple operator+(const B3ITuple& rTupA, const B3ITuple& rTupB) + { + B3ITuple aSum(rTupA); + aSum += rTupB; + return aSum; + } + + inline B3ITuple operator-(const B3ITuple& rTupA, const B3ITuple& rTupB) + { + B3ITuple aSub(rTupA); + aSub -= rTupB; + return aSub; + } + + inline B3ITuple operator/(const B3ITuple& rTupA, const B3ITuple& rTupB) + { + B3ITuple aDiv(rTupA); + aDiv /= rTupB; + return aDiv; + } + + inline B3ITuple operator*(const B3ITuple& rTupA, const B3ITuple& rTupB) + { + B3ITuple aMul(rTupA); + aMul *= rTupB; + return aMul; + } + + inline B3ITuple operator*(const B3ITuple& rTup, sal_Int32 t) + { + B3ITuple aNew(rTup); + aNew *= t; + return aNew; + } + + inline B3ITuple operator*(sal_Int32 t, const B3ITuple& rTup) + { + B3ITuple aNew(rTup); + aNew *= t; + return aNew; + } + + inline B3ITuple operator/(const B3ITuple& rTup, sal_Int32 t) + { + B3ITuple aNew(rTup); + aNew /= t; + return aNew; + } + + inline B3ITuple operator/(sal_Int32 t, const B3ITuple& rTup) + { + B3ITuple aNew(t, t, t); + B3ITuple aTmp(rTup); + aNew /= aTmp; + return aNew; + } +} // end of namespace basegfx + +#endif /* _BGFX_TUPLE_B3ITUPLE_HXX */ diff --git a/basegfx/inc/basegfx/vector/b2dsize.hxx b/basegfx/inc/basegfx/vector/b2dsize.hxx new file mode 100644 index 000000000000..6ca3065ba4f9 --- /dev/null +++ b/basegfx/inc/basegfx/vector/b2dsize.hxx @@ -0,0 +1,42 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_VECTOR_B2DSIZE_HXX +#define _BGFX_VECTOR_B2DSIZE_HXX + +#include <basegfx/vector/b2dvector.hxx> + +namespace basegfx +{ + // syntactic sugar: a B2DVector exactly models a Size object, + // thus, for interface clarity, we provide an alias name + + /// Alias name for interface clarity (not everybody is aware of the identity) + typedef B2DVector B2DSize; +} + +#endif /* _BGFX_VECTOR_B2DSIZE_HXX */ diff --git a/basegfx/inc/basegfx/vector/b2dvector.hxx b/basegfx/inc/basegfx/vector/b2dvector.hxx new file mode 100644 index 000000000000..e4cd8f3f179a --- /dev/null +++ b/basegfx/inc/basegfx/vector/b2dvector.hxx @@ -0,0 +1,267 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_VECTOR_B2DVECTOR_HXX +#define _BGFX_VECTOR_B2DVECTOR_HXX + +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/vector/b2ivector.hxx> +#include <basegfx/vector/b2enums.hxx> + +namespace basegfx +{ + // predeclaration + class B2DHomMatrix; + + /** Base Point class with two double values + + This class derives all operators and common handling for + a 2D data class from B2DTuple. All necessary extensions + which are special for 2D Vectors are added here. + + @see B2DTuple + */ + class B2DVector : public ::basegfx::B2DTuple + { + public: + /** Create a 2D Vector + + The vector is initialized to (0.0, 0.0) + */ + B2DVector() + : B2DTuple() + {} + + /** Create a 2D Vector + + @param fX + This parameter is used to initialize the X-coordinate + of the 2D Vector. + + @param fY + This parameter is used to initialize the Y-coordinate + of the 2D Vector. + */ + B2DVector(double fX, double fY) + : B2DTuple(fX, fY) + {} + + /** Create a copy of a 2D Vector + + @param rVec + The 2D Vector which will be copied. + */ + B2DVector(const B2DVector& rVec) + : B2DTuple(rVec) + {} + + /** Create a copy of a 2D Vector + + @param rVec + The 2D Vector which will be copied. + */ + B2DVector(const ::basegfx::B2IVector& rVec) + : B2DTuple(rVec) + {} + + /** constructor with tuple to allow copy-constructing + from B2DTuple-based classes + */ + B2DVector(const ::basegfx::B2DTuple& rTuple) + : B2DTuple(rTuple) + {} + + ~B2DVector() + {} + + /** *=operator to allow usage from B2DVector, too + */ + B2DVector& operator*=( const B2DVector& rPnt ) + { + mfX *= rPnt.mfX; + mfY *= rPnt.mfY; + return *this; + } + + /** *=operator to allow usage from B2DVector, too + */ + B2DVector& operator*=(double t) + { + mfX *= t; + mfY *= t; + return *this; + } + + /** assignment operator to allow assigning the results + of B2DTuple calculations + */ + B2DVector& operator=( const ::basegfx::B2DTuple& rVec ); + + /** Calculate the length of this 2D Vector + + @return The Length of the 2D Vector + */ + double getLength() const; + + /** Set the length of this 2D Vector + + @param fLen + The to be achieved length of the 2D Vector + */ + B2DVector& setLength(double fLen); + + /** Normalize this 2D Vector + + The length of the 2D Vector is set to 1.0 + */ + B2DVector& normalize(); + + /** Test if this 2D Vector is normalized + + @return + true if lenth of vector is equal to 1.0 + false else + */ + bool isNormalized() const; + + /** Calculate the Scalar with another 2D Vector + + @param rVec + The second 2D Vector + + @return + The Scalar value of the two involved 2D Vectors + */ + double scalar( const B2DVector& rVec ) const; + + /** Calculate the length of the cross product with another 2D Vector + + In 2D, returning an actual vector does not make much + sense here. The magnitude, although, can be readily + used for tasks such as angle calculations, since for + the returned value, the following equation holds: + retVal = getLength(this)*getLength(rVec)*sin(theta), + with theta being the angle between the two vectors. + + @param rVec + The second 2D Vector + + @return + The length of the cross product of the two involved 2D Vectors + */ + double cross( const B2DVector& rVec ) const; + + /** Calculate the Angle with another 2D Vector + + @param rVec + The second 2D Vector + + @return + The Angle value of the two involved 2D Vectors in -pi/2 < return < pi/2 + */ + double angle( const B2DVector& rVec ) const; + + /** Transform vector by given transformation matrix. + + Since this is a vector, translational components of the + matrix are disregarded. + */ + B2DVector& operator*=( const B2DHomMatrix& rMat ); + + static const B2DVector& getEmptyVector(); + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + /** Calculate the orientation to another 2D Vector + + @param rVecA + The first 2D Vector + + @param rVecB + The second 2D Vector + + @return + The mathematical Orientation of the two involved 2D Vectors + */ + B2VectorOrientation getOrientation( const B2DVector& rVecA, const B2DVector& rVecB ); + + /** Calculate a perpendicular 2D Vector to the given one + + @param rVec + The source 2D Vector + + @attention This only works if the given 2D Vector is normalized. + + @return + A 2D Vector perpendicular to the one given in parameter rVec + */ + B2DVector getPerpendicular( const B2DVector& rNormalizedVec ); + + /** Calculate a perpendicular 2D Vector to the given one, + normalize the given one as preparation + + @param rVec + The source 2D Vector + + @return + A normalized 2D Vector perpendicular to the one given in parameter rVec + */ + B2DVector getNormalizedPerpendicular( const B2DVector& rVec ); + + /** Test two vectors which need not to be normalized for parallelism + + @param rVecA + The first 2D Vector + + @param rVecB + The second 2D Vector + + @return + bool if the two values are parallel. Also true if + one of the vectors is empty. + */ + bool areParallel( const B2DVector& rVecA, const B2DVector& rVecB ); + + /** Transform vector by given transformation matrix. + + Since this is a vector, translational components of the + matrix are disregarded. + */ + B2DVector operator*( const B2DHomMatrix& rMat, const B2DVector& rVec ); + + /** Test continuity between given vectors. + + The two given vectors are assumed to describe control points on a + common point. Calculate if there is a continuity between them. + */ + B2VectorContinuity getContinuity( const B2DVector& rBackVector, const B2DVector& rForwardVector ); + +} // end of namespace basegfx + +#endif /* _BGFX_VECTOR_B2DVECTOR_HXX */ diff --git a/basegfx/inc/basegfx/vector/b2enums.hxx b/basegfx/inc/basegfx/vector/b2enums.hxx new file mode 100644 index 000000000000..6f68440bf715 --- /dev/null +++ b/basegfx/inc/basegfx/vector/b2enums.hxx @@ -0,0 +1,76 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_VECTOR_B2ENUMS_HXX +#define _BGFX_VECTOR_B2ENUMS_HXX + +#include <sal/types.h> + +namespace basegfx +{ + /** Descriptor for the mathematical orientations of two 2D Vectors + */ + enum B2VectorOrientation + { + /// mathematically positive oriented + ORIENTATION_POSITIVE = 0, + + /// mathematically negative oriented + ORIENTATION_NEGATIVE, + + /// mathematically neutral, thus parallel + ORIENTATION_NEUTRAL + }; + + /** Descriptor for the mathematical continuity of two 2D Vectors + */ + enum B2VectorContinuity + { + /// none + CONTINUITY_NONE = 0, + + /// mathematically negative oriented + CONTINUITY_C1, + + /// mathematically neutral, thus parallel + CONTINUITY_C2 + }; + + /** Descriptor for possible line joins between two line segments + */ + enum B2DLineJoin + { + B2DLINEJOIN_NONE, // no rounding + B2DLINEJOIN_MIDDLE, // calc middle value between joints + B2DLINEJOIN_BEVEL, // join edges with line + B2DLINEJOIN_MITER, // extend till cut + B2DLINEJOIN_ROUND // create arc + }; + +} // end of namespace basegfx + +#endif /* _BGFX_VECTOR_B2ENUMS_HXX */ diff --git a/basegfx/inc/basegfx/vector/b2isize.hxx b/basegfx/inc/basegfx/vector/b2isize.hxx new file mode 100644 index 000000000000..e6cdb048995b --- /dev/null +++ b/basegfx/inc/basegfx/vector/b2isize.hxx @@ -0,0 +1,42 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_VECTOR_B2ISIZE_HXX +#define _BGFX_VECTOR_B2ISIZE_HXX + +#include <basegfx/vector/b2ivector.hxx> + +namespace basegfx +{ + // syntactic sugar: a B2IVector exactly models a Size object, + // thus, for interface clarity, we provide an alias name + + /// Alias name for interface clarity (not everybody is aware of the identity) + typedef B2IVector B2ISize; +} + +#endif /* _BGFX_VECTOR_B2ISIZE_HXX */ diff --git a/basegfx/inc/basegfx/vector/b2ivector.hxx b/basegfx/inc/basegfx/vector/b2ivector.hxx new file mode 100644 index 000000000000..9e2fe1009841 --- /dev/null +++ b/basegfx/inc/basegfx/vector/b2ivector.hxx @@ -0,0 +1,230 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_VECTOR_B2IVECTOR_HXX +#define _BGFX_VECTOR_B2IVECTOR_HXX + +#include <basegfx/tuple/b2ituple.hxx> +#include <basegfx/vector/b2enums.hxx> + +namespace basegfx +{ + // predeclaration + class B2DHomMatrix; + + /** Base Point class with two sal_Int32 values + + This class derives all operators and common handling for + a 2D data class from B2ITuple. All necessary extensions + which are special for 2D Vectors are added here. + + @see B2ITuple + */ + class B2IVector : public ::basegfx::B2ITuple + { + public: + /** Create a 2D Vector + + The vector is initialized to (0, 0) + */ + B2IVector() + : B2ITuple() + {} + + /** Create a 2D Vector + + @param nX + This parameter is used to initialize the X-coordinate + of the 2D Vector. + + @param nY + This parameter is used to initialize the Y-coordinate + of the 2D Vector. + */ + B2IVector(sal_Int32 nX, sal_Int32 nY) + : B2ITuple(nX, nY) + {} + + /** Create a copy of a 2D Vector + + @param rVec + The 2D Vector which will be copied. + */ + B2IVector(const B2IVector& rVec) + : B2ITuple(rVec) + {} + + /** constructor with tuple to allow copy-constructing + from B2ITuple-based classes + */ + B2IVector(const ::basegfx::B2ITuple& rTuple) + : B2ITuple(rTuple) + {} + + ~B2IVector() + {} + + /** *=operator to allow usage from B2IVector, too + */ + B2IVector& operator*=( const B2IVector& rPnt ) + { + mnX *= rPnt.mnX; + mnY *= rPnt.mnY; + return *this; + } + + /** *=operator to allow usage from B2IVector, too + */ + B2IVector& operator*=(sal_Int32 t) + { + mnX *= t; + mnY *= t; + return *this; + } + + /** assignment operator to allow assigning the results + of B2ITuple calculations + */ + B2IVector& operator=( const ::basegfx::B2ITuple& rVec ); + + /** Calculate the length of this 2D Vector + + @return The Length of the 2D Vector + */ + double getLength() const; + + /** Set the length of this 2D Vector + + @param fLen + The to be achieved length of the 2D Vector + */ + B2IVector& setLength(double fLen); + + /** Calculate the Scalar with another 2D Vector + + @param rVec + The second 2D Vector + + @return + The Scalar value of the two involved 2D Vectors + */ + double scalar( const B2IVector& rVec ) const; + + /** Calculate the length of the cross product with another 2D Vector + + In 2D, returning an actual vector does not make much + sense here. The magnitude, although, can be readily + used for tasks such as angle calculations, since for + the returned value, the following equation holds: + retVal = getLength(this)*getLength(rVec)*sin(theta), + with theta being the angle between the two vectors. + + @param rVec + The second 2D Vector + + @return + The length of the cross product of the two involved 2D Vectors + */ + double cross( const B2IVector& rVec ) const; + + /** Calculate the Angle with another 2D Vector + + @param rVec + The second 2D Vector + + @return + The Angle value of the two involved 2D Vectors in -pi/2 < return < pi/2 + */ + double angle( const B2IVector& rVec ) const; + + /** Transform vector by given transformation matrix. + + Since this is a vector, translational components of the + matrix are disregarded. + */ + B2IVector& operator*=( const B2DHomMatrix& rMat ); + + static const B2IVector& getEmptyVector(); + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + /** Calculate the orientation to another 2D Vector + + @param rVecA + The first 2D Vector + + @param rVecB + The second 2D Vector + + @return + The mathematical Orientation of the two involved 2D Vectors + */ + B2VectorOrientation getOrientation( const B2IVector& rVecA, const B2IVector& rVecB ); + + /** Calculate a perpendicular 2D Vector to the given one + + @param rVec + The source 2D Vector + + @return + A 2D Vector perpendicular to the one given in parameter rVec + */ + B2IVector getPerpendicular( const B2IVector& rVec ); + + /** Test two vectors which need not to be normalized for parallelism + + @param rVecA + The first 2D Vector + + @param rVecB + The second 2D Vector + + @return + bool if the two values are parallel. Also true if + one of the vectors is empty. + */ + bool areParallel( const B2IVector& rVecA, const B2IVector& rVecB ); + + /** Transform vector by given transformation matrix. + + Since this is a vector, translational components of the + matrix are disregarded. + */ + B2IVector operator*( const B2DHomMatrix& rMat, const B2IVector& rVec ); + + /** Test continuity between given vectors. + + The two given vectors are assumed to describe control points on a + common point. Calculate if there is a continuity between them. + */ + B2VectorContinuity getContinuity( const B2IVector& rBackVector, const B2IVector& rForwardVector ); + +} // end of namespace basegfx + +#endif /* _BGFX_VECTOR_B2IVECTOR_HXX */ diff --git a/basegfx/inc/basegfx/vector/b3dsize.hxx b/basegfx/inc/basegfx/vector/b3dsize.hxx new file mode 100644 index 000000000000..e89a66bfa86c --- /dev/null +++ b/basegfx/inc/basegfx/vector/b3dsize.hxx @@ -0,0 +1,42 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_VECTOR_B3DSIZE_HXX +#define _BGFX_VECTOR_B3DSIZE_HXX + +#include <basegfx/vector/b3dvector.hxx> + +namespace basegfx +{ + // syntactic sugar: a B3DVector exactly models a Size3D object, + // thus, for interface clarity, we provide an alias name + + /// Alias name for interface clarity (not everybody is aware of the identity) + typedef B3DVector B3DSize; +} + +#endif /* _BGFX_VECTOR_B3DSIZE_HXX */ diff --git a/basegfx/inc/basegfx/vector/b3dvector.hxx b/basegfx/inc/basegfx/vector/b3dvector.hxx new file mode 100644 index 000000000000..c4c329881170 --- /dev/null +++ b/basegfx/inc/basegfx/vector/b3dvector.hxx @@ -0,0 +1,340 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_VECTOR_B3DVECTOR_HXX +#define _BGFX_VECTOR_B3DVECTOR_HXX + +#include <basegfx/tuple/b3dtuple.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + // predeclaration + class B3DHomMatrix; + + /** Base Point class with three double values + + This class derives all operators and common handling for + a 3D data class from B3DTuple. All necessary extensions + which are special for 3D Vectors are added here. + + @see B3DTuple + */ + class B3DVector : public ::basegfx::B3DTuple + { + public: + /** Create a 3D Vector + + The vector is initialized to (0.0, 0.0, 0.0) + */ + B3DVector() + : B3DTuple() + {} + + /** Create a 3D Vector + + @param fX + This parameter is used to initialize the X-coordinate + of the 3D Vector. + + @param fY + This parameter is used to initialize the Y-coordinate + of the 3D Vector. + + @param fZ + This parameter is used to initialize the Z-coordinate + of the 3D Vector. + */ + B3DVector(double fX, double fY, double fZ) + : B3DTuple(fX, fY, fZ) + {} + + /** Create a copy of a 3D Vector + + @param rVec + The 3D Vector which will be copied. + */ + B3DVector(const B3DVector& rVec) + : B3DTuple(rVec) + {} + + /** constructor with tuple to allow copy-constructing + from B3DTuple-based classes + */ + B3DVector(const ::basegfx::B3DTuple& rTuple) + : B3DTuple(rTuple) + {} + + ~B3DVector() + {} + + /** *=operator to allow usage from B3DVector, too + */ + B3DVector& operator*=( const B3DVector& rPnt ) + { + mfX *= rPnt.mfX; + mfY *= rPnt.mfY; + mfZ *= rPnt.mfZ; + return *this; + } + + /** *=operator to allow usage from B3DVector, too + */ + B3DVector& operator*=(double t) + { + mfX *= t; + mfY *= t; + mfZ *= t; + return *this; + } + + /** assignment operator to allow assigning the results + of B3DTuple calculations + */ + B3DVector& operator=( const ::basegfx::B3DTuple& rVec ) + { + mfX = rVec.getX(); + mfY = rVec.getY(); + mfZ = rVec.getZ(); + return *this; + } + + /** Calculate the length of this 3D Vector + + @return The Length of the 3D Vector + */ + double getLength(void) const + { + double fLen(scalar(*this)); + if((0.0 == fLen) || (1.0 == fLen)) + return fLen; + return sqrt(fLen); + } + + /** Calculate the length in the XY-Plane for this 3D Vector + + @return The XY-Plane Length of the 3D Vector + */ + double getXYLength(void) const + { + double fLen((mfX * mfX) + (mfY * mfY)); + if((0.0 == fLen) || (1.0 == fLen)) + return fLen; + return sqrt(fLen); + } + + /** Calculate the length in the XZ-Plane for this 3D Vector + + @return The XZ-Plane Length of the 3D Vector + */ + double getXZLength(void) const + { + double fLen((mfX * mfX) + (mfZ * mfZ)); // #i73040# + if((0.0 == fLen) || (1.0 == fLen)) + return fLen; + return sqrt(fLen); + } + + /** Calculate the length in the YZ-Plane for this 3D Vector + + @return The YZ-Plane Length of the 3D Vector + */ + double getYZLength(void) const + { + double fLen((mfY * mfY) + (mfZ * mfZ)); + if((0.0 == fLen) || (1.0 == fLen)) + return fLen; + return sqrt(fLen); + } + + /** Set the length of this 3D Vector + + @param fLen + The to be achieved length of the 3D Vector + */ + B3DVector& setLength(double fLen) + { + double fLenNow(scalar(*this)); + + if(!::basegfx::fTools::equalZero(fLenNow)) + { + const double fOne(1.0); + + if(!::basegfx::fTools::equal(fOne, fLenNow)) + { + fLen /= sqrt(fLenNow); + } + + mfX *= fLen; + mfY *= fLen; + mfZ *= fLen; + } + + return *this; + } + + /** Normalize this 3D Vector + + The length of the 3D Vector is set to 1.0 + */ + B3DVector& normalize(); + + /** Test if this 3D Vector is normalized + + @return + true if lenth of vector is equal to 1.0 + false else + */ + bool isNormalized() const + { + const double fOne(1.0); + const double fScalar(scalar(*this)); + + return (::basegfx::fTools::equal(fOne, fScalar)); + } + + /** get a 3D Vector which is perpendicular to this and a given 3D Vector + + @attention This only works if this and the given 3D Vector are + both normalized. + + @param rNormalizedVec + A normalized 3D Vector. + + @return + A 3D Vector perpendicular to this and the given one + */ + B3DVector getPerpendicular(const B3DVector& rNormalizedVec) const; + + /** get the projection of this Vector on the given Plane + + @attention This only works if the given 3D Vector defining + the Plane is normalized. + + @param rNormalizedPlane + A normalized 3D Vector defining a Plane. + + @return + The projected 3D Vector + */ + B3DVector getProjectionOnPlane(const B3DVector& rNormalizedPlane) const; + + /** Calculate the Scalar product + + This method calculates the Scalar product between this + and the given 3D Vector. + + @param rVec + A second 3D Vector. + + @return + The Scalar Product of two 3D Vectors + */ + double scalar(const B3DVector& rVec) const + { + return ((mfX * rVec.mfX) + (mfY * rVec.mfY) + (mfZ * rVec.mfZ)); + } + + /** Transform vector by given transformation matrix. + + Since this is a vector, translational components of the + matrix are disregarded. + */ + B3DVector& operator*=( const B3DHomMatrix& rMat ); + + static const B3DVector& getEmptyVector() + { + return (const B3DVector&) ::basegfx::B3DTuple::getEmptyTuple(); + } + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + /** get a 3D Vector which is in 2D (ignoring + the Z-Coordinate) perpendicular to a given 3D Vector + + @attention This only works if the given 3D Vector is normalized. + + @param rNormalizedVec + A normalized 3D Vector. + + @return + A 3D Vector perpendicular to the given one in X,Y (2D). + */ + inline B3DVector getPerpendicular2D( const B3DVector& rNormalizedVec ) + { + B3DVector aPerpendicular(-rNormalizedVec.getY(), rNormalizedVec.getX(), rNormalizedVec.getZ()); + return aPerpendicular; + } + + /** Test two vectors which need not to be normalized for parallelism + + @param rVecA + The first 3D Vector + + @param rVecB + The second 3D Vector + + @return + bool if the two values are parallel. Also true if + one of the vectors is empty. + */ + bool areParallel( const B3DVector& rVecA, const B3DVector& rVecB ); + + /** Transform vector by given transformation matrix. + + Since this is a vector, translational components of the + matrix are disregarded. + */ + B3DVector operator*( const B3DHomMatrix& rMat, const B3DVector& rVec ); + + /** Calculate the Cross Product of two 3D Vectors + + @param rVecA + A first 3D Vector. + + @param rVecB + A second 3D Vector. + + @return + The Cross Product of both 3D Vectors + */ + inline B3DVector cross(const B3DVector& rVecA, const B3DVector& rVecB) + { + B3DVector aVec( + rVecA.getY() * rVecB.getZ() - rVecA.getZ() * rVecB.getY(), + rVecA.getZ() * rVecB.getX() - rVecA.getX() * rVecB.getZ(), + rVecA.getX() * rVecB.getY() - rVecA.getY() * rVecB.getX()); + return aVec; + } +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +#endif /* _BGFX_VECTOR_B3DVECTOR_HXX */ diff --git a/basegfx/inc/basegfx/vector/b3isize.hxx b/basegfx/inc/basegfx/vector/b3isize.hxx new file mode 100644 index 000000000000..604ce5681ee7 --- /dev/null +++ b/basegfx/inc/basegfx/vector/b3isize.hxx @@ -0,0 +1,42 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_VECTOR_B3ISIZE_HXX +#define _BGFX_VECTOR_B3ISIZE_HXX + +#include <basegfx/vector/b3ivector.hxx> + +namespace basegfx +{ + // syntactic sugar: a B3IVector exactly models a Size3D object, + // thus, for interface clarity, we provide an alias name + + /// Alias name for interface clarity (not everybody is aware of the identity) + typedef B3IVector B3ISize; +} + +#endif /* _BGFX_VECTOR_B3ISIZE_HXX */ diff --git a/basegfx/inc/basegfx/vector/b3ivector.hxx b/basegfx/inc/basegfx/vector/b3ivector.hxx new file mode 100644 index 000000000000..b4900989cc06 --- /dev/null +++ b/basegfx/inc/basegfx/vector/b3ivector.hxx @@ -0,0 +1,259 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BGFX_VECTOR_B3IVECTOR_HXX +#define _BGFX_VECTOR_B3IVECTOR_HXX + +#include <basegfx/tuple/b3ituple.hxx> + +namespace basegfx +{ + // predeclaration + class B3DHomMatrix; + + /** Base Point class with three sal_Int32 values + + This class derives all operators and common handling for + a 3D data class from B3ITuple. All necessary extensions + which are special for 3D Vectors are added here. + + @see B3ITuple + */ + class B3IVector : public ::basegfx::B3ITuple + { + public: + /** Create a 3D Vector + + The vector is initialized to (0, 0, 0) + */ + B3IVector() + : B3ITuple() + {} + + /** Create a 3D Vector + + @param nX + This parameter is used to initialize the X-coordinate + of the 3D Vector. + + @param nY + This parameter is used to initialize the Y-coordinate + of the 3D Vector. + + @param nZ + This parameter is used to initialize the Z-coordinate + of the 3D Vector. + */ + B3IVector(sal_Int32 nX, sal_Int32 nY, sal_Int32 nZ) + : B3ITuple(nX, nY, nZ) + {} + + /** Create a copy of a 3D Vector + + @param rVec + The 3D Vector which will be copied. + */ + B3IVector(const B3IVector& rVec) + : B3ITuple(rVec) + {} + + /** constructor with tuple to allow copy-constructing + from B3ITuple-based classes + */ + B3IVector(const ::basegfx::B3ITuple& rTuple) + : B3ITuple(rTuple) + {} + + ~B3IVector() + {} + + /** *=operator to allow usage from B3IVector, too + */ + B3IVector& operator*=( const B3IVector& rPnt ) + { + mnX *= rPnt.mnX; + mnY *= rPnt.mnY; + mnZ *= rPnt.mnZ; + return *this; + } + + /** *=operator to allow usage from B3IVector, too + */ + B3IVector& operator*=(sal_Int32 t) + { + mnX *= t; + mnY *= t; + mnZ *= t; + return *this; + } + + /** assignment operator to allow assigning the results + of B3ITuple calculations + */ + B3IVector& operator=( const ::basegfx::B3ITuple& rVec ) + { + mnX = rVec.getX(); + mnY = rVec.getY(); + mnZ = rVec.getZ(); + return *this; + } + + /** Calculate the length of this 3D Vector + + @return The Length of the 3D Vector + */ + double getLength(void) const + { + double fLen(scalar(*this)); + if((0 == fLen) || (1.0 == fLen)) + return fLen; + return sqrt(fLen); + } + + /** Calculate the length in the XY-Plane for this 3D Vector + + @return The XY-Plane Length of the 3D Vector + */ + double getXYLength(void) const + { + double fLen((mnX * mnX) + (mnY * mnY)); + if((0 == fLen) || (1.0 == fLen)) + return fLen; + return sqrt(fLen); + } + + /** Calculate the length in the XZ-Plane for this 3D Vector + + @return The XZ-Plane Length of the 3D Vector + */ + double getXZLength(void) const + { + double fLen((mnX * mnZ) + (mnY * mnZ)); + if((0 == fLen) || (1.0 == fLen)) + return fLen; + return sqrt(fLen); + } + + /** Calculate the length in the YZ-Plane for this 3D Vector + + @return The YZ-Plane Length of the 3D Vector + */ + double getYZLength(void) const + { + double fLen((mnY * mnY) + (mnZ * mnZ)); + if((0 == fLen) || (1.0 == fLen)) + return fLen; + return sqrt(fLen); + } + + /** Set the length of this 3D Vector + + @param fLen + The to be achieved length of the 3D Vector + */ + B3IVector& setLength(double fLen) + { + double fLenNow(scalar(*this)); + + if(!::basegfx::fTools::equalZero(fLenNow)) + { + const double fOne(1.0); + + if(!::basegfx::fTools::equal(fOne, fLenNow)) + { + fLen /= sqrt(fLenNow); + } + + mnX = fround(mnX*fLen); + mnY = fround(mnY*fLen); + mnZ = fround(mnZ*fLen); + } + + return *this; + } + + /** Calculate the Scalar product + + This method calculates the Scalar product between this + and the given 3D Vector. + + @param rVec + A second 3D Vector. + + @return + The Scalar Product of two 3D Vectors + */ + double scalar(const B3IVector& rVec) const + { + return ((mnX * rVec.mnX) + (mnY * rVec.mnY) + (mnZ * rVec.mnZ)); + } + + /** Transform vector by given transformation matrix. + + Since this is a vector, translational components of the + matrix are disregarded. + */ + B3IVector& operator*=( const B3DHomMatrix& rMat ); + + static const B3IVector& getEmptyVector() + { + return (const B3IVector&) ::basegfx::B3ITuple::getEmptyTuple(); + } + }; + + // external operators + ////////////////////////////////////////////////////////////////////////// + + /** Transform vector by given transformation matrix. + + Since this is a vector, translational components of the + matrix are disregarded. + */ + B3IVector operator*( const B3DHomMatrix& rMat, const B3IVector& rVec ); + + /** Calculate the Cross Product of two 3D Vectors + + @param rVecA + A first 3D Vector. + + @param rVecB + A second 3D Vector. + + @return + The Cross Product of both 3D Vectors + */ + inline B3IVector cross(const B3IVector& rVecA, const B3IVector& rVecB) + { + B3IVector aVec( + rVecA.getY() * rVecB.getZ() - rVecA.getZ() * rVecB.getY(), + rVecA.getZ() * rVecB.getX() - rVecA.getX() * rVecB.getZ(), + rVecA.getX() * rVecB.getY() - rVecA.getY() * rVecB.getX()); + return aVec; + } +} // end of namespace basegfx + +#endif /* _BGFX_VECTOR_B3DVECTOR_HXX */ diff --git a/basegfx/inc/makefile.mk b/basegfx/inc/makefile.mk new file mode 100644 index 000000000000..aa8007b7e507 --- /dev/null +++ b/basegfx/inc/makefile.mk @@ -0,0 +1,47 @@ +#************************************************************************* +# +# 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=inc + +# --- Settings ----------------------------------------------------- + +.INCLUDE : settings.mk + +# --- Files -------------------------------------------------------- +# --- Targets ------------------------------------------------------- + +.INCLUDE : target.mk + +.IF "$(ENABLE_PCH)"!="" +ALLTAR : \ + $(SLO)$/precompiled.pch \ + $(SLO)$/precompiled_ex.pch + +.ENDIF # "$(ENABLE_PCH)"!="" + diff --git a/basegfx/inc/pch/precompiled_basegfx.cxx b/basegfx/inc/pch/precompiled_basegfx.cxx new file mode 100644 index 000000000000..ee9c448f8af4 --- /dev/null +++ b/basegfx/inc/pch/precompiled_basegfx.cxx @@ -0,0 +1,29 @@ +/************************************************************************* + * + * 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 "precompiled_basegfx.hxx" + diff --git a/basegfx/inc/pch/precompiled_basegfx.hxx b/basegfx/inc/pch/precompiled_basegfx.hxx new file mode 100644 index 000000000000..45f87434590d --- /dev/null +++ b/basegfx/inc/pch/precompiled_basegfx.hxx @@ -0,0 +1,32 @@ +/************************************************************************* + * + * 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): Generated on 2006-09-01 17:49:30.796084 + +#ifdef PRECOMPILED_HEADERS +#endif + diff --git a/basegfx/prj/build.lst b/basegfx/prj/build.lst new file mode 100644 index 000000000000..fe3354b3f256 --- /dev/null +++ b/basegfx/prj/build.lst @@ -0,0 +1,18 @@ +fx basegfx : o3tl sal offuh cppuhelper cppu CPPUNIT:cppunit NULL +fx basegfx usr1 - all fx_mkout NULL +fx basegfx\inc nmake - all fx_inc NULL +fx basegfx\prj get - all fx_prj NULL +fx basegfx\source\curve nmake - all fx_curve fx_inc NULL +fx basegfx\source\matrix nmake - all fx_matrix fx_inc NULL +fx basegfx\source\numeric nmake - all fx_numeric fx_inc NULL +fx basegfx\source\point nmake - all fx_point fx_inc NULL +fx basegfx\source\polygon nmake - all fx_polygon fx_inc NULL +fx basegfx\source\range nmake - all fx_range fx_inc NULL +fx basegfx\source\tuple nmake - all fx_tuple fx_inc NULL +fx basegfx\source\tools nmake - all fx_tools fx_inc NULL +fx basegfx\source\vector nmake - all fx_vector fx_inc NULL +fx basegfx\source\color nmake - all fx_color fx_inc NULL +fx basegfx\source\pixel nmake - all fx_pixel fx_inc NULL +fx basegfx\source\raster nmake - all fx_raster fx_inc NULL +fx basegfx\util nmake - all fx_util fx_curve fx_matrix fx_numeric fx_point fx_polygon fx_range fx_tuple fx_tools fx_vector fx_color fx_pixel fx_raster NULL +fx basegfx\test nmake - all fx_tests fx_util NULL diff --git a/basegfx/prj/d.lst b/basegfx/prj/d.lst new file mode 100644 index 000000000000..3d4d985f3ae6 --- /dev/null +++ b/basegfx/prj/d.lst @@ -0,0 +1,113 @@ +..\%__SRC%\lib\ibasegfx.lib %_DEST%\lib%_EXT%\ibasegfx.lib +..\%__SRC%\lib\basegfx_s.lib %_DEST%\lib%_EXT%\basegfx_s.lib + +..\%__SRC%\bin\basegfx?????.dll %_DEST%\bin%_EXT%\basegfx?????.dll +..\%__SRC%\bin\bgfx*.dll %_DEST%\bin%_EXT%\bgfx*.dll + +..\%__SRC%\lib\libbasegfx*.* %_DEST%\lib%_EXT%\libbasegfx*.* +..\%__SRC%\lib\*.a %_DEST%\lib%_EXT%\*.a + +mkdir: %_DEST%\inc%_EXT%\basegfx + +mkdir: %_DEST%\inc%_EXT%\basegfx\matrix +..\inc\basegfx\matrix\b2dhommatrix.hxx %_DEST%\inc%_EXT%\basegfx\matrix\b2dhommatrix.hxx +..\inc\basegfx\matrix\b2dhommatrixtools.hxx %_DEST%\inc%_EXT%\basegfx\matrix\b2dhommatrixtools.hxx +..\inc\basegfx\matrix\b3dhommatrix.hxx %_DEST%\inc%_EXT%\basegfx\matrix\b3dhommatrix.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\point +..\inc\basegfx\point\b2dpoint.hxx %_DEST%\inc%_EXT%\basegfx\point\b2dpoint.hxx +..\inc\basegfx\point\b3dpoint.hxx %_DEST%\inc%_EXT%\basegfx\point\b3dpoint.hxx +..\inc\basegfx\point\b2ipoint.hxx %_DEST%\inc%_EXT%\basegfx\point\b2ipoint.hxx +..\inc\basegfx\point\b3ipoint.hxx %_DEST%\inc%_EXT%\basegfx\point\b3ipoint.hxx +..\inc\basegfx\point\b2dhompoint.hxx %_DEST%\inc%_EXT%\basegfx\point\b2dhompoint.hxx +..\inc\basegfx\point\b3dhompoint.hxx %_DEST%\inc%_EXT%\basegfx\point\b3dhompoint.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\range +..\inc\basegfx\range\rangeexpander.hxx %_DEST%\inc%_EXT%\basegfx\range\rangeexpander.hxx +..\inc\basegfx\range\basicrange.hxx %_DEST%\inc%_EXT%\basegfx\range\basicrange.hxx +..\inc\basegfx\range\basicbox.hxx %_DEST%\inc%_EXT%\basegfx\range\basicbox.hxx +..\inc\basegfx\range\b1drange.hxx %_DEST%\inc%_EXT%\basegfx\range\b1drange.hxx +..\inc\basegfx\range\b2dpolyrange.hxx %_DEST%\inc%_EXT%\basegfx\range\b2dpolyrange.hxx +..\inc\basegfx\range\b2drange.hxx %_DEST%\inc%_EXT%\basegfx\range\b2drange.hxx +..\inc\basegfx\range\b2drectangle.hxx %_DEST%\inc%_EXT%\basegfx\range\b2drectangle.hxx +..\inc\basegfx\range\b2dconnectedranges.hxx %_DEST%\inc%_EXT%\basegfx\range\b2dconnectedranges.hxx +..\inc\basegfx\range\b3drange.hxx %_DEST%\inc%_EXT%\basegfx\range\b3drange.hxx +..\inc\basegfx\range\b3dvolume.hxx %_DEST%\inc%_EXT%\basegfx\range\b3dvolume.hxx +..\inc\basegfx\range\b1irange.hxx %_DEST%\inc%_EXT%\basegfx\range\b1irange.hxx +..\inc\basegfx\range\b2irange.hxx %_DEST%\inc%_EXT%\basegfx\range\b2irange.hxx +..\inc\basegfx\range\b2irectangle.hxx %_DEST%\inc%_EXT%\basegfx\range\b2irectangle.hxx +..\inc\basegfx\range\b3irange.hxx %_DEST%\inc%_EXT%\basegfx\range\b3irange.hxx +..\inc\basegfx\range\b3ivolume.hxx %_DEST%\inc%_EXT%\basegfx\range\b3ivolume.hxx +..\inc\basegfx\range\b1ibox.hxx %_DEST%\inc%_EXT%\basegfx\range\b1ibox.hxx +..\inc\basegfx\range\b2ibox.hxx %_DEST%\inc%_EXT%\basegfx\range\b2ibox.hxx +..\inc\basegfx\range\b3ibox.hxx %_DEST%\inc%_EXT%\basegfx\range\b3ibox.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\vector +..\inc\basegfx\vector\b2dvector.hxx %_DEST%\inc%_EXT%\basegfx\vector\b2dvector.hxx +..\inc\basegfx\vector\b2enums.hxx %_DEST%\inc%_EXT%\basegfx\vector\b2enums.hxx +..\inc\basegfx\vector\b2dsize.hxx %_DEST%\inc%_EXT%\basegfx\vector\b2dsize.hxx +..\inc\basegfx\vector\b3dvector.hxx %_DEST%\inc%_EXT%\basegfx\vector\b3dvector.hxx +..\inc\basegfx\vector\b3dsize.hxx %_DEST%\inc%_EXT%\basegfx\vector\b3dsize.hxx +..\inc\basegfx\vector\b2ivector.hxx %_DEST%\inc%_EXT%\basegfx\vector\b2ivector.hxx +..\inc\basegfx\vector\b2isize.hxx %_DEST%\inc%_EXT%\basegfx\vector\b2isize.hxx +..\inc\basegfx\vector\b3ivector.hxx %_DEST%\inc%_EXT%\basegfx\vector\b3ivector.hxx +..\inc\basegfx\vector\b3isize.hxx %_DEST%\inc%_EXT%\basegfx\vector\b3isize.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\curve +..\inc\basegfx\curve\b2dcubicbezier.hxx %_DEST%\inc%_EXT%\basegfx\curve\b2dcubicbezier.hxx +..\inc\basegfx\curve\b2dquadraticbezier.hxx %_DEST%\inc%_EXT%\basegfx\curve\b2dquadraticbezier.hxx +..\inc\basegfx\curve\b2dbeziertools.hxx %_DEST%\inc%_EXT%\basegfx\curve\b2dbeziertools.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\numeric +..\inc\basegfx\numeric\ftools.hxx %_DEST%\inc%_EXT%\basegfx\numeric\ftools.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\polygon +..\inc\basegfx\polygon\b2dpolygon.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dpolygon.hxx +..\inc\basegfx\polygon\b2dpolypolygon.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dpolypolygon.hxx +..\inc\basegfx\polygon\b2dpolypolygonfillrule.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dpolypolygonfillrule.hxx +..\inc\basegfx\polygon\b2dpolygontools.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dpolygontools.hxx +..\inc\basegfx\polygon\b2dpolypolygontools.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dpolypolygontools.hxx +..\inc\basegfx\polygon\b2dpolypolygonrasterconverter.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dpolypolygonrasterconverter.hxx +..\inc\basegfx\polygon\b2dlinegeometry.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dlinegeometry.hxx +..\inc\basegfx\polygon\b2dpolygonclipper.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dpolygonclipper.hxx +..\inc\basegfx\polygon\b2dpolygontriangulator.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dpolygontriangulator.hxx +..\inc\basegfx\polygon\b2dpolygoncutandtouch.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dpolygoncutandtouch.hxx +..\inc\basegfx\polygon\b2dpolypolygoncutter.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dpolypolygoncutter.hxx +..\inc\basegfx\polygon\b2dtrapezoid.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b2dtrapezoid.hxx +..\inc\basegfx\polygon\b3dpolygon.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b3dpolygon.hxx +..\inc\basegfx\polygon\b3dpolypolygon.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b3dpolypolygon.hxx +..\inc\basegfx\polygon\b3dpolygontools.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b3dpolygontools.hxx +..\inc\basegfx\polygon\b3dpolypolygontools.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b3dpolypolygontools.hxx +..\inc\basegfx\polygon\b3dpolygonclipper.hxx %_DEST%\inc%_EXT%\basegfx\polygon\b3dpolygonclipper.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\tuple +..\inc\basegfx\tuple\b2dtuple.hxx %_DEST%\inc%_EXT%\basegfx\tuple\b2dtuple.hxx +..\inc\basegfx\tuple\b3dtuple.hxx %_DEST%\inc%_EXT%\basegfx\tuple\b3dtuple.hxx +..\inc\basegfx\tuple\b2ituple.hxx %_DEST%\inc%_EXT%\basegfx\tuple\b2ituple.hxx +..\inc\basegfx\tuple\b3ituple.hxx %_DEST%\inc%_EXT%\basegfx\tuple\b3ituple.hxx +..\inc\basegfx\tuple\b2i64tuple.hxx %_DEST%\inc%_EXT%\basegfx\tuple\b2i64tuple.hxx +..\inc\basegfx\tuple\b3i64tuple.hxx %_DEST%\inc%_EXT%\basegfx\tuple\b3i64tuple.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\tools +..\inc\basegfx\tools\canvastools.hxx %_DEST%\inc%_EXT%\basegfx\tools\canvastools.hxx +..\inc\basegfx\tools\keystoplerp.hxx %_DEST%\inc%_EXT%\basegfx\tools\keystoplerp.hxx +..\inc\basegfx\tools\lerp.hxx %_DEST%\inc%_EXT%\basegfx\tools\lerp.hxx +..\inc\basegfx\tools\unopolypolygon.hxx %_DEST%\inc%_EXT%\basegfx\tools\unopolypolygon.hxx +..\inc\basegfx\tools\b2dclipstate.hxx %_DEST%\inc%_EXT%\basegfx\tools\b2dclipstate.hxx +..\inc\basegfx\tools\rectcliptools.hxx %_DEST%\inc%_EXT%\basegfx\tools\rectcliptools.hxx +..\inc\basegfx\tools\tools.hxx %_DEST%\inc%_EXT%\basegfx\tools\tools.hxx +..\inc\basegfx\tools\gradienttools.hxx %_DEST%\inc%_EXT%\basegfx\tools\gradienttools.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\color +..\inc\basegfx\color\bcolor.hxx %_DEST%\inc%_EXT%\basegfx\color\bcolor.hxx +..\inc\basegfx\color\bcolortools.hxx %_DEST%\inc%_EXT%\basegfx\color\bcolortools.hxx +..\inc\basegfx\color\bcolormodifier.hxx %_DEST%\inc%_EXT%\basegfx\color\bcolormodifier.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\pixel +..\inc\basegfx\pixel\bpixel.hxx %_DEST%\inc%_EXT%\basegfx\pixel\bpixel.hxx +..\inc\basegfx\pixel\bzpixel.hxx %_DEST%\inc%_EXT%\basegfx\pixel\bzpixel.hxx + +mkdir: %_DEST%\inc%_EXT%\basegfx\raster +..\inc\basegfx\raster\bpixelraster.hxx %_DEST%\inc%_EXT%\basegfx\raster\bpixelraster.hxx +..\inc\basegfx\raster\bzpixelraster.hxx %_DEST%\inc%_EXT%\basegfx\raster\bzpixelraster.hxx +..\inc\basegfx\raster\rasterconvert3d.hxx %_DEST%\inc%_EXT%\basegfx\raster\rasterconvert3d.hxx diff --git a/basegfx/qa/mkpolygons.pl b/basegfx/qa/mkpolygons.pl new file mode 100644 index 000000000000..b465a4f845ab --- /dev/null +++ b/basegfx/qa/mkpolygons.pl @@ -0,0 +1,344 @@ +: +eval 'exec perl -wS $0 ${1+"$@"}' + if 0; + +# +# 2009 Copyright Novell, Inc. & Sun Microsystems, Inc. +# +# 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. +# + +use IO::File; +use Cwd; +use File::Spec; +use File::Spec::Functions; +use File::Temp; +use File::Path; + +$TempDir = ""; + + +# all the XML package generation is a blatant rip from AF's +# write-calc-doc.pl + + +############################################################################### +# Open a file with the given name. +# First it is checked if the temporary directory, in which all files for +# the document are gathered, is already present and create it if it is not. +# Then create the path to the file inside the temporary directory. +# Finally open the file and return a file handle to it. +# +sub open_file +{ + my $filename = pop @_; + + # Create base directory of temporary directory tree if not alreay + # present. + if ($TempDir eq "") + { + $TempDir = File::Temp::tempdir (CLEANUP => 1); + } + + # Create the path to the file. + my $fullname = File::Spec->catfile ($TempDir, $filename); + my ($volume,$directories,$file) = File::Spec->splitpath ($fullname); + mkpath (File::Spec->catpath ($volume,$directories,"")); + + # Open the file and return a file handle to it. + return new IO::File ($fullname, "w"); +} + + +############################################################################### +# Zip the files in the directory tree into the given file. +# +sub zip_dirtree +{ + my $filename = pop @_; + + my $cwd = getcwd; + my $zip_name = $filename; + + # We are about to change the directory. + # Therefore create an absolute pathname for the zip archive. + + # First transfer the drive from $cwd to $zip_name. This is a + # workaround for a bug in file_name_is_absolute which thinks + # the the path \bla is an absolute path under DOS. + my ($volume,$directories,$file) = File::Spec->splitpath ($zip_name); + my ($volume_cwd,$directories_cwd,$file_cwd) = File::Spec->splitpath ($cwd); + $volume = $volume_cwd if ($volume eq ""); + $zip_name = File::Spec->catpath ($volume,$directories,$file); + + # Add the current working directory to a relative path. + if ( ! file_name_is_absolute ($zip_name)) + { + $zip_name = File::Spec->catfile ($cwd, $zip_name); + + # Try everything to clean up the name. + $zip_name = File::Spec->rel2abs ($filename); + $zip_name = File::Spec->canonpath ($zip_name); + + # Remove .. directories from the middle of the path. + while ($zip_name =~ /\/[^\/][^\.\/][^\/]*\/\.\.\//) + { + $zip_name = $` . "/" . $'; + } + } + + # Just in case the zip program gets confused by an existing file with the + # same name as the one to be written that file is removed first. + if ( -e $filename) + { + if (unlink ($filename) == 0) + { + print "Existing file $filename could not be deleted.\n"; + print "Please close the application that uses it, then try again.\n"; + return; + } + } + + # Finally create the zip file. First change into the temporary directory + # so that the resulting zip file contains only paths relative to it. + print "zipping [$ZipCmd $ZipFlags $zip_name *]\n"; + chdir ($TempDir); + system ("$ZipCmd $ZipFlags $zip_name *"); + chdir ($cwd); +} + + +sub writeHeader +{ + print $OUT qq~<?xml version="1.0" encoding="UTF-8"?> + +<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:smil="urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" office:version="1.0"> + <office:scripts/> + <office:automatic-styles> + <style:style style:name="dp1" style:family="drawing-page"> + <style:drawing-page-properties presentation:background-visible="true" presentation:background-objects-visible="true" presentation:display-footer="true" presentation:display-page-number="false" presentation:display-date-time="true"/> + </style:style> + <style:style style:name="gr1" style:family="graphic" style:parent-style-name="standard"> + <style:graphic-properties draw:textarea-horizontal-align="center" draw:fill="none" draw:stroke="none" draw:textarea-vertical-align="middle"/> + </style:style> + <style:style style:name="gr2" style:family="graphic" style:parent-style-name="standard"> + <style:graphic-properties draw:textarea-horizontal-align="center" draw:textarea-vertical-align="middle"/> + </style:style> + <style:style style:name="pr1" style:family="presentation" style:parent-style-name="Default-title"> + <style:graphic-properties draw:fill-color="#ffffff" draw:auto-grow-height="true" fo:min-height="3.508cm"/> + </style:style> + <style:style style:name="pr2" style:family="presentation" style:parent-style-name="Default-notes"> + <style:graphic-properties draw:fill-color="#ffffff" draw:auto-grow-height="true" fo:min-height="13.367cm"/> + </style:style> + <style:style style:name="P1" style:family="paragraph"> + <style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-indent="0cm"/> + </style:style> + <style:style style:name="P2" style:family="paragraph"> + <style:paragraph-properties fo:margin-left="0.6cm" fo:margin-right="0cm" fo:text-indent="-0.6cm"/> + </style:style> + <text:list-style style:name="L1"> + <text:list-level-style-bullet text:level="1" text:bullet-char="●"> + <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/> + </text:list-level-style-bullet> + <text:list-level-style-bullet text:level="2" text:bullet-char="●"> + <style:list-level-properties text:space-before="0.6cm" text:min-label-width="0.6cm"/> + <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/> + </text:list-level-style-bullet> + <text:list-level-style-bullet text:level="3" text:bullet-char="●"> + <style:list-level-properties text:space-before="1.2cm" text:min-label-width="0.6cm"/> + <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/> + </text:list-level-style-bullet> + <text:list-level-style-bullet text:level="4" text:bullet-char="●"> + <style:list-level-properties text:space-before="1.8cm" text:min-label-width="0.6cm"/> + <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/> + </text:list-level-style-bullet> + <text:list-level-style-bullet text:level="5" text:bullet-char="●"> + <style:list-level-properties text:space-before="2.4cm" text:min-label-width="0.6cm"/> + <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/> + </text:list-level-style-bullet> + <text:list-level-style-bullet text:level="6" text:bullet-char="●"> + <style:list-level-properties text:space-before="3cm" text:min-label-width="0.6cm"/> + <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/> + </text:list-level-style-bullet> + <text:list-level-style-bullet text:level="7" text:bullet-char="●"> + <style:list-level-properties text:space-before="3.6cm" text:min-label-width="0.6cm"/> + <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/> + </text:list-level-style-bullet> + <text:list-level-style-bullet text:level="8" text:bullet-char="●"> + <style:list-level-properties text:space-before="4.2cm" text:min-label-width="0.6cm"/> + <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/> + </text:list-level-style-bullet> + <text:list-level-style-bullet text:level="9" text:bullet-char="●"> + <style:list-level-properties text:space-before="4.8cm" text:min-label-width="0.6cm"/> + <style:text-properties fo:font-family="StarSymbol" style:use-window-font-color="true" fo:font-size="45%"/> + </text:list-level-style-bullet> + </text:list-style> + </office:automatic-styles> + <office:body> + <office:presentation> +~; + +} + +sub writeSlideHeader +{ + my $titleText = pop @_; + my $slideNum = pop @_; + + print $OUT " <draw:page draw:name=\"page1\" draw:style-name=\"dp1\" draw:master-page-name=\"Default\">\n"; + print $OUT " <office:forms form:automatic-focus=\"false\" form:apply-design-mode=\"false\"/>\n"; + print $OUT " <draw:rect draw:style-name=\"gr1\" draw:text-style-name=\"P1\" draw:id=\"id$slideNum\" draw:layer=\"layout\" svg:width=\"17.5cm\" svg:height=\"6cm\" svg:x=\"5cm\" svg:y=\"4cm\">\n"; + print $OUT " <text:p text:style-name=\"P2\">Slide: $slideNum</text:p>\n"; + print $OUT " <text:p text:style-name=\"P2\">Path: $titleText</text:p>\n"; + print $OUT " </draw:rect>\n"; +} + + +sub writeSlideFooter +{ + print $OUT " <presentation:notes draw:style-name=\"dp1\">\n"; + print $OUT " <draw:page-thumbnail draw:style-name=\"gr1\" draw:layer=\"layout\" svg:width=\"14.851cm\" svg:height=\"11.138cm\" svg:x=\"3.068cm\" svg:y=\"2.257cm\" draw:page-number=\"1\" presentation:class=\"page\"/>\n"; + print $OUT " <draw:frame presentation:style-name=\"pr3\" draw:layer=\"layout\" svg:width=\"16.79cm\" svg:height=\"13.116cm\" svg:x=\"2.098cm\" svg:y=\"14.109cm\" presentation:class=\"notes\" presentation:placeholder=\"true\">\n"; + print $OUT " <draw:text-box/>\n"; + print $OUT " </draw:frame>\n"; + print $OUT " </presentation:notes>\n"; + print $OUT " </draw:page>\n"; +} + +sub writeFooter +{ + print $OUT qq~ <presentation:settings presentation:full-screen="false"/> + </office:presentation> + </office:body> +</office:document-content> +~; + +} + +sub writePath +{ + my $pathAry = pop @_; + my $path = $pathAry->[1]; + my $viewBox = $pathAry->[0]; + + print $OUT " <draw:path draw:style-name=\"gr2\" draw:text-style-name=\"P1\" draw:layer=\"layout\" svg:width=\"10cm\" svg:height=\"10cm\" svg:x=\"5cm\" svg:y=\"5cm\" svg:viewBox=\""; + print $OUT $viewBox; + print $OUT "\" svg:d=\""; + print $OUT $path; + print $OUT "\">\n"; + print $OUT " <text:p/>\n"; + print $OUT " </draw:path>\n"; +} + +sub writeManifest +{ + my $outFile = open_file("META-INF/manifest.xml"); + + print $outFile qq~<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE manifest:manifest PUBLIC "-//OpenOffice.org//DTD Manifest 1.0//EN" "Manifest.dtd"> +<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"> + <manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.presentation" manifest:full-path="/"/> + <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/> +</manifest:manifest> +~; + + $outFile->close; +} + + +############################################################################### +# Print usage information. +# +sub usage () +{ + print <<END_OF_USAGE; +usage: $0 <option>* [<SvgD-values>] + +output-file-name defaults to polygons.odp. + + -h Print this usage information. + -o output-file-name +END_OF_USAGE +} + +############################################################################### +# Process the command line. +# +sub process_command_line +{ + foreach (@ARGV) + { + if (/^-h/) + { + usage; + exit 0; + } + } + + $global_output_name = "polygons.odp"; + my $j = 0, $noMoreOptions = 0; + for (my $i=0; $i<$#ARGV; $i++) + { + if ( !$noMoreOptions and $ARGV[$i] eq "-o") + { + $i++; + $global_output_name = $ARGV[$i]; + } + elsif ( !$noMoreOptions and $ARGV[$i] eq "--") + { + $noMoreOptions = 1; + } + elsif ( !$noMoreOptions and $ARGV[$i] =~ /^-/) + { + print "Unknown option $ARGV[$i]\n"; + usage; + exit 1; + } + else + { + push(@paths, [$ARGV[$i],$ARGV[$i+1]]); + $i++; + } + } + + print "output to $global_output_name\n"; +} + +############################################################################### +# Main +############################################################################### + +$ZipCmd = $ENV{LOG_FILE_ZIP_CMD}; +$ZipFlags = $ENV{LOG_FILE_ZIP_FLAGS}; +# Provide default values for the zip command and it's flags. +if ( ! defined $ZipCmd) +{ + $ZipCmd = "zip" unless defined $ZipCmd; + $ZipFlags = "-r -q" unless defined $ZipFlags; +} + +process_command_line(); + +writeManifest(); + +$OUT = open_file( "content.xml" ); + +writeHeader(); + +$pathNum=0; +foreach $path (@paths) +{ + writeSlideHeader($pathNum, $path->[1]); + writePath($path); + writeSlideFooter(); + $pathNum++; +} + +writeFooter(); + +$OUT->close; + +zip_dirtree ($global_output_name); + diff --git a/basegfx/source/color/bcolor.cxx b/basegfx/source/color/bcolor.cxx new file mode 100644 index 000000000000..6e5b4c985e6d --- /dev/null +++ b/basegfx/source/color/bcolor.cxx @@ -0,0 +1,40 @@ +/************************************************************************* + * + * 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/color/bcolor.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/color/bcolormodifier.cxx b/basegfx/source/color/bcolormodifier.cxx new file mode 100644 index 000000000000..15662c44414c --- /dev/null +++ b/basegfx/source/color/bcolormodifier.cxx @@ -0,0 +1,72 @@ +/************************************************************************* + * + * 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/color/bcolormodifier.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + ::basegfx::BColor BColorModifier::getModifiedColor(const ::basegfx::BColor& aSourceColor) const + { + switch(meMode) + { + case BCOLORMODIFYMODE_INTERPOLATE : + { + return interpolate(maBColor, aSourceColor, mfValue); + } + case BCOLORMODIFYMODE_GRAY : + { + const double fLuminance(aSourceColor.luminance()); + return ::basegfx::BColor(fLuminance, fLuminance, fLuminance); + } + case BCOLORMODIFYMODE_BLACKANDWHITE : + { + const double fLuminance(aSourceColor.luminance()); + + if(fLuminance < mfValue) + { + return ::basegfx::BColor::getEmptyBColor(); + } + else + { + return ::basegfx::BColor(1.0, 1.0, 1.0); + } + } + default : // BCOLORMODIFYMODE_REPLACE + { + return maBColor; + } + } + } +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/color/bcolortools.cxx b/basegfx/source/color/bcolortools.cxx new file mode 100644 index 000000000000..543097de3d77 --- /dev/null +++ b/basegfx/source/color/bcolortools.cxx @@ -0,0 +1,268 @@ +/************************************************************************* + * + * 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/color/bcolor.hxx> +#include <basegfx/color/bcolortools.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx { namespace tools +{ + BColor rgb2hsl(const BColor& rRGBColor) + { + const double r=rRGBColor.getRed(), g=rRGBColor.getGreen(), b=rRGBColor.getBlue(); + const double minVal = ::std::min( ::std::min( r, g ), b ); + const double maxVal = ::std::max( ::std::max( r, g ), b ); + const double d = maxVal - minVal; + + double h=0, s=0, l=0; + + l = (maxVal + minVal) / 2.0; + + if( ::basegfx::fTools::equalZero(d) ) + { + s = h = 0; // hue undefined (achromatic case) + } + else + { + s = l > 0.5 ? d/(2.0-maxVal-minVal) : + d/(maxVal + minVal); + + if( r == maxVal ) + h = (g - b)/d; + else if( g == maxVal ) + h = 2.0 + (b - r)/d; + else + h = 4.0 + (r - g)/d; + + h *= 60.0; + + if( h < 0.0 ) + h += 360.0; + } + + return BColor(h,s,l); + } + + static inline double hsl2rgbHelper( double nValue1, double nValue2, double nHue ) + { + // clamp hue to [0,360] + nHue = fmod( nHue, 360.0 ); + + // cope with wrap-arounds + if( nHue < 0.0 ) + nHue += 360.0; + + if( nHue < 60.0 ) + return nValue1 + (nValue2 - nValue1)*nHue/60.0; + else if( nHue < 180.0 ) + return nValue2; + else if( nHue < 240.0 ) + return nValue1 + (nValue2 - nValue1)*(240.0 - nHue)/60.0; + else + return nValue1; + } + + BColor hsl2rgb(const BColor& rHSLColor) + { + const double h=rHSLColor.getRed(), s=rHSLColor.getGreen(), l=rHSLColor.getBlue(); + + if( fTools::equalZero(s) ) + return BColor(l, l, l ); // achromatic case + + const double nVal1( l <= 0.5 ? l*(1.0 + s) : l + s - l*s ); + const double nVal2( 2.0*l - nVal1 ); + + return BColor( + hsl2rgbHelper(nVal2, + nVal1, + h + 120.0), + hsl2rgbHelper(nVal2, + nVal1, + h), + hsl2rgbHelper(nVal2, + nVal1, + h - 120.0) ); + } + + BColor rgb2hsv(const BColor& rRGBColor) + { + const double r=rRGBColor.getRed(), g=rRGBColor.getGreen(), b=rRGBColor.getBlue(); + const double maxVal = std::max(std::max(r,g),b); + const double minVal = std::min(std::min(r,g),b); + const double delta = maxVal-minVal; + + double h=0, s=0, v=0; + + v = maxVal; + if( fTools::equalZero(v) ) + s = 0; + else + s = delta / v; + + if( !fTools::equalZero(s) ) + { + if( maxVal == r ) + { + h = (g - b) / delta; + } + else if( maxVal == g ) + { + h = 2.0 + (b - r) / delta; + } + else + { + h = 4.0 + (r - g) / delta; + } + + h *= 60.0; + + if( h < 0 ) + h += 360; + } + + return BColor(h,s,v); + } + + BColor hsv2rgb(const BColor& rHSVColor) + { + double h=rHSVColor.getRed(); + const double s=rHSVColor.getGreen(), v=rHSVColor.getBlue(); + + if( fTools::equalZero(s) ) + { + // achromatic case: no hue. + return BColor(v,v,v); + } + else + { + if( fTools::equal(h,360) ) + h = 0; // 360 degrees is equivalent to 0 degrees + + h /= 60.0; + const sal_Int32 intval = static_cast< sal_Int32 >( h ); + const double f = h - intval; + const double p = v*(1.0-s); + const double q = v*(1.0-(s*f)); + const double t = v*(1.0-(s*(1.0-f))); + + /* which hue area? */ + switch( intval ) + { + case 0: + return BColor(v,t,p); + + case 1: + return BColor(q,v,p); + + case 2: + return BColor(p,v,t); + + case 3: + return BColor(p,q,v); + + case 4: + return BColor(t,p,v); + + case 5: + return BColor(v,p,q); + + default: + // hue overflow + return BColor(); + } + } + } + + BColor rgb2yiq(const BColor& rRGBColor) + { + // from Foley, van Dam, Computer Graphics + const double r=rRGBColor.getRed(), g=rRGBColor.getGreen(), b=rRGBColor.getBlue(); + return BColor( + 0.299*r + 0.587*g + 0.114*b, + 0.596*r - 0.274*g - 0.322*b, + 0.211*r - 0.522*g + 0.311*b); + } + + BColor yiq2rgb(const BColor& rYIQColor) + { + // from Foley, van Dam, Computer Graphics + const double y=rYIQColor.getRed(), i=rYIQColor.getGreen(), q=rYIQColor.getBlue(); + return BColor( + y + 0.956*i + 0.623*q, + y - 0.272*i - 0.648*q, + y - 1.105*i + 1.705*q ); + } + + BColor ciexyz2rgb( const BColor& rXYZColor ) + { + // from Poynton color faq, and SMPTE RP 177-1993, Derivation + // of Basic Television Color Equations + const double x=rXYZColor.getRed(), y=rXYZColor.getGreen(), z=rXYZColor.getBlue(); + return BColor( + 3.240479*x - 1.53715*y - 0.498535*z, + -0.969256*x + 1.875991*y + 0.041556*z, + 0.055648*x - 0.204043*y + 1.057311*z ); + } + + BColor rgb2ciexyz( const BColor& rRGBColor ) + { + // from Poynton color faq, and SMPTE RP 177-1993, Derivation + // of Basic Television Color Equations + const double r=rRGBColor.getRed(), g=rRGBColor.getGreen(), b=rRGBColor.getBlue(); + return BColor( + 0.412453*r + 0.35758*g + 0.180423*b, + 0.212671*r + 0.71516*g + 0.072169*b, + 0.019334*r + 0.119193*g + 0.950227*b); + } + + BColor rgb2ypbpr(const BColor& rRGBColor) + { + const double r=rRGBColor.getRed(), g=rRGBColor.getGreen(), b=rRGBColor.getBlue(); + return BColor( + 0.299*r + 0.587*g + 0.114*b, + -0.168736*r - 0.331264*g + 0.5*b, + 0.5*r - 0.418688*g - 0.081312*b); + } + + BColor ypbpr2rgb(const BColor& rYPbPrColor) + { + const double y=rYPbPrColor.getRed(), pb=rYPbPrColor.getGreen(), pr=rYPbPrColor.getBlue(); + return BColor( + 1.*y + 0.*pb + 1.402*pr, + 1.*y - 0.344136*pb - 0.714136*pr, + 1.*y + 1.772*pb + 0.*pr); + } + +} } // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/color/makefile.mk b/basegfx/source/color/makefile.mk new file mode 100644 index 000000000000..c4e842db72ae --- /dev/null +++ b/basegfx/source/color/makefile.mk @@ -0,0 +1,49 @@ +#************************************************************************* +# +# 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=color + +#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb +#ENABLE_EXCEPTIONS=FALSE +#USE_DEFFILE=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= \ + $(SLO)$/bcolor.obj \ + $(SLO)$/bcolortools.obj \ + $(SLO)$/bcolormodifier.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/curve/b2dbeziertools.cxx b/basegfx/source/curve/b2dbeziertools.cxx new file mode 100644 index 000000000000..eddd0b281fc2 --- /dev/null +++ b/basegfx/source/curve/b2dbeziertools.cxx @@ -0,0 +1,163 @@ +/************************************************************************* + * + * 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/curve/b2dbeziertools.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <algorithm> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + B2DCubicBezierHelper::B2DCubicBezierHelper(const B2DCubicBezier& rBase, sal_uInt32 nDivisions) + : maLengthArray(), + mnEdgeCount(0) + { + const bool bIsBezier(rBase.isBezier()); + + if(bIsBezier) + { + // check nDivisions; at least one is needed, but also prevent too big values + if(nDivisions < 1) + { + nDivisions = 1; + } + else if(nDivisions > 1000) + { + nDivisions = 1000; + } + + // set nEdgeCount + mnEdgeCount = nDivisions + 1; + + // fill in maLengthArray + maLengthArray.clear(); + maLengthArray.reserve(mnEdgeCount); + B2DPoint aCurrent(rBase.getStartPoint()); + double fLength(0.0); + + for(sal_uInt32 a(1);;) + { + const B2DPoint aNext(rBase.interpolatePoint((double)a / (double)mnEdgeCount)); + const B2DVector aEdge(aNext - aCurrent); + + fLength += aEdge.getLength(); + maLengthArray.push_back(fLength); + + if(++a < mnEdgeCount) + { + aCurrent = aNext; + } + else + { + const B2DPoint aLastNext(rBase.getEndPoint()); + const B2DVector aLastEdge(aLastNext - aNext); + + fLength += aLastEdge.getLength(); + maLengthArray.push_back(fLength); + break; + } + } + } + else + { + maLengthArray.clear(); + maLengthArray.push_back(rBase.getEdgeLength()); + mnEdgeCount = 1; + } + } + + double B2DCubicBezierHelper::distanceToRelative(double fDistance) const + { + if(fDistance <= 0.0) + { + return 0.0; + } + + const double fLength(getLength()); + + if(fTools::moreOrEqual(fDistance, fLength)) + { + return 1.0; + } + + // fDistance is in ]0.0 .. fLength[ + + if(1 == mnEdgeCount) + { + // not a bezier, linear edge + return fDistance / fLength; + } + + // it is a bezier + ::std::vector< double >::const_iterator aIter = ::std::lower_bound(maLengthArray.begin(), maLengthArray.end(), fDistance); + const sal_uInt32 nIndex(aIter - maLengthArray.begin()); + const double fHighBound(maLengthArray[nIndex]); + const double fLowBound(nIndex ? maLengthArray[nIndex - 1] : 0.0); + const double fLinearInterpolatedLength((fDistance - fLowBound) / (fHighBound - fLowBound)); + + return (static_cast< double >(nIndex) + fLinearInterpolatedLength) / static_cast< double >(mnEdgeCount); + } + + double B2DCubicBezierHelper::relativeToDistance(double fRelative) const + { + if(fRelative <= 0.0) + { + return 0.0; + } + + const double fLength(getLength()); + + if(fTools::moreOrEqual(fRelative, 1.0)) + { + return fLength; + } + + // fRelative is in ]0.0 .. 1.0[ + + if(1 == mnEdgeCount) + { + // not a bezier, linear edge + return fRelative * fLength; + } + + // fRelative is in ]0.0 .. 1.0[ + const double fIndex(fRelative * static_cast< double >(mnEdgeCount)); + double fIntIndex; + const double fFractIndex(modf(fIndex, &fIntIndex)); + const sal_uInt32 nIntIndex(static_cast< sal_uInt32 >(fIntIndex)); + const double fStartDistance(nIntIndex ? maLengthArray[nIntIndex - 1] : 0.0); + + return fStartDistance + ((maLengthArray[nIntIndex] - fStartDistance) * fFractIndex); + } +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +// eof diff --git a/basegfx/source/curve/b2dcubicbezier.cxx b/basegfx/source/curve/b2dcubicbezier.cxx new file mode 100644 index 000000000000..adec8a2ac6ca --- /dev/null +++ b/basegfx/source/curve/b2dcubicbezier.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/curve/b2dcubicbezier.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <limits> + +// #i37443# +#define FACTOR_FOR_UNSHARPEN (1.6) +#ifdef DBG_UTIL +static double fMultFactUnsharpen = FACTOR_FOR_UNSHARPEN; +#endif + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace + { + void ImpSubDivAngle( + const B2DPoint& rfPA, // start point + const B2DPoint& rfEA, // edge on A + const B2DPoint& rfEB, // edge on B + const B2DPoint& rfPB, // end point + B2DPolygon& rTarget, // target polygon + double fAngleBound, // angle bound in [0.0 .. 2PI] + bool bAllowUnsharpen, // #i37443# allow the criteria to get unsharp in recursions + sal_uInt16 nMaxRecursionDepth) // endless loop protection + { + if(nMaxRecursionDepth) + { + // do angle test + B2DVector aLeft(rfEA - rfPA); + B2DVector aRight(rfEB - rfPB); + + // #i72104# + if(aLeft.equalZero()) + { + aLeft = rfEB - rfPA; + } + + if(aRight.equalZero()) + { + aRight = rfEA - rfPB; + } + + const double fCurrentAngle(aLeft.angle(aRight)); + + if(fabs(fCurrentAngle) > (F_PI - fAngleBound)) + { + // end recursion + nMaxRecursionDepth = 0; + } + else + { + if(bAllowUnsharpen) + { + // #i37443# unsharpen criteria +#ifdef DBG_UTIL + fAngleBound *= fMultFactUnsharpen; +#else + fAngleBound *= FACTOR_FOR_UNSHARPEN; +#endif + } + } + } + + if(nMaxRecursionDepth) + { + // divide at 0.5 + const B2DPoint aS1L(average(rfPA, rfEA)); + const B2DPoint aS1C(average(rfEA, rfEB)); + const B2DPoint aS1R(average(rfEB, rfPB)); + const B2DPoint aS2L(average(aS1L, aS1C)); + const B2DPoint aS2R(average(aS1C, aS1R)); + const B2DPoint aS3C(average(aS2L, aS2R)); + + // left recursion + ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1); + + // right recursion + ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1); + } + else + { + rTarget.append(rfPB); + } + } + + void ImpSubDivAngleStart( + const B2DPoint& rfPA, // start point + const B2DPoint& rfEA, // edge on A + const B2DPoint& rfEB, // edge on B + const B2DPoint& rfPB, // end point + B2DPolygon& rTarget, // target polygon + const double& rfAngleBound, // angle bound in [0.0 .. 2PI] + bool bAllowUnsharpen) // #i37443# allow the criteria to get unsharp in recursions + { + sal_uInt16 nMaxRecursionDepth(8); + const B2DVector aLeft(rfEA - rfPA); + const B2DVector aRight(rfEB - rfPB); + bool bLeftEqualZero(aLeft.equalZero()); + bool bRightEqualZero(aRight.equalZero()); + bool bAllParallel(false); + + if(bLeftEqualZero && bRightEqualZero) + { + nMaxRecursionDepth = 0; + } + else + { + const B2DVector aBase(rfPB - rfPA); + const bool bBaseEqualZero(aBase.equalZero()); // #i72104# + + if(!bBaseEqualZero) + { + const bool bLeftParallel(bLeftEqualZero ? true : areParallel(aLeft, aBase)); + const bool bRightParallel(bRightEqualZero ? true : areParallel(aRight, aBase)); + + if(bLeftParallel && bRightParallel) + { + bAllParallel = true; + + if(!bLeftEqualZero) + { + double fFactor; + + if(fabs(aBase.getX()) > fabs(aBase.getY())) + { + fFactor = aLeft.getX() / aBase.getX(); + } + else + { + fFactor = aLeft.getY() / aBase.getY(); + } + + if(fFactor >= 0.0 && fFactor <= 1.0) + { + bLeftEqualZero = true; + } + } + + if(!bRightEqualZero) + { + double fFactor; + + if(fabs(aBase.getX()) > fabs(aBase.getY())) + { + fFactor = aRight.getX() / -aBase.getX(); + } + else + { + fFactor = aRight.getY() / -aBase.getY(); + } + + if(fFactor >= 0.0 && fFactor <= 1.0) + { + bRightEqualZero = true; + } + } + + if(bLeftEqualZero && bRightEqualZero) + { + nMaxRecursionDepth = 0; + } + } + } + } + + if(nMaxRecursionDepth) + { + // divide at 0.5 ad test both edges for angle criteria + const B2DPoint aS1L(average(rfPA, rfEA)); + const B2DPoint aS1C(average(rfEA, rfEB)); + const B2DPoint aS1R(average(rfEB, rfPB)); + const B2DPoint aS2L(average(aS1L, aS1C)); + const B2DPoint aS2R(average(aS1C, aS1R)); + const B2DPoint aS3C(average(aS2L, aS2R)); + + // test left + bool bAngleIsSmallerLeft(bAllParallel && bLeftEqualZero); + if(!bAngleIsSmallerLeft) + { + const B2DVector aLeftLeft(bLeftEqualZero ? aS2L - aS1L : aS1L - rfPA); // #i72104# + const B2DVector aRightLeft(aS2L - aS3C); + const double fCurrentAngleLeft(aLeftLeft.angle(aRightLeft)); + bAngleIsSmallerLeft = (fabs(fCurrentAngleLeft) > (F_PI - rfAngleBound)); + } + + // test right + bool bAngleIsSmallerRight(bAllParallel && bRightEqualZero); + if(!bAngleIsSmallerRight) + { + const B2DVector aLeftRight(aS2R - aS3C); + const B2DVector aRightRight(bRightEqualZero ? aS2R - aS1R : aS1R - rfPB); // #i72104# + const double fCurrentAngleRight(aLeftRight.angle(aRightRight)); + bAngleIsSmallerRight = (fabs(fCurrentAngleRight) > (F_PI - rfAngleBound)); + } + + if(bAngleIsSmallerLeft && bAngleIsSmallerRight) + { + // no recursion necessary at all + nMaxRecursionDepth = 0; + } + else + { + // left + if(bAngleIsSmallerLeft) + { + rTarget.append(aS3C); + } + else + { + ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, rfAngleBound, bAllowUnsharpen, nMaxRecursionDepth); + } + + // right + if(bAngleIsSmallerRight) + { + rTarget.append(rfPB); + } + else + { + ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, rfAngleBound, bAllowUnsharpen, nMaxRecursionDepth); + } + } + } + + if(!nMaxRecursionDepth) + { + rTarget.append(rfPB); + } + } + + void ImpSubDivDistance( + const B2DPoint& rfPA, // start point + const B2DPoint& rfEA, // edge on A + const B2DPoint& rfEB, // edge on B + const B2DPoint& rfPB, // end point + B2DPolygon& rTarget, // target polygon + double fDistanceBound2, // quadratic distance criteria + double fLastDistanceError2, // the last quadratic distance error + sal_uInt16 nMaxRecursionDepth) // endless loop protection + { + if(nMaxRecursionDepth) + { + // decide if another recursion is needed. If not, set + // nMaxRecursionDepth to zero + + // Perform bezier flatness test (lecture notes from R. Schaback, + // Mathematics of Computer-Aided Design, Uni Goettingen, 2000) + // + // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)|| + // 0<=j<=n + // + // What is calculated here is an upper bound to the distance from + // a line through b_0 and b_3 (rfPA and P4 in our notation) and the + // curve. We can drop 0 and n from the running indices, since the + // argument of max becomes zero for those cases. + const double fJ1x(rfEA.getX() - rfPA.getX() - 1.0/3.0*(rfPB.getX() - rfPA.getX())); + const double fJ1y(rfEA.getY() - rfPA.getY() - 1.0/3.0*(rfPB.getY() - rfPA.getY())); + const double fJ2x(rfEB.getX() - rfPA.getX() - 2.0/3.0*(rfPB.getX() - rfPA.getX())); + const double fJ2y(rfEB.getY() - rfPA.getY() - 2.0/3.0*(rfPB.getY() - rfPA.getY())); + const double fDistanceError2(::std::max(fJ1x*fJ1x + fJ1y*fJ1y, fJ2x*fJ2x + fJ2y*fJ2y)); + + // stop if error measure does not improve anymore. This is a + // safety guard against floating point inaccuracies. + // stop if distance from line is guaranteed to be bounded by d + const bool bFurtherDivision(fLastDistanceError2 > fDistanceError2 && fDistanceError2 >= fDistanceBound2); + + if(bFurtherDivision) + { + // remember last error value + fLastDistanceError2 = fDistanceError2; + } + else + { + // stop recustion + nMaxRecursionDepth = 0; + } + } + + if(nMaxRecursionDepth) + { + // divide at 0.5 + const B2DPoint aS1L(average(rfPA, rfEA)); + const B2DPoint aS1C(average(rfEA, rfEB)); + const B2DPoint aS1R(average(rfEB, rfPB)); + const B2DPoint aS2L(average(aS1L, aS1C)); + const B2DPoint aS2R(average(aS1C, aS1R)); + const B2DPoint aS3C(average(aS2L, aS2R)); + + // left recursion + ImpSubDivDistance(rfPA, aS1L, aS2L, aS3C, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1); + + // right recursion + ImpSubDivDistance(aS3C, aS2R, aS1R, rfPB, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1); + } + else + { + rTarget.append(rfPB); + } + } + } // end of anonymous namespace +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + B2DCubicBezier::B2DCubicBezier(const B2DCubicBezier& rBezier) + : maStartPoint(rBezier.maStartPoint), + maEndPoint(rBezier.maEndPoint), + maControlPointA(rBezier.maControlPointA), + maControlPointB(rBezier.maControlPointB) + { + } + + B2DCubicBezier::B2DCubicBezier() + { + } + + B2DCubicBezier::B2DCubicBezier(const B2DPoint& rStart, const B2DPoint& rEnd) + : maStartPoint(rStart), + maEndPoint(rEnd), + maControlPointA(rStart), + maControlPointB(rEnd) + { + } + + B2DCubicBezier::B2DCubicBezier(const B2DPoint& rStart, const B2DPoint& rControlPointA, const B2DPoint& rControlPointB, const B2DPoint& rEnd) + : maStartPoint(rStart), + maEndPoint(rEnd), + maControlPointA(rControlPointA), + maControlPointB(rControlPointB) + { + } + + B2DCubicBezier::~B2DCubicBezier() + { + } + + // assignment operator + B2DCubicBezier& B2DCubicBezier::operator=(const B2DCubicBezier& rBezier) + { + maStartPoint = rBezier.maStartPoint; + maEndPoint = rBezier.maEndPoint; + maControlPointA = rBezier.maControlPointA; + maControlPointB = rBezier.maControlPointB; + + return *this; + } + + // compare operators + bool B2DCubicBezier::operator==(const B2DCubicBezier& rBezier) const + { + return ( + maStartPoint == rBezier.maStartPoint + && maEndPoint == rBezier.maEndPoint + && maControlPointA == rBezier.maControlPointA + && maControlPointB == rBezier.maControlPointB + ); + } + + bool B2DCubicBezier::operator!=(const B2DCubicBezier& rBezier) const + { + return ( + maStartPoint != rBezier.maStartPoint + || maEndPoint != rBezier.maEndPoint + || maControlPointA != rBezier.maControlPointA + || maControlPointB != rBezier.maControlPointB + ); + } + + bool B2DCubicBezier::equal(const B2DCubicBezier& rBezier) const + { + return ( + maStartPoint.equal(rBezier.maStartPoint) + && maEndPoint.equal(rBezier.maEndPoint) + && maControlPointA.equal(rBezier.maControlPointA) + && maControlPointB.equal(rBezier.maControlPointB) + ); + } + + // test if vectors are used + bool B2DCubicBezier::isBezier() const + { + if(maControlPointA != maStartPoint || maControlPointB != maEndPoint) + { + return true; + } + + return false; + } + + void B2DCubicBezier::testAndSolveTrivialBezier() + { + if(maControlPointA != maStartPoint || maControlPointB != maEndPoint) + { + const B2DVector aEdge(maEndPoint - maStartPoint); + + // controls parallel to edge can be trivial. No edge -> not parallel -> control can + // still not be trivial (e.g. ballon loop) + if(!aEdge.equalZero()) + { + // get control vectors + const B2DVector aVecA(maControlPointA - maStartPoint); + const B2DVector aVecB(maControlPointB - maEndPoint); + + // check if trivial per se + bool bAIsTrivial(aVecA.equalZero()); + bool bBIsTrivial(aVecB.equalZero()); + + // #i102241# prepare inverse edge length to normalize cross values; + // else the small compare value used in fTools::equalZero + // will be length dependent and this detection will work as less + // precise as longer the edge is. In principle, the length of the control + // vector would need to be used too, but to be trivial it is assumed to + // be of roughly equal length to the edge, so edge length can be used + // for both. Only needed when one of both is not trivial per se. + const double fInverseEdgeLength(bAIsTrivial && bBIsTrivial + ? 1.0 + : 1.0 / aEdge.getLength()); + + // if A is not zero, check if it could be + if(!bAIsTrivial) + { + // #i102241# parallel to edge? Check aVecA, aEdge. Use cross() which does what + // we need here with the precision we need + const double fCross(aVecA.cross(aEdge) * fInverseEdgeLength); + + if(fTools::equalZero(fCross)) + { + // get scale to edge. Use bigger distance for numeric quality + const double fScale(fabs(aEdge.getX()) > fabs(aEdge.getY()) + ? aVecA.getX() / aEdge.getX() + : aVecA.getY() / aEdge.getY()); + + // relative end point of vector in edge range? + if(fTools::moreOrEqual(fScale, 0.0) && fTools::lessOrEqual(fScale, 1.0)) + { + bAIsTrivial = true; + } + } + } + + // if B is not zero, check if it could be, but only if A is already trivial; + // else solve to trivial will not be possible for whole edge + if(bAIsTrivial && !bBIsTrivial) + { + // parallel to edge? Check aVecB, aEdge + const double fCross(aVecB.cross(aEdge) * fInverseEdgeLength); + + if(fTools::equalZero(fCross)) + { + // get scale to edge. Use bigger distance for numeric quality + const double fScale(fabs(aEdge.getX()) > fabs(aEdge.getY()) + ? aVecB.getX() / aEdge.getX() + : aVecB.getY() / aEdge.getY()); + + // end point of vector in edge range? Caution: controlB is directed AGAINST edge + if(fTools::lessOrEqual(fScale, 0.0) && fTools::moreOrEqual(fScale, -1.0)) + { + bBIsTrivial = true; + } + } + } + + // if both are/can be reduced, do it. + // Not possible if only one is/can be reduced (!) + if(bAIsTrivial && bBIsTrivial) + { + maControlPointA = maStartPoint; + maControlPointB = maEndPoint; + } + } + } + } + + namespace { + double impGetLength(const B2DCubicBezier& rEdge, double fDeviation, sal_uInt32 nRecursionWatch) + { + const double fEdgeLength(rEdge.getEdgeLength()); + const double fControlPolygonLength(rEdge.getControlPolygonLength()); + const double fCurrentDeviation(fTools::equalZero(fControlPolygonLength) ? 0.0 : 1.0 - (fEdgeLength / fControlPolygonLength)); + + if(!nRecursionWatch || fTools:: lessOrEqual(fCurrentDeviation, fDeviation)) + { + return (fEdgeLength + fControlPolygonLength) * 0.5; + } + else + { + B2DCubicBezier aLeft, aRight; + const double fNewDeviation(fDeviation * 0.5); + const sal_uInt32 nNewRecursionWatch(nRecursionWatch - 1); + + rEdge.split(0.5, &aLeft, &aRight); + + return impGetLength(aLeft, fNewDeviation, nNewRecursionWatch) + + impGetLength(aRight, fNewDeviation, nNewRecursionWatch); + } + } + } + + double B2DCubicBezier::getLength(double fDeviation) const + { + if(isBezier()) + { + if(fDeviation < 0.00000001) + { + fDeviation = 0.00000001; + } + + return impGetLength(*this, fDeviation, 6); + } + else + { + return B2DVector(getEndPoint() - getStartPoint()).getLength(); + } + } + + double B2DCubicBezier::getEdgeLength() const + { + const B2DVector aEdge(maEndPoint - maStartPoint); + return aEdge.getLength(); + } + + double B2DCubicBezier::getControlPolygonLength() const + { + const B2DVector aVectorA(maControlPointA - maStartPoint); + const B2DVector aVectorB(maEndPoint - maControlPointB); + + if(!aVectorA.equalZero() || !aVectorB.equalZero()) + { + const B2DVector aTop(maControlPointB - maControlPointA); + return (aVectorA.getLength() + aVectorB.getLength() + aTop.getLength()); + } + else + { + return getEdgeLength(); + } + } + + void B2DCubicBezier::adaptiveSubdivideByAngle(B2DPolygon& rTarget, double fAngleBound, bool bAllowUnsharpen) const + { + if(isBezier()) + { + // use support method #i37443# and allow unsharpen the criteria + ImpSubDivAngleStart(maStartPoint, maControlPointA, maControlPointB, maEndPoint, rTarget, fAngleBound * F_PI180, bAllowUnsharpen); + } + else + { + rTarget.append(getEndPoint()); + } + } + + B2DVector B2DCubicBezier::getTangent(double t) const + { + if(fTools::lessOrEqual(t, 0.0)) + { + // tangent in start point + B2DVector aTangent(getControlPointA() - getStartPoint()); + + if(!aTangent.equalZero()) + { + return aTangent; + } + + // start point and control vector are the same, fallback + // to implicit start vector to control point B + aTangent = (getControlPointB() - getStartPoint()) * 0.3; + + if(!aTangent.equalZero()) + { + return aTangent; + } + + // not a bezier at all, return edge vector + return (getEndPoint() - getStartPoint()) * 0.3; + } + else if(fTools::moreOrEqual(t, 1.0)) + { + // tangent in end point + B2DVector aTangent(getEndPoint() - getControlPointB()); + + if(!aTangent.equalZero()) + { + return aTangent; + } + + // end point and control vector are the same, fallback + // to implicit start vector from control point A + aTangent = (getEndPoint() - getControlPointA()) * 0.3; + + if(!aTangent.equalZero()) + { + return aTangent; + } + + // not a bezier at all, return edge vector + return (getEndPoint() - getStartPoint()) * 0.3; + } + else + { + // t is in ]0.0 .. 1.0[. Split and extract + B2DCubicBezier aRight; + split(t, 0, &aRight); + + return aRight.getControlPointA() - aRight.getStartPoint(); + } + } + + // #i37443# adaptive subdivide by nCount subdivisions + void B2DCubicBezier::adaptiveSubdivideByCount(B2DPolygon& rTarget, sal_uInt32 nCount) const + { + const double fLenFact(1.0 / static_cast< double >(nCount + 1)); + + for(sal_uInt32 a(1); a <= nCount; a++) + { + const double fPos(static_cast< double >(a) * fLenFact); + rTarget.append(interpolatePoint(fPos)); + } + + rTarget.append(getEndPoint()); + } + + // adaptive subdivide by distance + void B2DCubicBezier::adaptiveSubdivideByDistance(B2DPolygon& rTarget, double fDistanceBound) const + { + if(isBezier()) + { + ImpSubDivDistance(maStartPoint, maControlPointA, maControlPointB, maEndPoint, rTarget, + fDistanceBound * fDistanceBound, ::std::numeric_limits<double>::max(), 30); + } + else + { + rTarget.append(getEndPoint()); + } + } + + B2DPoint B2DCubicBezier::interpolatePoint(double t) const + { + OSL_ENSURE(t >= 0.0 && t <= 1.0, "B2DCubicBezier::interpolatePoint: Access out of range (!)"); + + if(isBezier()) + { + const B2DPoint aS1L(interpolate(maStartPoint, maControlPointA, t)); + const B2DPoint aS1C(interpolate(maControlPointA, maControlPointB, t)); + const B2DPoint aS1R(interpolate(maControlPointB, maEndPoint, t)); + const B2DPoint aS2L(interpolate(aS1L, aS1C, t)); + const B2DPoint aS2R(interpolate(aS1C, aS1R, t)); + + return interpolate(aS2L, aS2R, t); + } + else + { + return interpolate(maStartPoint, maEndPoint, t); + } + } + + double B2DCubicBezier::getSmallestDistancePointToBezierSegment(const B2DPoint& rTestPoint, double& rCut) const + { + const sal_uInt32 nInitialDivisions(3L); + B2DPolygon aInitialPolygon; + + // as start make a fix division, creates nInitialDivisions + 2L points + aInitialPolygon.append(getStartPoint()); + adaptiveSubdivideByCount(aInitialPolygon, nInitialDivisions); + + // now look for the closest point + const sal_uInt32 nPointCount(aInitialPolygon.count()); + B2DVector aVector(rTestPoint - aInitialPolygon.getB2DPoint(0L)); + double fQuadDist(aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY()); + double fNewQuadDist; + sal_uInt32 nSmallestIndex(0L); + + for(sal_uInt32 a(1L); a < nPointCount; a++) + { + aVector = B2DVector(rTestPoint - aInitialPolygon.getB2DPoint(a)); + fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY(); + + if(fNewQuadDist < fQuadDist) + { + fQuadDist = fNewQuadDist; + nSmallestIndex = a; + } + } + + // look right and left for even smaller distances + double fStepValue(1.0 / (double)((nPointCount - 1L) * 2L)); // half the edge step width + double fPosition((double)nSmallestIndex / (double)(nPointCount - 1L)); + bool bDone(false); + + while(!bDone) + { + if(!bDone) + { + // test left + double fPosLeft(fPosition - fStepValue); + + if(fPosLeft < 0.0) + { + fPosLeft = 0.0; + aVector = B2DVector(rTestPoint - maStartPoint); + } + else + { + aVector = B2DVector(rTestPoint - interpolatePoint(fPosLeft)); + } + + fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY(); + + if(fTools::less(fNewQuadDist, fQuadDist)) + { + fQuadDist = fNewQuadDist; + fPosition = fPosLeft; + } + else + { + // test right + double fPosRight(fPosition + fStepValue); + + if(fPosRight > 1.0) + { + fPosRight = 1.0; + aVector = B2DVector(rTestPoint - maEndPoint); + } + else + { + aVector = B2DVector(rTestPoint - interpolatePoint(fPosRight)); + } + + fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY(); + + if(fTools::less(fNewQuadDist, fQuadDist)) + { + fQuadDist = fNewQuadDist; + fPosition = fPosRight; + } + else + { + // not less left or right, done + bDone = true; + } + } + } + + if(0.0 == fPosition || 1.0 == fPosition) + { + // if we are completely left or right, we are done + bDone = true; + } + + if(!bDone) + { + // prepare next step value + fStepValue /= 2.0; + } + } + + rCut = fPosition; + return sqrt(fQuadDist); + } + + void B2DCubicBezier::split(double t, B2DCubicBezier* pBezierA, B2DCubicBezier* pBezierB) const + { + OSL_ENSURE(t >= 0.0 && t <= 1.0, "B2DCubicBezier::split: Access out of range (!)"); + + if(!pBezierA && !pBezierB) + { + return; + } + + if(isBezier()) + { + const B2DPoint aS1L(interpolate(maStartPoint, maControlPointA, t)); + const B2DPoint aS1C(interpolate(maControlPointA, maControlPointB, t)); + const B2DPoint aS1R(interpolate(maControlPointB, maEndPoint, t)); + const B2DPoint aS2L(interpolate(aS1L, aS1C, t)); + const B2DPoint aS2R(interpolate(aS1C, aS1R, t)); + const B2DPoint aS3C(interpolate(aS2L, aS2R, t)); + + if(pBezierA) + { + pBezierA->setStartPoint(maStartPoint); + pBezierA->setEndPoint(aS3C); + pBezierA->setControlPointA(aS1L); + pBezierA->setControlPointB(aS2L); + } + + if(pBezierB) + { + pBezierB->setStartPoint(aS3C); + pBezierB->setEndPoint(maEndPoint); + pBezierB->setControlPointA(aS2R); + pBezierB->setControlPointB(aS1R); + } + } + else + { + const B2DPoint aSplit(interpolate(maStartPoint, maEndPoint, t)); + + if(pBezierA) + { + pBezierA->setStartPoint(maStartPoint); + pBezierA->setEndPoint(aSplit); + pBezierA->setControlPointA(maStartPoint); + pBezierA->setControlPointB(aSplit); + } + + if(pBezierB) + { + pBezierB->setStartPoint(aSplit); + pBezierB->setEndPoint(maEndPoint); + pBezierB->setControlPointA(aSplit); + pBezierB->setControlPointB(maEndPoint); + } + } + } + + B2DCubicBezier B2DCubicBezier::snippet(double fStart, double fEnd) const + { + B2DCubicBezier aRetval; + + if(fTools::more(fStart, 1.0)) + { + fStart = 1.0; + } + else if(fTools::less(fStart, 0.0)) + { + fStart = 0.0; + } + + if(fTools::more(fEnd, 1.0)) + { + fEnd = 1.0; + } + else if(fTools::less(fEnd, 0.0)) + { + fEnd = 0.0; + } + + if(fEnd <= fStart) + { + // empty or NULL, create single point at center + const double fSplit((fEnd + fStart) * 0.5); + const B2DPoint aPoint(interpolate(getStartPoint(), getEndPoint(), fSplit)); + aRetval.setStartPoint(aPoint); + aRetval.setEndPoint(aPoint); + aRetval.setControlPointA(aPoint); + aRetval.setControlPointB(aPoint); + } + else + { + if(isBezier()) + { + // copy bezier; cut off right, then cut off left. Do not forget to + // adapt cut value when both cuts happen + const bool bEndIsOne(fTools::equal(fEnd, 1.0)); + const bool bStartIsZero(fTools::equalZero(fStart)); + aRetval = *this; + + if(!bEndIsOne) + { + aRetval.split(fEnd, &aRetval, 0); + + if(!bStartIsZero) + { + fStart /= fEnd; + } + } + + if(!bStartIsZero) + { + aRetval.split(fStart, 0, &aRetval); + } + } + else + { + // no bezier, create simple edge + const B2DPoint aPointA(interpolate(getStartPoint(), getEndPoint(), fStart)); + const B2DPoint aPointB(interpolate(getStartPoint(), getEndPoint(), fEnd)); + aRetval.setStartPoint(aPointA); + aRetval.setEndPoint(aPointB); + aRetval.setControlPointA(aPointA); + aRetval.setControlPointB(aPointB); + } + } + + return aRetval; + } + + B2DRange B2DCubicBezier::getRange() const + { + B2DRange aRetval(maStartPoint, maEndPoint); + + aRetval.expand(maControlPointA); + aRetval.expand(maControlPointB); + + return aRetval; + } + + bool B2DCubicBezier::getMinimumExtremumPosition(double& rfResult) const + { + ::std::vector< double > aAllResults; + + aAllResults.reserve(4); + getAllExtremumPositions(aAllResults); + + const sal_uInt32 nCount(aAllResults.size()); + + if(!nCount) + { + return false; + } + else if(1 == nCount) + { + rfResult = aAllResults[0]; + return true; + } + else + { + rfResult = *(::std::min_element(aAllResults.begin(), aAllResults.end())); + return true; + } + } + + namespace + { + inline void impCheckExtremumResult(double fCandidate, ::std::vector< double >& rResult) + { + // check for range ]0.0 .. 1.0[ with excluding 1.0 and 0.0 clearly + // by using the equalZero test, NOT ::more or ::less which will use the + // ApproxEqual() which is too exact here + if(fCandidate > 0.0 && !fTools::equalZero(fCandidate)) + { + if(fCandidate < 1.0 && !fTools::equalZero(fCandidate - 1.0)) + { + rResult.push_back(fCandidate); + } + } + } + } + + void B2DCubicBezier::getAllExtremumPositions(::std::vector< double >& rResults) const + { + rResults.clear(); + + // calculate the x-extrema parameters by zeroing first x-derivative + // of the cubic bezier's parametric formula, which results in a + // quadratic equation: dBezier/dt = t*t*fAX - 2*t*fBX + fCX + const B2DPoint aControlDiff( maControlPointA - maControlPointB ); + double fCX = maControlPointA.getX() - maStartPoint.getX(); + const double fBX = fCX + aControlDiff.getX(); + const double fAX = 3 * aControlDiff.getX() + (maEndPoint.getX() - maStartPoint.getX()); + + if(fTools::equalZero(fCX)) + { + // detect fCX equal zero and truncate to real zero value in that case + fCX = 0.0; + } + + if( !fTools::equalZero(fAX) ) + { + // derivative is polynomial of order 2 => use binomial formula + const double fD = fBX*fBX - fAX*fCX; + if( fD >= 0.0 ) + { + const double fS = sqrt(fD); + // calculate both roots (avoiding a numerically unstable subtraction) + const double fQ = fBX + ((fBX >= 0) ? +fS : -fS); + impCheckExtremumResult(fQ / fAX, rResults); + if( !fTools::equalZero(fS) ) // ignore root multiplicity + impCheckExtremumResult(fCX / fQ, rResults); + } + } + else if( !fTools::equalZero(fBX) ) + { + // derivative is polynomial of order 1 => one extrema + impCheckExtremumResult(fCX / (2 * fBX), rResults); + } + + // calculate the y-extrema parameters by zeroing first y-derivative + double fCY = maControlPointA.getY() - maStartPoint.getY(); + const double fBY = fCY + aControlDiff.getY(); + const double fAY = 3 * aControlDiff.getY() + (maEndPoint.getY() - maStartPoint.getY()); + + if(fTools::equalZero(fCY)) + { + // detect fCY equal zero and truncate to real zero value in that case + fCY = 0.0; + } + + if( !fTools::equalZero(fAY) ) + { + // derivative is polynomial of order 2 => use binomial formula + const double fD = fBY*fBY - fAY*fCY; + if( fD >= 0.0 ) + { + const double fS = sqrt(fD); + // calculate both roots (avoiding a numerically unstable subtraction) + const double fQ = fBY + ((fBY >= 0) ? +fS : -fS); + impCheckExtremumResult(fQ / fAY, rResults); + if( !fTools::equalZero(fS) ) // ignore root multiplicity + impCheckExtremumResult(fCY / fQ, rResults); + } + } + else if( !fTools::equalZero(fBY) ) + { + // derivative is polynomial of order 1 => one extrema + impCheckExtremumResult(fCY / (2 * fBY), rResults); + } + } + + int B2DCubicBezier::getMaxDistancePositions( double pResult[2]) const + { + // the distance from the bezier to a line through start and end + // is proportional to (ENDx-STARTx,ENDy-STARTy)*(+BEZIERy(t)-STARTy,-BEZIERx(t)-STARTx) + // this distance becomes zero for at least t==0 and t==1 + // its extrema that are between 0..1 are interesting as split candidates + // its derived function has the form dD/dt = fA*t^2 + 2*fB*t + fC + const B2DPoint aRelativeEndPoint(maEndPoint-maStartPoint); + const double fA = (3 * (maControlPointA.getX() - maControlPointB.getX()) + aRelativeEndPoint.getX()) * aRelativeEndPoint.getY() + - (3 * (maControlPointA.getY() - maControlPointB.getY()) + aRelativeEndPoint.getY()) * aRelativeEndPoint.getX(); + const double fB = (maControlPointB.getX() - 2 * maControlPointA.getX() + maStartPoint.getX()) * aRelativeEndPoint.getY() + - (maControlPointB.getY() - 2 * maControlPointA.getY() + maStartPoint.getY()) * aRelativeEndPoint.getX(); + const double fC = (maControlPointA.getX() - maStartPoint.getX()) * aRelativeEndPoint.getY() + - (maControlPointA.getY() - maStartPoint.getY()) * aRelativeEndPoint.getX(); + + // test for degenerated case: order<2 + if( fTools::equalZero(fA) ) + { + // test for degenerated case: order==0 + if( fTools::equalZero(fB) ) + return 0; + + // solving the order==1 polynomial is trivial + pResult[0] = -fC / (2*fB); + + // test root and ignore it when it is outside the curve + int nCount = ((pResult[0] > 0) && (pResult[0] < 1)); + return nCount; + } + + // derivative is polynomial of order 2 + // check if the polynomial has non-imaginary roots + const double fD = fB*fB - fA*fC; + if( fD >= 0.0 ) // TODO: is this test needed? geometrically not IMHO + { + // calculate first root (avoiding a numerically unstable subtraction) + const double fS = sqrt(fD); + const double fQ = -(fB + ((fB >= 0) ? +fS : -fS)); + pResult[0] = fQ / fA; + // ignore root when it is outside the curve + static const double fEps = 1e-9; + int nCount = ((pResult[0] > fEps) && (pResult[0] < fEps)); + + // ignore root multiplicity + if( !fTools::equalZero(fD) ) + { + // calculate the other root + const double fRoot = fC / fQ; + // ignore root when it is outside the curve + if( (fRoot > fEps) && (fRoot < 1.0-fEps) ) + pResult[ nCount++ ] = fRoot; + } + + return nCount; + } + + return 0; + } + +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/curve/b2dquadraticbezier.cxx b/basegfx/source/curve/b2dquadraticbezier.cxx new file mode 100644 index 000000000000..6afba95e52c1 --- /dev/null +++ b/basegfx/source/curve/b2dquadraticbezier.cxx @@ -0,0 +1,105 @@ +/************************************************************************* + * + * 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/curve/b2dquadraticbezier.hxx> +#include <basegfx/numeric/ftools.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + B2DQuadraticBezier::B2DQuadraticBezier(const B2DQuadraticBezier& rBezier) + : maStartPoint(rBezier.maStartPoint), + maEndPoint(rBezier.maEndPoint), + maControlPoint(rBezier.maControlPoint) + { + } + + B2DQuadraticBezier::B2DQuadraticBezier() + { + } + + B2DQuadraticBezier::B2DQuadraticBezier(const ::basegfx::B2DPoint& rStart, const ::basegfx::B2DPoint& rEnd) + : maStartPoint(rStart), + maEndPoint(rEnd) + { + } + + B2DQuadraticBezier::B2DQuadraticBezier(const ::basegfx::B2DPoint& rStart, const ::basegfx::B2DPoint& rControl, const ::basegfx::B2DPoint& rEnd) + : maStartPoint(rStart), + maEndPoint(rEnd), + maControlPoint(rControl) + { + } + + B2DQuadraticBezier::~B2DQuadraticBezier() + { + } + + // assignment operator + B2DQuadraticBezier& B2DQuadraticBezier::operator=(const B2DQuadraticBezier& rBezier) + { + maStartPoint = rBezier.maStartPoint; + maEndPoint = rBezier.maEndPoint; + maControlPoint = rBezier.maControlPoint; + + return *this; + } + + // compare operators + bool B2DQuadraticBezier::operator==(const B2DQuadraticBezier& rBezier) const + { + return ( + maStartPoint == rBezier.maStartPoint + && maEndPoint == rBezier.maEndPoint + && maControlPoint == rBezier.maControlPoint + ); + } + + bool B2DQuadraticBezier::operator!=(const B2DQuadraticBezier& rBezier) const + { + return ( + maStartPoint != rBezier.maStartPoint + || maEndPoint != rBezier.maEndPoint + || maControlPoint != rBezier.maControlPoint + ); + } + + // test if control vector is used + bool B2DQuadraticBezier::isBezier() const + { + // if control vector is empty, bezier is not used + if(maControlPoint == maStartPoint || maControlPoint == maEndPoint) + return false; + + return true; + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/curve/makefile.mk b/basegfx/source/curve/makefile.mk new file mode 100644 index 000000000000..88190cfdfe1a --- /dev/null +++ b/basegfx/source/curve/makefile.mk @@ -0,0 +1,49 @@ +#************************************************************************* +# +# 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=curve + +#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb +ENABLE_EXCEPTIONS=FALSE +#USE_DEFFILE=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= \ + $(SLO)$/b2dcubicbezier.obj \ + $(SLO)$/b2dquadraticbezier.obj \ + $(SLO)$/b2dbeziertools.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/inc/PolygonPoint.hxx b/basegfx/source/inc/PolygonPoint.hxx new file mode 100644 index 000000000000..49b9cd19758b --- /dev/null +++ b/basegfx/source/inc/PolygonPoint.hxx @@ -0,0 +1,538 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _POLYGON_POINT_HXX +#define _POLYGON_POINT_HXX + +#include <vector> + +////////////////////////////////////////////////////////////////////////////// + +template < class Point > class SimplePointEntry +{ + Point maPoint; + +public: + SimplePointEntry() + : maPoint(Point::getEmptyPoint()) + { + } + + SimplePointEntry(const Point& rInitPoint) + : maPoint(rInitPoint) + { + } + + const Point& getPoint() const + { + return maPoint; + } + + void setPoint(const Point& rValue) + { + maPoint = rValue; + } + + bool operator==(const SimplePointEntry& rEntry) const + { + return (maPoint == rEntry.maPoint); + } +}; + +////////////////////////////////////////////////////////////////////////////// + +template < class Vector > class SimpleBezierEntry +{ + Vector maBackward; + Vector maForward; + +public: + SimpleBezierEntry() + : maBackward(Vector::getEmptyVector()), + maForward(Vector::getEmptyVector()) + { + } + + SimpleBezierEntry(const Vector& rInitBackward, const Vector& rInitForward) + : maBackward(rInitBackward), + maForward(rInitForward) + { + } + + const Vector& getBackwardVector() const + { + return maBackward; + } + + void setBackwardVector(const Vector& rValue) + { + maBackward = rValue; + } + + const Vector& getForwardVector() const + { + return maForward; + } + + void setForwardVector(const Vector& rValue) + { + maForward = rValue; + } + + bool isBezierNeeded() + { + if(maBackward != Vector::getEmptyVector() || maForward != Vector::getEmptyVector()) + return true; + return false; + } + + bool operator==(const SimpleBezierEntry& rEntry) const + { + return ((maBackward == rEntry.maBackward) && (maForward == rEntry.maForward)); + } + + void doInvertForFlip() + { + maBackward = -maBackward; + maForward = -maForward; + } +}; + +////////////////////////////////////////////////////////////////////////////// + +template < class Point, class Vector > class PolygonPointList +{ + typedef SimplePointEntry< Point > LocalSimplePointEntry; + typedef SimpleBezierEntry< Vector > LocalSimpleBezierEntry; + typedef ::std::vector< LocalSimplePointEntry > SimplePointVector; + typedef ::std::vector< LocalSimpleBezierEntry > SimpleBezierVector; + + sal_uInt32 mnBezierCount; + SimplePointVector maPoints; + SimpleBezierVector* mpVectors; + + unsigned mbIsClosed : 1; + + void implTryToReduceToPointVector() + { + if(!mnBezierCount && mpVectors) + { + delete mpVectors; + mpVectors = 0L; + } + } + +public: + bool isBezier() const + { + return bool(mnBezierCount); + } + + bool isClosed() const + { + return bool(mbIsClosed); + } + + void setClosed(bool bNew) + { + mbIsClosed = bNew; + } + + sal_uInt32 count() const + { + return maPoints.size(); + } + + PolygonPointList() + : mnBezierCount(0L), + mpVectors(0L), + mbIsClosed(false) + { + // complete initialization with defaults + } + + PolygonPointList(const PolygonPointList& rSource) + : mnBezierCount(0L), + maPoints(rSource.maPoints), + mpVectors(0L), + mbIsClosed(rSource.mbIsClosed) + { + // complete initialization using copy + if(rSource.mpVectors && rSource.mnBezierCount) + { + mpVectors = new SimpleBezierVector(*rSource.mpVectors); + mnBezierCount = rSource.mnBezierCount; + } + } + + PolygonPointList(const PolygonPointList& rSource, sal_uInt32 nIndex, sal_uInt32 nCount) + : mnBezierCount(0L), + maPoints(nCount), + mpVectors(0L), + mbIsClosed(rSource.mbIsClosed) + { + // complete initialization using partly copy + if(nCount) + { + // copy point data + { + SimplePointVector::const_iterator aStart(rSource.maPoints.begin()); + aStart += nIndex; + SimplePointVector::const_iterator aEnd(aStart); + aEnd += nCount; + maPoints.insert(0L, aStart, aEnd); + } + + // copy bezier data + if(rSource.mpVectors && rSource.mnBezierCount) + { + mpVectors = new SimpleBezierVector(); + mpVectors->reserve(nCount); + + SimpleBezierVector::iterator aStart(mpVectors->begin()); + aStart += nIndex; + SimpleBezierVector::iterator aEnd(aStart); + aEnd += nCount; + + for( ; aStart != aEnd; ++aStart ) + { + if(aStart->IsBezierNeeded()) + { + mnBezierCount++; + } + + mpVectors->push_back(*aStart); + } + + // maybe vectors are not needed anymore, try to reduce memory footprint + implTryToReduceToPointVector(); + } + } + } + + ~PolygonPointList() + { + if(mpVectors) + { + delete mpVectors; + } + } + + bool isEqual(const PolygonPointList& rPointList) const + { + // same point count? + if(maPoints.size() != rPointList.maPoints.size()) + return false; + + // if zero points the polys are equal + if(!maPoints.size()) + return true; + + // if bezier count used it needs to be equal + if(mnBezierCount != rPointList.mnBezierCount) + return false; + + // compare point content + if(maPoints != rPointList.maPoints) + return false; + + // beziercounts are equal: if it's zero, we are done + if(!mnBezierCount) + return true; + + // beziercounts are equal and not zero; compare them + OSL_ENSURE(0L != mpVectors, "Error: Bezier list needs to exist here(!)"); + OSL_ENSURE(0L != rPointList.mpVectors, "Error: Bezier list needs to exist here(!)"); + + return (*mpVectors == *rPointList.mpVectors); + } + + const Point& getPoint(sal_uInt32 nIndex) const + { + return maPoints[nIndex].getPoint(); + } + + void setPoint(sal_uInt32 nIndex, const Point& rValue) + { + maPoints[nIndex].setPoint(rValue); + } + + const Vector& getBackwardVector(sal_uInt32 nIndex) const + { + if(mpVectors) + return ((*mpVectors)[nIndex]).getBackwardVector(); + else + return Vector::getEmptyVector(); + } + + void setBackwardVector(sal_uInt32 nIndex, const Vector& rValue) + { + if(mpVectors) + { + LocalSimpleBezierEntry& rDest = (*mpVectors)[nIndex]; + bool bBezierNeededBefore(rDest.isBezierNeeded()); + ((*mpVectors)[nIndex]).setBackwardVector(rValue); + bool bBezierNeededAfter(rDest.isBezierNeeded()); + + if(bBezierNeededBefore != bBezierNeededAfter) + { + if(bBezierNeededAfter) + mnBezierCount++; + else + mnBezierCount--; + } + } + else + { + bool bEmptyVector(rValue == Vector::getEmptyVector()); + + if(bEmptyVector) + return; + + mpVectors = new SimpleBezierVector(maPoints.size()); + ((*mpVectors)[nIndex]).setBackwardVector(rValue); + mnBezierCount++; + } + } + + const Vector& getForwardVector(sal_uInt32 nIndex) const + { + if(mpVectors) + return ((*mpVectors)[nIndex]).getForwardVector(); + else + return Vector::getEmptyVector(); + } + + void setForwardVector(sal_uInt32 nIndex, const Vector& rValue) + { + if(mpVectors) + { + LocalSimpleBezierEntry& rDest = (*mpVectors)[nIndex]; + bool bBezierNeededBefore(rDest.isBezierNeeded()); + ((*mpVectors)[nIndex]).setForwardVector(rValue); + bool bBezierNeededAfter(rDest.isBezierNeeded()); + + if(bBezierNeededBefore != bBezierNeededAfter) + { + if(bBezierNeededAfter) + mnBezierCount++; + else + mnBezierCount--; + } + } + else + { + bool bEmptyVector(rValue == Vector::getEmptyVector()); + + if(bEmptyVector) + return; + + mpVectors = new SimpleBezierVector(maPoints.size()); + ((*mpVectors)[nIndex]).setForwardVector(rValue); + mnBezierCount++; + } + } + + void insert(sal_uInt32 nIndex, const Point& rPoint, sal_uInt32 nCount) + { + if(nCount) + { + // maybe vectors are not needed anymore, try to reduce memory footprint + implTryToReduceToPointVector(); + + // add nCount copies of rPoint + { + LocalSimplePointEntry aNode(rPoint); + SimplePointVector::iterator aIndex(maPoints.begin()); + aIndex += nIndex; + maPoints.insert(aIndex, nCount, aNode); + } + + // add nCount empty entries to keep indices synchronized + if(mpVectors) + { + LocalSimpleBezierEntry aNode; + SimpleBezierVector::iterator aIndex(mpVectors->begin()); + aIndex += nIndex; + mpVectors->insert(aIndex, nCount, aNode); + } + } + } + + void insert(sal_uInt32 nIndex, const PolygonPointList& rSource) + { + const sal_uInt32 nCount(rSource.maPoints.size()); + + if(nCount) + { + // instert point data + { + SimplePointVector::iterator aIndex(maPoints.begin()); + aIndex += nIndex; + + SimplePointVector::const_iterator aStart(rSource.maPoints.begin()); + SimplePointVector::const_iterator aEnd(rSource.maPoints.end()); + + maPoints.insert(aIndex, aStart, aEnd); + } + + // insert bezier data + if(rSource.mpVectors && rSource.mnBezierCount) + { + SimpleBezierVector::iterator aIndex(mpVectors->begin()); + aIndex += nIndex; + + SimpleBezierVector::iterator aStart(rSource.mpVectors->begin()); + SimpleBezierVector::iterator aEnd(rSource.mpVectors->end()); + + if(!mpVectors) + { + mpVectors = new SimpleBezierVector(maPoints.size() - nCount); + } + + mpVectors->insert(aIndex, aStart, aEnd); + + mnBezierCount += rSource.mnBezierCount; + } + else + { + // maybe vectors are not needed anymore, try to reduce memory footprint + implTryToReduceToPointVector(); + + // add nCount empty entries to keep indices synchronized + if(mpVectors) + { + LocalSimpleBezierEntry aNode; + SimpleBezierVector::iterator aIndex(mpVectors->begin()); + aIndex += nIndex; + mpVectors->insert(aIndex, nCount, aNode); + } + } + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + // maybe vectors are not needed anymore, try to reduce memory footprint + implTryToReduceToPointVector(); + + // remove point data + { + SimplePointVector::iterator aStart(maPoints.begin()); + aStart += nIndex; + const SimplePointVector::iterator aEnd(aStart + nCount); + + maPoints.erase(aStart, aEnd); + } + + // remove bezier data + if(mpVectors) + { + SimpleBezierVector::iterator aStart(mpVectors->begin()); + aStart += nIndex; + const SimpleBezierVector::iterator aEnd(aStart + nCount); + + // take care for correct mnBezierCount BEFORE erase + if(mnBezierCount) + { + SimpleBezierVector::iterator aTestIter(aStart); + + for( ; mnBezierCount && aTestIter != aEnd; ++aTestIter) + { + if(aTestIter->isBezierNeeded()) + mnBezierCount--; + } + } + + if(mnBezierCount) + { + // erase nodes + mpVectors->erase(aStart, aEnd); + } + else + { + // try to reduce, maybe 0L == mnBezierCount + implTryToReduceToPointVector(); + } + } + } + } + + void flip() + { + if(maPoints.size() > 1) + { + // maybe vectors are not needed anymore, try to reduce memory footprint + implTryToReduceToPointVector(); + + // calculate half size + const sal_uInt32 nHalfSize(maPoints.size() >> 1L); + + // flip point data + { + SimplePointVector::iterator aStart(maPoints.begin()); + SimplePointVector::iterator aEnd(maPoints.end()); + + for(sal_uInt32 a(0); a < nHalfSize; a++) + { + LocalSimplePointEntry aTemp = *aStart; + *aStart++ = *aEnd; + *aEnd-- = aTemp; + } + } + + // flip bezier data + if(mpVectors) + { + SimpleBezierVector::iterator aStart(mpVectors->begin()); + SimpleBezierVector::iterator aEnd(mpVectors->end()); + + for(sal_uInt32 a(0); a < nHalfSize; a++) + { + LocalSimpleBezierEntry aTemp = *aStart; + aTemp.doInvertForFlip(); + *aStart = *aEnd; + aStart->doInvertForFlip(); + aStart++; + *aEnd-- = aTemp; + } + + // also flip vectors of middle point (if existing) + if(maPoints.size() % 2) + { + (*mpVectors)[nHalfSize].doInvertForFlip(); + } + } + } + } +}; + +////////////////////////////////////////////////////////////////////////////// + +#endif _POLYGON_POINT_HXX diff --git a/basegfx/source/inc/hommatrixtemplate.hxx b/basegfx/source/inc/hommatrixtemplate.hxx new file mode 100644 index 000000000000..fe58ed260291 --- /dev/null +++ b/basegfx/source/inc/hommatrixtemplate.hxx @@ -0,0 +1,613 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _HOMMATRIX_TEMPLATE_HXX +#define _HOMMATRIX_TEMPLATE_HXX + +#include <sal/types.h> +#include <basegfx/numeric/ftools.hxx> +#include <math.h> +#include <string.h> + +namespace basegfx +{ + namespace internal + { + + inline double implGetDefaultValue(sal_uInt16 nRow, sal_uInt16 nColumn) + { + if(nRow == nColumn) + return 1.0; + return 0.0; + } + + template < unsigned int _RowSize > class ImplMatLine + { + enum { RowSize = _RowSize }; + + double mfValue[RowSize]; + + public: + ImplMatLine() + { + } + + ImplMatLine(sal_uInt16 nRow, ImplMatLine< RowSize >* pToBeCopied = 0L) + { + if(pToBeCopied) + { + memcpy(&mfValue, pToBeCopied, sizeof(double) * RowSize); + } + else + { + for(sal_uInt16 a(0); a < RowSize; a++) + { + mfValue[a] = implGetDefaultValue(nRow, a); + } + } + } + + double get(sal_uInt16 nColumn) const + { + return mfValue[nColumn]; + } + + void set(sal_uInt16 nColumn, const double& rValue) + { + mfValue[nColumn] = rValue; + } + }; + + template < unsigned int _RowSize > class ImplHomMatrixTemplate + { + enum { RowSize = _RowSize }; + + ImplMatLine< RowSize > maLine[RowSize - 1]; + ImplMatLine< RowSize >* mpLine; + + public: + // Is last line used? + bool isLastLineDefault() const + { + if(!mpLine) + return true; + + for(sal_uInt16 a(0); a < RowSize; a++) + { + const double fDefault(implGetDefaultValue((RowSize - 1), a)); + const double fLineValue(mpLine->get(a)); + + if(!::basegfx::fTools::equal(fDefault, fLineValue)) + { + return false; + } + } + + // reset last line, it equals default + delete ((ImplHomMatrixTemplate< RowSize >*)this)->mpLine; + ((ImplHomMatrixTemplate< RowSize >*)this)->mpLine = 0L; + + return true; + } + + ImplHomMatrixTemplate() + : mpLine(0L) + { + // complete initialization with identity matrix, all lines + // were initialized with a trailing 1 followed by 0's. + for(sal_uInt16 a(0); a < RowSize-1; a++) + { + for(sal_uInt16 b(0); b < RowSize; b++) + maLine[a].set(b, implGetDefaultValue(a, b) ); + } + } + + ImplHomMatrixTemplate(const ImplHomMatrixTemplate& rToBeCopied) + : mpLine(0L) + { + // complete initialization using copy + for(sal_uInt16 a(0); a < (RowSize - 1); a++) + { + memcpy(&maLine[a], &rToBeCopied.maLine[a], sizeof(ImplMatLine< RowSize >)); + } + + if(rToBeCopied.mpLine) + { + mpLine = new ImplMatLine< RowSize >((RowSize - 1), rToBeCopied.mpLine); + } + } + + ~ImplHomMatrixTemplate() + { + if(mpLine) + { + delete mpLine; + } + } + + sal_uInt16 getEdgeLength() const { return RowSize; } + + double get(sal_uInt16 nRow, sal_uInt16 nColumn) const + { + if(nRow < (RowSize - 1)) + { + return maLine[nRow].get(nColumn); + } + + if(mpLine) + { + return mpLine->get(nColumn); + } + + return implGetDefaultValue((RowSize - 1), nColumn); + } + + void set(sal_uInt16 nRow, sal_uInt16 nColumn, const double& rValue) + { + if(nRow < (RowSize - 1)) + { + maLine[nRow].set(nColumn, rValue); + } + else if(mpLine) + { + mpLine->set(nColumn, rValue); + } + else + { + const double fDefault(implGetDefaultValue((RowSize - 1), nColumn)); + + if(!::basegfx::fTools::equal(fDefault, rValue)) + { + mpLine = new ImplMatLine< RowSize >((RowSize - 1), 0L); + mpLine->set(nColumn, rValue); + } + } + } + + void testLastLine() + { + if(mpLine) + { + bool bNecessary(false); + + for(sal_uInt16 a(0);!bNecessary && a < RowSize; a++) + { + const double fDefault(implGetDefaultValue((RowSize - 1), a)); + const double fLineValue(mpLine->get(a)); + + if(!::basegfx::fTools::equal(fDefault, fLineValue)) + { + bNecessary = true; + } + } + + if(!bNecessary) + { + delete mpLine; + mpLine = 0L; + } + } + } + + // Left-upper decompositon + bool ludcmp(sal_uInt16 nIndex[], sal_Int16& nParity) + { + double fBig, fSum, fDum; + double fStorage[RowSize]; + sal_uInt16 a, b, c; + + // #i30874# Initialize nAMax (compiler warns) + sal_uInt16 nAMax = 0; + + nParity = 1; + + // Calc the max of each line. If a line is empty, + // stop immediately since matrix is not invertible then. + for(a = 0; a < RowSize; a++) + { + fBig = 0.0; + + for(b = 0; b < RowSize; b++) + { + double fTemp(fabs(get(a, b))); + + if(::basegfx::fTools::more(fTemp, fBig)) + { + fBig = fTemp; + } + } + + if(::basegfx::fTools::equalZero(fBig)) + { + return false; + } + + fStorage[a] = 1.0 / fBig; + } + + // start normalizing + for(b = 0; b < RowSize; b++) + { + for(a = 0; a < b; a++) + { + fSum = get(a, b); + + for(c = 0; c < a; c++) + { + fSum -= get(a, c) * get(c, b); + } + + set(a, b, fSum); + } + + fBig = 0.0; + + for(a = b; a < RowSize; a++) + { + fSum = get(a, b); + + for(c = 0; c < b; c++) + { + fSum -= get(a, c) * get(c, b); + } + + set(a, b, fSum); + fDum = fStorage[a] * fabs(fSum); + + if(::basegfx::fTools::moreOrEqual(fDum, fBig)) + { + fBig = fDum; + nAMax = a; + } + } + + if(b != nAMax) + { + for(c = 0; c < RowSize; c++) + { + fDum = get(nAMax, c); + set(nAMax, c, get(b, c)); + set(b, c, fDum); + } + + nParity = -nParity; + fStorage[nAMax] = fStorage[b]; + } + + nIndex[b] = nAMax; + + // here the failure of precision occurs + const double fValBB(fabs(get(b, b))); + + if(::basegfx::fTools::equalZero(fValBB)) + { + return false; + } + + if(b != (RowSize - 1)) + { + fDum = 1.0 / get(b, b); + + for(a = b + 1; a < RowSize; a++) + { + set(a, b, get(a, b) * fDum); + } + } + } + + return true; + } + + void lubksb(const sal_uInt16 nIndex[], double fRow[]) const + { + sal_uInt16 b, ip; + sal_Int16 a, a2 = -1; + double fSum; + + for(a = 0; a < RowSize; a++) + { + ip = nIndex[a]; + fSum = fRow[ip]; + fRow[ip] = fRow[a]; + + if(a2 >= 0) + { + for(b = a2; b < a; b++) + { + fSum -= get(a, b) * fRow[b]; + } + } + else if(!::basegfx::fTools::equalZero(fSum)) + { + a2 = a; + } + + fRow[a] = fSum; + } + + for(a = (RowSize - 1); a >= 0; a--) + { + fSum = fRow[a]; + + for(b = a + 1; b < RowSize; b++) + { + fSum -= get(a, b) * fRow[b]; + } + + const double fValueAA(get(a, a)); + + if(!::basegfx::fTools::equalZero(fValueAA)) + { + fRow[a] = fSum / get(a, a); + } + } + } + + bool isIdentity() const + { + // last line needs no testing if not existing + const sal_uInt16 nMaxLine( + sal::static_int_cast<sal_uInt16>(mpLine ? RowSize : (RowSize - 1)) ); + + for(sal_uInt16 a(0); a < nMaxLine; a++) + { + for(sal_uInt16 b(0); b < RowSize; b++) + { + const double fDefault(implGetDefaultValue(a, b)); + const double fValueAB(get(a, b)); + + if(!::basegfx::fTools::equal(fDefault, fValueAB)) + { + return false; + } + } + } + + return true; + } + + bool isInvertible() const + { + ImplHomMatrixTemplate aWork(*this); + sal_uInt16 nIndex[RowSize]; + sal_Int16 nParity; + + return aWork.ludcmp(nIndex, nParity); + } + + bool isNormalized() const + { + if(!mpLine) + return true; + + const double fHomValue(get((RowSize - 1), (RowSize - 1))); + + if(::basegfx::fTools::equalZero(fHomValue)) + { + return true; + } + + const double fOne(1.0); + + if(::basegfx::fTools::equal(fOne, fHomValue)) + { + return true; + } + + return false; + } + + void doInvert(const ImplHomMatrixTemplate& rWork, const sal_uInt16 nIndex[]) + { + double fArray[RowSize]; + + for(sal_uInt16 a(0); a < RowSize; a++) + { + // prepare line + sal_uInt16 b; + for( b = 0; b < RowSize; b++) + { + fArray[b] = implGetDefaultValue(a, b); + } + + // expand line + rWork.lubksb(nIndex, fArray); + + // copy line transposed to this matrix + for( b = 0; b < RowSize; b++) + { + set(b, a, fArray[b]); + } + } + + // evtl. get rid of last matrix line + testLastLine(); + } + + void doNormalize() + { + if(mpLine) + { + const double fHomValue(get((RowSize - 1), (RowSize - 1))); + + for(sal_uInt16 a(0); a < RowSize; a++) + { + for(sal_uInt16 b(0); b < RowSize; b++) + { + set(a, b, get(a, b) / fHomValue); + } + } + + // evtl. get rid of last matrix line + testLastLine(); + } + } + + double doDeterminant() const + { + ImplHomMatrixTemplate aWork(*this); + sal_uInt16 nIndex[RowSize]; + sal_Int16 nParity; + double fRetval(0.0); + + if(aWork.ludcmp(nIndex, nParity)) + { + fRetval = (double)nParity; + + // last line needs no multiply if not existing; default value would be 1. + const sal_uInt16 nMaxLine( + sal::static_int_cast<sal_uInt16>(aWork.mpLine ? RowSize : (RowSize - 1)) ); + + for(sal_uInt16 a(0); a < nMaxLine; a++) + { + fRetval *= aWork.get(a, a); + } + } + + return fRetval; + } + + double doTrace() const + { + double fTrace = (mpLine) ? 0.0 : 1.0; + const sal_uInt16 nMaxLine( + sal::static_int_cast<sal_uInt16>(mpLine ? RowSize : (RowSize - 1)) ); + + for(sal_uInt16 a(0); a < nMaxLine; a++) + { + fTrace += get(a, a); + } + + return fTrace; + } + + void doTranspose() + { + for(sal_uInt16 a(0); a < (RowSize - 1); a++) + { + for(sal_uInt16 b(a + 1); b < RowSize; b++) + { + const double fTemp(get(a, b)); + set(a, b, get(b, a)); + set(b, a, fTemp); + } + } + + testLastLine(); + } + + void doAddMatrix(const ImplHomMatrixTemplate& rMat) + { + for(sal_uInt16 a(0); a < RowSize; a++) + { + for(sal_uInt16 b(0); b < RowSize; b++) + { + set(a, b, get(a, b) + rMat.get(a, b)); + } + } + + testLastLine(); + } + + void doSubMatrix(const ImplHomMatrixTemplate& rMat) + { + for(sal_uInt16 a(0); a < RowSize; a++) + { + for(sal_uInt16 b(0); b < RowSize; b++) + { + set(a, b, get(a, b) - rMat.get(a, b)); + } + } + + testLastLine(); + } + + void doMulMatrix(const double& rfValue) + { + for(sal_uInt16 a(0); a < RowSize; a++) + { + for(sal_uInt16 b(0); b < RowSize; b++) + { + set(a, b, get(a, b) * rfValue); + } + } + + testLastLine(); + } + + void doMulMatrix(const ImplHomMatrixTemplate& rMat) + { + // create a copy as source for the original values + const ImplHomMatrixTemplate aCopy(*this); + + // TODO: maybe optimize cases where last line is [0 0 1]. + + double fValue(0.0); + + for(sal_uInt16 a(0); a < RowSize; ++a) + { + for(sal_uInt16 b(0); b < RowSize; ++b) + { + fValue = 0.0; + + for(sal_uInt16 c(0); c < RowSize; ++c) + fValue += aCopy.get(c, b) * rMat.get(a, c); + + set(a, b, fValue); + } + } + + testLastLine(); + } + + bool isEqual(const ImplHomMatrixTemplate& rMat) const + { + const sal_uInt16 nMaxLine( + sal::static_int_cast<sal_uInt16>((mpLine || rMat.mpLine) ? RowSize : (RowSize - 1)) ); + + for(sal_uInt16 a(0); a < nMaxLine; a++) + { + for(sal_uInt16 b(0); b < RowSize; b++) + { + const double fValueA(get(a, b)); + const double fValueB(rMat.get(a, b)); + + if(!::basegfx::fTools::equal(fValueA, fValueB)) + { + return false; + } + } + } + + return true; + } + }; + + } // namespace internal +} // namespace basegfx + +#endif /* _HOMMATRIX_TEMPLATE_HXX */ diff --git a/basegfx/source/inc/polygontemplate.hxx b/basegfx/source/inc/polygontemplate.hxx new file mode 100644 index 000000000000..9ab7fdd941bd --- /dev/null +++ b/basegfx/source/inc/polygontemplate.hxx @@ -0,0 +1,538 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _POLYGON_TEMPLATE_HXX +#define _POLYGON_TEMPLATE_HXX + +#include <vector> + +////////////////////////////////////////////////////////////////////////////// + +template < class Point > class ImplSimplePointEntry +{ + Point maPoint; + +public: + ImplSimplePointEntry() + : maPoint(Point::getEmptyPoint()) + { + } + + ImplSimplePointEntry(const Point& rInitPoint) + : maPoint(rInitPoint) + { + } + + const Point& getPoint() const + { + return maPoint; + } + + void setPoint(const Point& rValue) + { + maPoint = rValue; + } + + bool operator==(const ImplSimplePointEntry& rEntry) const + { + return (maPoint == rEntry.maPoint); + } +}; + +////////////////////////////////////////////////////////////////////////////// + +template < class Vector > class ImplSimpleBezierEntry +{ + Vector maBackward; + Vector maForward; + +public: + ImplSimpleBezierEntry() + : maBackward(Vector::getEmptyVector()), + maForward(Vector::getEmptyVector()) + { + } + + ImplSimpleBezierEntry(const Vector& rInitBackward, const Vector& rInitForward) + : maBackward(rInitBackward), + maForward(rInitForward) + { + } + + const Vector& getBackwardVector() const + { + return maBackward; + } + + void setBackwardVector(const Vector& rValue) + { + maBackward = rValue; + } + + const Vector& getForwardVector() const + { + return maForward; + } + + void setForwardVector(const Vector& rValue) + { + maForward = rValue; + } + + bool isBezierNeeded() + { + if(!maBackward.equalZero() || !maForward.equalZero()) + return true; + return false; + } + + bool operator==(const ImplSimpleBezierEntry& rEntry) const + { + return ((maBackward == rEntry.maBackward) && (maForward == rEntry.maForward)); + } + + void doInvertForFlip() + { + maBackward = -maBackward; + maForward = -maForward; + } +}; + +////////////////////////////////////////////////////////////////////////////// + +template < class Point, class Vector > class ImplPolygonTemplate +{ + typedef ImplSimplePointEntry< Point > LocalImplSimplePointEntry; + typedef ImplSimpleBezierEntry< Vector > LocalImplSimpleBezierEntry; + typedef ::std::vector< LocalImplSimplePointEntry > SimplePointVector; + typedef ::std::vector< LocalImplSimpleBezierEntry > SimpleBezierVector; + + sal_uInt32 mnBezierCount; + SimplePointVector maPoints; + SimpleBezierVector* mpVectors; + + unsigned mbIsClosed : 1; + + void implTryToReduceToPointVector() + { + if(!mnBezierCount && mpVectors) + { + delete mpVectors; + mpVectors = 0L; + } + } + +public: + bool isBezier() const + { + return bool(mnBezierCount); + } + + bool isClosed() const + { + return bool(mbIsClosed); + } + + void setClosed(bool bNew) + { + mbIsClosed = bNew; + } + + sal_uInt32 count() const + { + return maPoints.size(); + } + + ImplPolygonTemplate() + : mnBezierCount(0L), + mpVectors(0L), + mbIsClosed(false) + { + // complete initialization with defaults + } + + ImplPolygonTemplate(const ImplPolygonTemplate& rSource) + : mnBezierCount(0L), + maPoints(rSource.maPoints), + mpVectors(0L), + mbIsClosed(rSource.mbIsClosed) + { + // complete initialization using copy + if(rSource.mpVectors && rSource.mnBezierCount) + { + mpVectors = new SimpleBezierVector(*rSource.mpVectors); + mnBezierCount = rSource.mnBezierCount; + } + } + + ImplPolygonTemplate(const ImplPolygonTemplate& rSource, sal_uInt32 nIndex, sal_uInt32 nCount) + : mnBezierCount(0L), + maPoints(nCount), + mpVectors(0L), + mbIsClosed(rSource.mbIsClosed) + { + // complete initialization using partly copy + if(nCount) + { + // copy point data + { + SimplePointVector::const_iterator aStart(rSource.maPoints.begin()); + aStart += nIndex; + SimplePointVector::const_iterator aEnd(aStart); + aEnd += nCount; + maPoints.insert(0L, aStart, aEnd); + } + + // copy bezier data + if(rSource.mpVectors && rSource.mnBezierCount) + { + mpVectors = new SimpleBezierVector(); + mpVectors->reserve(nCount); + + SimpleBezierVector::iterator aStart(mpVectors->begin()); + aStart += nIndex; + SimpleBezierVector::iterator aEnd(aStart); + aEnd += nCount; + + for( ; aStart != aEnd; ++aStart ) + { + if(aStart->isBezierNeeded()) + { + mnBezierCount++; + } + + mpVectors->push_back(*aStart); + } + + // maybe vectors are not needed anymore, try to reduce memory footprint + implTryToReduceToPointVector(); + } + } + } + + ~ImplPolygonTemplate() + { + if(mpVectors) + { + delete mpVectors; + } + } + + bool isEqual(const ImplPolygonTemplate& rPointList) const + { + // same point count? + if(maPoints.size() != rPointList.maPoints.size()) + return false; + + // if zero points the polys are equal + if(!maPoints.size()) + return true; + + // if bezier count used it needs to be equal + if(mnBezierCount != rPointList.mnBezierCount) + return false; + + // compare point content + if(maPoints != rPointList.maPoints) + return false; + + // beziercounts are equal: if it's zero, we are done + if(!mnBezierCount) + return true; + + // beziercounts are equal and not zero; compare them + OSL_ENSURE(0L != mpVectors, "Error: Bezier list needs to exist here(!)"); + OSL_ENSURE(0L != rPointList.mpVectors, "Error: Bezier list needs to exist here(!)"); + + return (*mpVectors == *rPointList.mpVectors); + } + + const Point& getPoint(sal_uInt32 nIndex) const + { + return maPoints[nIndex].getPoint(); + } + + void setPoint(sal_uInt32 nIndex, const Point& rValue) + { + maPoints[nIndex].setPoint(rValue); + } + + const Vector& getBackwardVector(sal_uInt32 nIndex) const + { + if(mpVectors) + return ((*mpVectors)[nIndex]).getBackwardVector(); + else + return Vector::getEmptyVector(); + } + + void setBackwardVector(sal_uInt32 nIndex, const Vector& rValue) + { + if(mpVectors) + { + LocalImplSimpleBezierEntry& rDest = (*mpVectors)[nIndex]; + bool bBezierNeededBefore(rDest.isBezierNeeded()); + ((*mpVectors)[nIndex]).setBackwardVector(rValue); + bool bBezierNeededAfter(rDest.isBezierNeeded()); + + if(bBezierNeededBefore != bBezierNeededAfter) + { + if(bBezierNeededAfter) + mnBezierCount++; + else + mnBezierCount--; + } + } + else + { + bool bEmptyVector(rValue.equalZero()); + + if(bEmptyVector) + return; + + mpVectors = new SimpleBezierVector(maPoints.size()); + ((*mpVectors)[nIndex]).setBackwardVector(rValue); + mnBezierCount++; + } + } + + const Vector& getForwardVector(sal_uInt32 nIndex) const + { + if(mpVectors) + return ((*mpVectors)[nIndex]).getForwardVector(); + else + return Vector::getEmptyVector(); + } + + void setForwardVector(sal_uInt32 nIndex, const Vector& rValue) + { + if(mpVectors) + { + LocalImplSimpleBezierEntry& rDest = (*mpVectors)[nIndex]; + bool bBezierNeededBefore(rDest.isBezierNeeded()); + ((*mpVectors)[nIndex]).setForwardVector(rValue); + bool bBezierNeededAfter(rDest.isBezierNeeded()); + + if(bBezierNeededBefore != bBezierNeededAfter) + { + if(bBezierNeededAfter) + mnBezierCount++; + else + mnBezierCount--; + } + } + else + { + bool bEmptyVector(rValue.equalZero()); + + if(bEmptyVector) + return; + + mpVectors = new SimpleBezierVector(maPoints.size()); + ((*mpVectors)[nIndex]).setForwardVector(rValue); + mnBezierCount++; + } + } + + void insert(sal_uInt32 nIndex, const Point& rPoint, sal_uInt32 nCount) + { + if(nCount) + { + // maybe vectors are not needed anymore, try to reduce memory footprint + implTryToReduceToPointVector(); + + // add nCount copies of rPoint + { + LocalImplSimplePointEntry aNode(rPoint); + SimplePointVector::iterator aIndex(maPoints.begin()); + aIndex += nIndex; + maPoints.insert(aIndex, nCount, aNode); + } + + // add nCount empty entries to keep indices synchronized + if(mpVectors) + { + LocalImplSimpleBezierEntry aNode; + SimpleBezierVector::iterator aIndex(mpVectors->begin()); + aIndex += nIndex; + mpVectors->insert(aIndex, nCount, aNode); + } + } + } + + void insert(sal_uInt32 nIndex, const ImplPolygonTemplate& rSource) + { + const sal_uInt32 nCount(rSource.maPoints.size()); + + if(nCount) + { + // instert point data + { + SimplePointVector::iterator aIndex(maPoints.begin()); + aIndex += nIndex; + + SimplePointVector::const_iterator aStart(rSource.maPoints.begin()); + SimplePointVector::const_iterator aEnd(rSource.maPoints.end()); + + maPoints.insert(aIndex, aStart, aEnd); + } + + // insert bezier data + if(rSource.mpVectors && rSource.mnBezierCount) + { + SimpleBezierVector::iterator aIndex(mpVectors->begin()); + aIndex += nIndex; + + SimpleBezierVector::iterator aStart(rSource.mpVectors->begin()); + SimpleBezierVector::iterator aEnd(rSource.mpVectors->end()); + + if(!mpVectors) + { + mpVectors = new SimpleBezierVector(maPoints.size() - nCount); + } + + mpVectors->insert(aIndex, aStart, aEnd); + + mnBezierCount += rSource.mnBezierCount; + } + else + { + // maybe vectors are not needed anymore, try to reduce memory footprint + implTryToReduceToPointVector(); + + // add nCount empty entries to keep indices synchronized + if(mpVectors) + { + LocalImplSimpleBezierEntry aNode; + SimpleBezierVector::iterator aIndex(mpVectors->begin()); + aIndex += nIndex; + mpVectors->insert(aIndex, nCount, aNode); + } + } + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + // maybe vectors are not needed anymore, try to reduce memory footprint + implTryToReduceToPointVector(); + + // remove point data + { + SimplePointVector::iterator aStart(maPoints.begin()); + aStart += nIndex; + const SimplePointVector::iterator aEnd(aStart + nCount); + + maPoints.erase(aStart, aEnd); + } + + // remove bezier data + if(mpVectors) + { + SimpleBezierVector::iterator aStart(mpVectors->begin()); + aStart += nIndex; + const SimpleBezierVector::iterator aEnd(aStart + nCount); + + // take care for correct mnBezierCount BEFORE erase + if(mnBezierCount) + { + SimpleBezierVector::iterator aTestIter(aStart); + + for( ; mnBezierCount && aTestIter != aEnd; ++aTestIter) + { + if(aTestIter->isBezierNeeded()) + mnBezierCount--; + } + } + + if(mnBezierCount) + { + // erase nodes + mpVectors->erase(aStart, aEnd); + } + else + { + // try to reduce, maybe 0L == mnBezierCount + implTryToReduceToPointVector(); + } + } + } + } + + void flip() + { + if(maPoints.size() > 1) + { + // maybe vectors are not needed anymore, try to reduce memory footprint + implTryToReduceToPointVector(); + + // calculate half size + const sal_uInt32 nHalfSize(maPoints.size() >> 1L); + + // flip point data + { + SimplePointVector::iterator aStart(maPoints.begin()); + SimplePointVector::iterator aEnd(maPoints.end()); + + for(sal_uInt32 a(0); a < nHalfSize; a++) + { + LocalImplSimplePointEntry aTemp = *aStart; + *aStart++ = *aEnd; + *aEnd-- = aTemp; + } + } + + // flip bezier data + if(mpVectors) + { + SimpleBezierVector::iterator aStart(mpVectors->begin()); + SimpleBezierVector::iterator aEnd(mpVectors->end()); + + for(sal_uInt32 a(0); a < nHalfSize; a++) + { + LocalImplSimpleBezierEntry aTemp = *aStart; + aTemp.doInvertForFlip(); + *aStart = *aEnd; + aStart->doInvertForFlip(); + aStart++; + *aEnd-- = aTemp; + } + + // also flip vectors of middle point (if existing) + if(maPoints.size() % 2) + { + (*mpVectors)[nHalfSize].doInvertForFlip(); + } + } + } + } +}; + +////////////////////////////////////////////////////////////////////////////// + +#endif _POLYGON_TEMPLATE_HXX diff --git a/basegfx/source/matrix/b2dhommatrix.cxx b/basegfx/source/matrix/b2dhommatrix.cxx new file mode 100644 index 000000000000..96d3bdb01c01 --- /dev/null +++ b/basegfx/source/matrix/b2dhommatrix.cxx @@ -0,0 +1,454 @@ +/************************************************************************* + * + * 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 <rtl/instance.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <hommatrixtemplate.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +/////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + class Impl2DHomMatrix : public ::basegfx::internal::ImplHomMatrixTemplate< 3 > + { + }; + + namespace { struct IdentityMatrix : public rtl::Static< B2DHomMatrix::ImplType, + IdentityMatrix > {}; } + + B2DHomMatrix::B2DHomMatrix() : + mpImpl( IdentityMatrix::get() ) // use common identity matrix + { + } + + B2DHomMatrix::B2DHomMatrix(const B2DHomMatrix& rMat) : + mpImpl(rMat.mpImpl) + { + } + + B2DHomMatrix::~B2DHomMatrix() + { + } + + B2DHomMatrix::B2DHomMatrix(double f_0x0, double f_0x1, double f_0x2, double f_1x0, double f_1x1, double f_1x2) + : mpImpl( IdentityMatrix::get() ) // use common identity matrix, will be made unique with 1st set-call + { + mpImpl->set(0, 0, f_0x0); + mpImpl->set(0, 1, f_0x1); + mpImpl->set(0, 2, f_0x2); + mpImpl->set(1, 0, f_1x0); + mpImpl->set(1, 1, f_1x1); + mpImpl->set(1, 2, f_1x2); + } + + B2DHomMatrix& B2DHomMatrix::operator=(const B2DHomMatrix& rMat) + { + mpImpl = rMat.mpImpl; + return *this; + } + + void B2DHomMatrix::makeUnique() + { + mpImpl.make_unique(); + } + + double B2DHomMatrix::get(sal_uInt16 nRow, sal_uInt16 nColumn) const + { + return mpImpl->get(nRow, nColumn); + } + + void B2DHomMatrix::set(sal_uInt16 nRow, sal_uInt16 nColumn, double fValue) + { + mpImpl->set(nRow, nColumn, fValue); + } + + void B2DHomMatrix::set3x2(double f_0x0, double f_0x1, double f_0x2, double f_1x0, double f_1x1, double f_1x2) + { + mpImpl->set(0, 0, f_0x0); + mpImpl->set(0, 1, f_0x1); + mpImpl->set(0, 2, f_0x2); + mpImpl->set(1, 0, f_1x0); + mpImpl->set(1, 1, f_1x1); + mpImpl->set(1, 2, f_1x2); + } + + bool B2DHomMatrix::isLastLineDefault() const + { + return mpImpl->isLastLineDefault(); + } + + bool B2DHomMatrix::isIdentity() const + { + if(mpImpl.same_object(IdentityMatrix::get())) + return true; + + return mpImpl->isIdentity(); + } + + void B2DHomMatrix::identity() + { + mpImpl = IdentityMatrix::get(); + } + + bool B2DHomMatrix::isInvertible() const + { + return mpImpl->isInvertible(); + } + + bool B2DHomMatrix::invert() + { + Impl2DHomMatrix aWork(*mpImpl); + sal_uInt16* pIndex = new sal_uInt16[mpImpl->getEdgeLength()]; + sal_Int16 nParity; + + if(aWork.ludcmp(pIndex, nParity)) + { + mpImpl->doInvert(aWork, pIndex); + delete[] pIndex; + + return true; + } + + delete[] pIndex; + return false; + } + + bool B2DHomMatrix::isNormalized() const + { + return mpImpl->isNormalized(); + } + + void B2DHomMatrix::normalize() + { + if(!const_cast<const B2DHomMatrix*>(this)->mpImpl->isNormalized()) + mpImpl->doNormalize(); + } + + double B2DHomMatrix::determinant() const + { + return mpImpl->doDeterminant(); + } + + double B2DHomMatrix::trace() const + { + return mpImpl->doTrace(); + } + + void B2DHomMatrix::transpose() + { + mpImpl->doTranspose(); + } + + B2DHomMatrix& B2DHomMatrix::operator+=(const B2DHomMatrix& rMat) + { + mpImpl->doAddMatrix(*rMat.mpImpl); + return *this; + } + + B2DHomMatrix& B2DHomMatrix::operator-=(const B2DHomMatrix& rMat) + { + mpImpl->doSubMatrix(*rMat.mpImpl); + return *this; + } + + B2DHomMatrix& B2DHomMatrix::operator*=(double fValue) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fValue)) + mpImpl->doMulMatrix(fValue); + + return *this; + } + + B2DHomMatrix& B2DHomMatrix::operator/=(double fValue) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fValue)) + mpImpl->doMulMatrix(1.0 / fValue); + + return *this; + } + + B2DHomMatrix& B2DHomMatrix::operator*=(const B2DHomMatrix& rMat) + { + if(!rMat.isIdentity()) + mpImpl->doMulMatrix(*rMat.mpImpl); + + return *this; + } + + bool B2DHomMatrix::operator==(const B2DHomMatrix& rMat) const + { + if(mpImpl.same_object(rMat.mpImpl)) + return true; + + return mpImpl->isEqual(*rMat.mpImpl); + } + + bool B2DHomMatrix::operator!=(const B2DHomMatrix& rMat) const + { + return !(*this == rMat); + } + + void B2DHomMatrix::rotate(double fRadiant) + { + if(!fTools::equalZero(fRadiant)) + { + double fSin(0.0); + double fCos(1.0); + + tools::createSinCosOrthogonal(fSin, fCos, fRadiant); + Impl2DHomMatrix aRotMat; + + aRotMat.set(0, 0, fCos); + aRotMat.set(1, 1, fCos); + aRotMat.set(1, 0, fSin); + aRotMat.set(0, 1, -fSin); + + mpImpl->doMulMatrix(aRotMat); + } + } + + void B2DHomMatrix::translate(double fX, double fY) + { + if(!fTools::equalZero(fX) || !fTools::equalZero(fY)) + { + Impl2DHomMatrix aTransMat; + + aTransMat.set(0, 2, fX); + aTransMat.set(1, 2, fY); + + mpImpl->doMulMatrix(aTransMat); + } + } + + void B2DHomMatrix::scale(double fX, double fY) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fX) || !fTools::equal(fOne, fY)) + { + Impl2DHomMatrix aScaleMat; + + aScaleMat.set(0, 0, fX); + aScaleMat.set(1, 1, fY); + + mpImpl->doMulMatrix(aScaleMat); + } + } + + void B2DHomMatrix::shearX(double fSx) + { + // #i76239# do not test againt 1.0, but against 0.0. We are talking about a value not on the diagonal (!) + if(!fTools::equalZero(fSx)) + { + Impl2DHomMatrix aShearXMat; + + aShearXMat.set(0, 1, fSx); + + mpImpl->doMulMatrix(aShearXMat); + } + } + + void B2DHomMatrix::shearY(double fSy) + { + // #i76239# do not test againt 1.0, but against 0.0. We are talking about a value not on the diagonal (!) + if(!fTools::equalZero(fSy)) + { + Impl2DHomMatrix aShearYMat; + + aShearYMat.set(1, 0, fSy); + + mpImpl->doMulMatrix(aShearYMat); + } + } + + /** Decomposition + + New, optimized version with local shearX detection. Old version (keeping + below, is working well, too) used the 3D matrix decomposition when + shear was used. Keeping old version as comment below since it may get + necessary to add the determinant() test from there here, too. + */ + bool B2DHomMatrix::decompose(B2DTuple& rScale, B2DTuple& rTranslate, double& rRotate, double& rShearX) const + { + // when perspective is used, decompose is not made here + if(!mpImpl->isLastLineDefault()) + { + return false; + } + + // reset rotate and shear and copy translation values in every case + rRotate = rShearX = 0.0; + rTranslate.setX(get(0, 2)); + rTranslate.setY(get(1, 2)); + + // test for rotation and shear + if(fTools::equalZero(get(0, 1)) && fTools::equalZero(get(1, 0))) + { + // no rotation and shear, copy scale values + rScale.setX(get(0, 0)); + rScale.setY(get(1, 1)); + } + else + { + // get the unit vectors of the transformation -> the perpendicular vectors + B2DVector aUnitVecX(get(0, 0), get(1, 0)); + B2DVector aUnitVecY(get(0, 1), get(1, 1)); + const double fScalarXY(aUnitVecX.scalar(aUnitVecY)); + + // Test if shear is zero. That's the case if the unit vectors in the matrix + // are perpendicular -> scalar is zero. This is also the case when one of + // the unit vectors is zero. + if(fTools::equalZero(fScalarXY)) + { + // calculate unsigned scale values + rScale.setX(aUnitVecX.getLength()); + rScale.setY(aUnitVecY.getLength()); + + // check unit vectors for zero lengths + const bool bXIsZero(fTools::equalZero(rScale.getX())); + const bool bYIsZero(fTools::equalZero(rScale.getY())); + + if(bXIsZero || bYIsZero) + { + // still extract as much as possible. Scalings are already set + if(!bXIsZero) + { + // get rotation of X-Axis + rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX()); + } + else if(!bYIsZero) + { + // get rotation of X-Axis. When assuming X and Y perpendicular + // and correct rotation, it's the Y-Axis rotation minus 90 degrees + rRotate = atan2(aUnitVecY.getY(), aUnitVecY.getX()) - M_PI_2; + } + + // one or both unit vectors do not extist, determinant is zero, no decomposition possible. + // Eventually used rotations or shears are lost + return false; + } + else + { + // no shear + // calculate rotation of X unit vector relative to (1, 0) + rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX()); + + // use orientation to evtl. correct sign of Y-Scale + const double fCrossXY(aUnitVecX.cross(aUnitVecY)); + + if(fCrossXY < 0.0) + { + rScale.setY(-rScale.getY()); + } + } + } + else + { + // fScalarXY is not zero, thus both unit vectors exist. No need to handle that here + // shear, extract it + double fCrossXY(aUnitVecX.cross(aUnitVecY)); + + // get rotation by calculating angle of X unit vector relative to (1, 0). + // This is before the parallell test following the motto to extract + // as much as possible + rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX()); + + // get unsigned scale value for X. It will not change and is useful + // for further corrections + rScale.setX(aUnitVecX.getLength()); + + if(fTools::equalZero(fCrossXY)) + { + // extract as much as possible + rScale.setY(aUnitVecY.getLength()); + + // unit vectors are parallel, thus not linear independent. No + // useful decomposition possible. This should not happen since + // the only way to get the unit vectors nearly parallell is + // a very big shearing. Anyways, be prepared for hand-filled + // matrices + // Eventually used rotations or shears are lost + return false; + } + else + { + // calculate the contained shear + rShearX = fScalarXY / fCrossXY; + + if(!fTools::equalZero(rRotate)) + { + // To be able to correct the shear for aUnitVecY, rotation needs to be + // removed first. Correction of aUnitVecX is easy, it will be rotated back to (1, 0). + aUnitVecX.setX(rScale.getX()); + aUnitVecX.setY(0.0); + + // for Y correction we rotate the UnitVecY back about -rRotate + const double fNegRotate(-rRotate); + const double fSin(sin(fNegRotate)); + const double fCos(cos(fNegRotate)); + + const double fNewX(aUnitVecY.getX() * fCos - aUnitVecY.getY() * fSin); + const double fNewY(aUnitVecY.getX() * fSin + aUnitVecY.getY() * fCos); + + aUnitVecY.setX(fNewX); + aUnitVecY.setY(fNewY); + } + + // Correct aUnitVecY and fCrossXY to fShear=0. Rotation is already removed. + // Shear correction can only work with removed rotation + aUnitVecY.setX(aUnitVecY.getX() - (aUnitVecY.getY() * rShearX)); + fCrossXY = aUnitVecX.cross(aUnitVecY); + + // calculate unsigned scale value for Y, after the corrections since + // the shear correction WILL change the length of aUnitVecY + rScale.setY(aUnitVecY.getLength()); + + // use orientation to set sign of Y-Scale + if(fCrossXY < 0.0) + { + rScale.setY(-rScale.getY()); + } + } + } + } + + return true; + } +} // end of namespace basegfx + +/////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/matrix/b2dhommatrixtools.cxx b/basegfx/source/matrix/b2dhommatrixtools.cxx new file mode 100644 index 000000000000..0b85ee229ecc --- /dev/null +++ b/basegfx/source/matrix/b2dhommatrixtools.cxx @@ -0,0 +1,401 @@ +/************************************************************************* + * + * 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/matrix/b2dhommatrixtools.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> + +/////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + ::rtl::OUString exportToSvg( const B2DHomMatrix& rMatrix ) + { + rtl::OUStringBuffer aStrBuf; + aStrBuf.appendAscii("matrix("); + + aStrBuf.append(rMatrix.get(0,0)); + aStrBuf.appendAscii(", "); + + aStrBuf.append(rMatrix.get(1,0)); + aStrBuf.appendAscii(", "); + + aStrBuf.append(rMatrix.get(0,1)); + aStrBuf.appendAscii(", "); + + aStrBuf.append(rMatrix.get(1,1)); + aStrBuf.appendAscii(", "); + + aStrBuf.append(rMatrix.get(0,2)); + aStrBuf.appendAscii(", "); + + aStrBuf.append(rMatrix.get(1,2)); + aStrBuf.appendAscii(")"); + + return aStrBuf.makeStringAndClear(); + } + + namespace tools + { + void createSinCosOrthogonal(double& o_rSin, double& o_rCos, double fRadiant) + { + if( fTools::equalZero( fmod( fRadiant, F_PI2 ) ) ) + { + // determine quadrant + const sal_Int32 nQuad( + (4 + fround( 4/F_2PI*fmod( fRadiant, F_2PI ) )) % 4 ); + switch( nQuad ) + { + case 0: // -2pi,0,2pi + o_rSin = 0.0; + o_rCos = 1.0; + break; + + case 1: // -3/2pi,1/2pi + o_rSin = 1.0; + o_rCos = 0.0; + break; + + case 2: // -pi,pi + o_rSin = 0.0; + o_rCos = -1.0; + break; + + case 3: // -1/2pi,3/2pi + o_rSin = -1.0; + o_rCos = 0.0; + break; + + default: + OSL_ENSURE( false, "createSinCos: Impossible case reached" ); + } + } + else + { + // TODO(P1): Maybe use glibc's sincos here (though + // that's kinda non-portable...) + o_rSin = sin(fRadiant); + o_rCos = cos(fRadiant); + } + } + + B2DHomMatrix createScaleB2DHomMatrix(double fScaleX, double fScaleY) + { + B2DHomMatrix aRetval; + const double fOne(1.0); + + if(!fTools::equal(fScaleX, fOne)) + { + aRetval.set(0, 0, fScaleX); + } + + if(!fTools::equal(fScaleY, fOne)) + { + aRetval.set(1, 1, fScaleY); + } + + return aRetval; + } + + B2DHomMatrix createShearXB2DHomMatrix(double fShearX) + { + B2DHomMatrix aRetval; + + if(!fTools::equalZero(fShearX)) + { + aRetval.set(0, 1, fShearX); + } + + return aRetval; + } + + B2DHomMatrix createShearYB2DHomMatrix(double fShearY) + { + B2DHomMatrix aRetval; + + if(!fTools::equalZero(fShearY)) + { + aRetval.set(1, 0, fShearY); + } + + return aRetval; + } + + B2DHomMatrix createRotateB2DHomMatrix(double fRadiant) + { + B2DHomMatrix aRetval; + + if(!fTools::equalZero(fRadiant)) + { + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + aRetval.set(0, 0, fCos); + aRetval.set(1, 1, fCos); + aRetval.set(1, 0, fSin); + aRetval.set(0, 1, -fSin); + } + + return aRetval; + } + + B2DHomMatrix createTranslateB2DHomMatrix(double fTranslateX, double fTranslateY) + { + B2DHomMatrix aRetval; + + if(!(fTools::equalZero(fTranslateX) && fTools::equalZero(fTranslateY))) + { + aRetval.set(0, 2, fTranslateX); + aRetval.set(1, 2, fTranslateY); + } + + return aRetval; + } + + B2DHomMatrix createScaleShearXRotateTranslateB2DHomMatrix( + double fScaleX, double fScaleY, + double fShearX, + double fRadiant, + double fTranslateX, double fTranslateY) + { + const double fOne(1.0); + + if(fTools::equal(fScaleX, fOne) && fTools::equal(fScaleY, fOne)) + { + /// no scale, take shortcut + return createShearXRotateTranslateB2DHomMatrix(fShearX, fRadiant, fTranslateX, fTranslateY); + } + else + { + /// scale used + if(fTools::equalZero(fShearX)) + { + /// no shear + if(fTools::equalZero(fRadiant)) + { + /// no rotate, take shortcut + return createScaleTranslateB2DHomMatrix(fScaleX, fScaleY, fTranslateX, fTranslateY); + } + else + { + /// rotate and scale used, no shear + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fCos * fScaleX, + /* Row 0, Column 1 */ fScaleY * -fSin, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ fSin * fScaleX, + /* Row 1, Column 1 */ fScaleY * fCos, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + } + else + { + /// scale and shear used + if(fTools::equalZero(fRadiant)) + { + /// scale and shear, but no rotate + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fScaleX, + /* Row 0, Column 1 */ fScaleY * fShearX, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ 0.0, + /* Row 1, Column 1 */ fScaleY, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + else + { + /// scale, shear and rotate used + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fCos * fScaleX, + /* Row 0, Column 1 */ fScaleY * ((fCos * fShearX) - fSin), + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ fSin * fScaleX, + /* Row 1, Column 1 */ fScaleY * ((fSin * fShearX) + fCos), + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + } + } + } + + B2DHomMatrix createShearXRotateTranslateB2DHomMatrix( + double fShearX, + double fRadiant, + double fTranslateX, double fTranslateY) + { + if(fTools::equalZero(fShearX)) + { + /// no shear + if(fTools::equalZero(fRadiant)) + { + /// no shear, no rotate, take shortcut + return createTranslateB2DHomMatrix(fTranslateX, fTranslateY); + } + else + { + /// no shear, but rotate used + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fCos, + /* Row 0, Column 1 */ -fSin, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ fSin, + /* Row 1, Column 1 */ fCos, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + } + else + { + /// shear used + if(fTools::equalZero(fRadiant)) + { + /// no rotate, but shear used + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ 1.0, + /* Row 0, Column 1 */ fShearX, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ 0.0, + /* Row 1, Column 1 */ 1.0, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + else + { + /// shear and rotate used + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fCos, + /* Row 0, Column 1 */ (fCos * fShearX) - fSin, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ fSin, + /* Row 1, Column 1 */ (fSin * fShearX) + fCos, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + } + } + + B2DHomMatrix createScaleTranslateB2DHomMatrix( + double fScaleX, double fScaleY, + double fTranslateX, double fTranslateY) + { + const double fOne(1.0); + + if(fTools::equal(fScaleX, fOne) && fTools::equal(fScaleY, fOne)) + { + /// no scale, take shortcut + return createTranslateB2DHomMatrix(fTranslateX, fTranslateY); + } + else + { + /// scale used + if(fTools::equalZero(fTranslateX) && fTools::equalZero(fTranslateY)) + { + /// no translate, but scale. + B2DHomMatrix aRetval; + + aRetval.set(0, 0, fScaleX); + aRetval.set(1, 1, fScaleY); + + return aRetval; + } + else + { + /// translate and scale + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fScaleX, + /* Row 0, Column 1 */ 0.0, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ 0.0, + /* Row 1, Column 1 */ fScaleY, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + } + } + + B2DHomMatrix createRotateAroundPoint( + double fPointX, double fPointY, + double fRadiant) + { + B2DHomMatrix aRetval; + + if(!fTools::equalZero(fRadiant)) + { + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + + aRetval.set3x2( + /* Row 0, Column 0 */ fCos, + /* Row 0, Column 1 */ -fSin, + /* Row 0, Column 2 */ (fPointX * (1.0 - fCos)) + (fSin * fPointY), + /* Row 1, Column 0 */ fSin, + /* Row 1, Column 1 */ fCos, + /* Row 1, Column 2 */ (fPointY * (1.0 - fCos)) - (fSin * fPointX)); + } + + return aRetval; + } + } // end of namespace tools +} // end of namespace basegfx + +/////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/matrix/b3dhommatrix.cxx b/basegfx/source/matrix/b3dhommatrix.cxx new file mode 100644 index 000000000000..bc3c3b0b55dd --- /dev/null +++ b/basegfx/source/matrix/b3dhommatrix.cxx @@ -0,0 +1,596 @@ +/************************************************************************* + * + * 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 <rtl/instance.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <hommatrixtemplate.hxx> +#include <basegfx/vector/b3dvector.hxx> + +namespace basegfx +{ + class Impl3DHomMatrix : public ::basegfx::internal::ImplHomMatrixTemplate< 4 > + { + }; + + namespace { struct IdentityMatrix : public rtl::Static< B3DHomMatrix::ImplType, + IdentityMatrix > {}; } + + B3DHomMatrix::B3DHomMatrix() : + mpImpl( IdentityMatrix::get() ) // use common identity matrix + { + } + + B3DHomMatrix::B3DHomMatrix(const B3DHomMatrix& rMat) : + mpImpl(rMat.mpImpl) + { + } + + B3DHomMatrix::~B3DHomMatrix() + { + } + + B3DHomMatrix& B3DHomMatrix::operator=(const B3DHomMatrix& rMat) + { + mpImpl = rMat.mpImpl; + return *this; + } + + void B3DHomMatrix::makeUnique() + { + mpImpl.make_unique(); + } + + double B3DHomMatrix::get(sal_uInt16 nRow, sal_uInt16 nColumn) const + { + return mpImpl->get(nRow, nColumn); + } + + void B3DHomMatrix::set(sal_uInt16 nRow, sal_uInt16 nColumn, double fValue) + { + mpImpl->set(nRow, nColumn, fValue); + } + + bool B3DHomMatrix::isLastLineDefault() const + { + return mpImpl->isLastLineDefault(); + } + + bool B3DHomMatrix::isIdentity() const + { + if(mpImpl.same_object(IdentityMatrix::get())) + return true; + + return mpImpl->isIdentity(); + } + + void B3DHomMatrix::identity() + { + mpImpl = IdentityMatrix::get(); + } + + bool B3DHomMatrix::isInvertible() const + { + return mpImpl->isInvertible(); + } + + bool B3DHomMatrix::invert() + { + Impl3DHomMatrix aWork(*mpImpl); + sal_uInt16* pIndex = new sal_uInt16[mpImpl->getEdgeLength()]; + sal_Int16 nParity; + + if(aWork.ludcmp(pIndex, nParity)) + { + mpImpl->doInvert(aWork, pIndex); + delete[] pIndex; + + return true; + } + + delete[] pIndex; + return false; + } + + bool B3DHomMatrix::isNormalized() const + { + return mpImpl->isNormalized(); + } + + void B3DHomMatrix::normalize() + { + if(!const_cast<const B3DHomMatrix*>(this)->mpImpl->isNormalized()) + mpImpl->doNormalize(); + } + + double B3DHomMatrix::determinant() const + { + return mpImpl->doDeterminant(); + } + + double B3DHomMatrix::trace() const + { + return mpImpl->doTrace(); + } + + void B3DHomMatrix::transpose() + { + mpImpl->doTranspose(); + } + + B3DHomMatrix& B3DHomMatrix::operator+=(const B3DHomMatrix& rMat) + { + mpImpl->doAddMatrix(*rMat.mpImpl); + return *this; + } + + B3DHomMatrix& B3DHomMatrix::operator-=(const B3DHomMatrix& rMat) + { + mpImpl->doSubMatrix(*rMat.mpImpl); + return *this; + } + + B3DHomMatrix& B3DHomMatrix::operator*=(double fValue) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fValue)) + mpImpl->doMulMatrix(fValue); + + return *this; + } + + B3DHomMatrix& B3DHomMatrix::operator/=(double fValue) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fValue)) + mpImpl->doMulMatrix(1.0 / fValue); + + return *this; + } + + B3DHomMatrix& B3DHomMatrix::operator*=(const B3DHomMatrix& rMat) + { + if(!rMat.isIdentity()) + mpImpl->doMulMatrix(*rMat.mpImpl); + + return *this; + } + + bool B3DHomMatrix::operator==(const B3DHomMatrix& rMat) const + { + if(mpImpl.same_object(rMat.mpImpl)) + return true; + + return mpImpl->isEqual(*rMat.mpImpl); + } + + bool B3DHomMatrix::operator!=(const B3DHomMatrix& rMat) const + { + return !(*this == rMat); + } + + void B3DHomMatrix::rotate(double fAngleX,double fAngleY,double fAngleZ) + { + if(!fTools::equalZero(fAngleX) || !fTools::equalZero(fAngleY) || !fTools::equalZero(fAngleZ)) + { + if(!fTools::equalZero(fAngleX)) + { + Impl3DHomMatrix aRotMatX; + double fSin(sin(fAngleX)); + double fCos(cos(fAngleX)); + + aRotMatX.set(1, 1, fCos); + aRotMatX.set(2, 2, fCos); + aRotMatX.set(2, 1, fSin); + aRotMatX.set(1, 2, -fSin); + + mpImpl->doMulMatrix(aRotMatX); + } + + if(!fTools::equalZero(fAngleY)) + { + Impl3DHomMatrix aRotMatY; + double fSin(sin(fAngleY)); + double fCos(cos(fAngleY)); + + aRotMatY.set(0, 0, fCos); + aRotMatY.set(2, 2, fCos); + aRotMatY.set(0, 2, fSin); + aRotMatY.set(2, 0, -fSin); + + mpImpl->doMulMatrix(aRotMatY); + } + + if(!fTools::equalZero(fAngleZ)) + { + Impl3DHomMatrix aRotMatZ; + double fSin(sin(fAngleZ)); + double fCos(cos(fAngleZ)); + + aRotMatZ.set(0, 0, fCos); + aRotMatZ.set(1, 1, fCos); + aRotMatZ.set(1, 0, fSin); + aRotMatZ.set(0, 1, -fSin); + + mpImpl->doMulMatrix(aRotMatZ); + } + } + } + + void B3DHomMatrix::translate(double fX, double fY, double fZ) + { + if(!fTools::equalZero(fX) || !fTools::equalZero(fY) || !fTools::equalZero(fZ)) + { + Impl3DHomMatrix aTransMat; + + aTransMat.set(0, 3, fX); + aTransMat.set(1, 3, fY); + aTransMat.set(2, 3, fZ); + + mpImpl->doMulMatrix(aTransMat); + } + } + + void B3DHomMatrix::scale(double fX, double fY, double fZ) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fX) || !fTools::equal(fOne, fY) ||!fTools::equal(fOne, fZ)) + { + Impl3DHomMatrix aScaleMat; + + aScaleMat.set(0, 0, fX); + aScaleMat.set(1, 1, fY); + aScaleMat.set(2, 2, fZ); + + mpImpl->doMulMatrix(aScaleMat); + } + } + + void B3DHomMatrix::shearXY(double fSx, double fSy) + { + // #i76239# do not test againt 1.0, but against 0.0. We are talking about a value not on the diagonal (!) + if(!fTools::equalZero(fSx) || !fTools::equalZero(fSy)) + { + Impl3DHomMatrix aShearXYMat; + + aShearXYMat.set(0, 2, fSx); + aShearXYMat.set(1, 2, fSy); + + mpImpl->doMulMatrix(aShearXYMat); + } + } + + void B3DHomMatrix::shearYZ(double fSy, double fSz) + { + // #i76239# do not test againt 1.0, but against 0.0. We are talking about a value not on the diagonal (!) + if(!fTools::equalZero(fSy) || !fTools::equalZero(fSz)) + { + Impl3DHomMatrix aShearYZMat; + + aShearYZMat.set(1, 0, fSy); + aShearYZMat.set(2, 0, fSz); + + mpImpl->doMulMatrix(aShearYZMat); + } + } + + void B3DHomMatrix::shearXZ(double fSx, double fSz) + { + // #i76239# do not test againt 1.0, but against 0.0. We are talking about a value not on the diagonal (!) + if(!fTools::equalZero(fSx) || !fTools::equalZero(fSz)) + { + Impl3DHomMatrix aShearXZMat; + + aShearXZMat.set(0, 1, fSx); + aShearXZMat.set(2, 1, fSz); + + mpImpl->doMulMatrix(aShearXZMat); + } + } + + void B3DHomMatrix::frustum(double fLeft, double fRight, double fBottom, double fTop, double fNear, double fFar) + { + const double fZero(0.0); + const double fOne(1.0); + + if(!fTools::more(fNear, fZero)) + { + fNear = 0.001; + } + + if(!fTools::more(fFar, fZero)) + { + fFar = fOne; + } + + if(fTools::equal(fNear, fFar)) + { + fFar = fNear + fOne; + } + + if(fTools::equal(fLeft, fRight)) + { + fLeft -= fOne; + fRight += fOne; + } + + if(fTools::equal(fTop, fBottom)) + { + fBottom -= fOne; + fTop += fOne; + } + + Impl3DHomMatrix aFrustumMat; + + aFrustumMat.set(0, 0, 2.0 * fNear / (fRight - fLeft)); + aFrustumMat.set(1, 1, 2.0 * fNear / (fTop - fBottom)); + aFrustumMat.set(0, 2, (fRight + fLeft) / (fRight - fLeft)); + aFrustumMat.set(1, 2, (fTop + fBottom) / (fTop - fBottom)); + aFrustumMat.set(2, 2, -fOne * ((fFar + fNear) / (fFar - fNear))); + aFrustumMat.set(3, 2, -fOne); + aFrustumMat.set(2, 3, -fOne * ((2.0 * fFar * fNear) / (fFar - fNear))); + aFrustumMat.set(3, 3, fZero); + + mpImpl->doMulMatrix(aFrustumMat); + } + + void B3DHomMatrix::ortho(double fLeft, double fRight, double fBottom, double fTop, double fNear, double fFar) + { + if(fTools::equal(fNear, fFar)) + { + fFar = fNear + 1.0; + } + + if(fTools::equal(fLeft, fRight)) + { + fLeft -= 1.0; + fRight += 1.0; + } + + if(fTools::equal(fTop, fBottom)) + { + fBottom -= 1.0; + fTop += 1.0; + } + + Impl3DHomMatrix aOrthoMat; + + aOrthoMat.set(0, 0, 2.0 / (fRight - fLeft)); + aOrthoMat.set(1, 1, 2.0 / (fTop - fBottom)); + aOrthoMat.set(2, 2, -1.0 * (2.0 / (fFar - fNear))); + aOrthoMat.set(0, 3, -1.0 * ((fRight + fLeft) / (fRight - fLeft))); + aOrthoMat.set(1, 3, -1.0 * ((fTop + fBottom) / (fTop - fBottom))); + aOrthoMat.set(2, 3, -1.0 * ((fFar + fNear) / (fFar - fNear))); + + mpImpl->doMulMatrix(aOrthoMat); + } + + void B3DHomMatrix::orientation(B3DPoint aVRP, B3DVector aVPN, B3DVector aVUV) + { + Impl3DHomMatrix aOrientationMat; + + // translate -VRP + aOrientationMat.set(0, 3, -aVRP.getX()); + aOrientationMat.set(1, 3, -aVRP.getY()); + aOrientationMat.set(2, 3, -aVRP.getZ()); + + // build rotation + aVUV.normalize(); + aVPN.normalize(); + + // build x-axis as peroendicular fron aVUV and aVPN + B3DVector aRx(aVUV.getPerpendicular(aVPN)); + aRx.normalize(); + + // y-axis perpendicular to that + B3DVector aRy(aVPN.getPerpendicular(aRx)); + aRy.normalize(); + + // the calculated normals are the line vectors of the rotation matrix, + // set them to create rotation + aOrientationMat.set(0, 0, aRx.getX()); + aOrientationMat.set(0, 1, aRx.getY()); + aOrientationMat.set(0, 2, aRx.getZ()); + aOrientationMat.set(1, 0, aRy.getX()); + aOrientationMat.set(1, 1, aRy.getY()); + aOrientationMat.set(1, 2, aRy.getZ()); + aOrientationMat.set(2, 0, aVPN.getX()); + aOrientationMat.set(2, 1, aVPN.getY()); + aOrientationMat.set(2, 2, aVPN.getZ()); + + mpImpl->doMulMatrix(aOrientationMat); + } + + bool B3DHomMatrix::decompose(B3DTuple& rScale, B3DTuple& rTranslate, B3DTuple& rRotate, B3DTuple& rShear) const + { + // when perspective is used, decompose is not made here + if(!mpImpl->isLastLineDefault()) + return false; + + // If determinant is zero, decomposition is not possible + if(0.0 == determinant()) + return false; + + // isolate translation + rTranslate.setX(mpImpl->get(0, 3)); + rTranslate.setY(mpImpl->get(1, 3)); + rTranslate.setZ(mpImpl->get(2, 3)); + + // correct translate values + rTranslate.correctValues(); + + // get scale and shear + B3DVector aCol0(mpImpl->get(0, 0), mpImpl->get(1, 0), mpImpl->get(2, 0)); + B3DVector aCol1(mpImpl->get(0, 1), mpImpl->get(1, 1), mpImpl->get(2, 1)); + B3DVector aCol2(mpImpl->get(0, 2), mpImpl->get(1, 2), mpImpl->get(2, 2)); + B3DVector aTemp; + + // get ScaleX + rScale.setX(aCol0.getLength()); + aCol0.normalize(); + + // get ShearXY + rShear.setX(aCol0.scalar(aCol1)); + + if(fTools::equalZero(rShear.getX())) + { + rShear.setX(0.0); + } + else + { + aTemp.setX(aCol1.getX() - rShear.getX() * aCol0.getX()); + aTemp.setY(aCol1.getY() - rShear.getX() * aCol0.getY()); + aTemp.setZ(aCol1.getZ() - rShear.getX() * aCol0.getZ()); + aCol1 = aTemp; + } + + // get ScaleY + rScale.setY(aCol1.getLength()); + aCol1.normalize(); + + const double fShearX(rShear.getX()); + + if(!fTools::equalZero(fShearX)) + { + rShear.setX(rShear.getX() / rScale.getY()); + } + + // get ShearXZ + rShear.setY(aCol0.scalar(aCol2)); + + if(fTools::equalZero(rShear.getY())) + { + rShear.setY(0.0); + } + else + { + aTemp.setX(aCol2.getX() - rShear.getY() * aCol0.getX()); + aTemp.setY(aCol2.getY() - rShear.getY() * aCol0.getY()); + aTemp.setZ(aCol2.getZ() - rShear.getY() * aCol0.getZ()); + aCol2 = aTemp; + } + + // get ShearYZ + rShear.setZ(aCol1.scalar(aCol2)); + + if(fTools::equalZero(rShear.getZ())) + { + rShear.setZ(0.0); + } + else + { + aTemp.setX(aCol2.getX() - rShear.getZ() * aCol1.getX()); + aTemp.setY(aCol2.getY() - rShear.getZ() * aCol1.getY()); + aTemp.setZ(aCol2.getZ() - rShear.getZ() * aCol1.getZ()); + aCol2 = aTemp; + } + + // get ScaleZ + rScale.setZ(aCol2.getLength()); + aCol2.normalize(); + + const double fShearY(rShear.getY()); + + if(!fTools::equalZero(fShearY)) + { + rShear.setY(rShear.getY() / rScale.getZ()); + } + + const double fShearZ(rShear.getZ()); + + if(!fTools::equalZero(fShearZ)) + { + rShear.setZ(rShear.getZ() / rScale.getZ()); + } + + // correct shear values + rShear.correctValues(); + + // Coordinate system flip? + if(0.0 > aCol0.scalar(aCol1.getPerpendicular(aCol2))) + { + rScale = -rScale; + aCol0 = -aCol0; + aCol1 = -aCol1; + aCol2 = -aCol2; + } + + // correct scale values + rScale.correctValues(1.0); + + // Get rotations + { + double fy=0; + double cy=0; + + if( ::basegfx::fTools::equal( aCol0.getZ(), 1.0 ) + || aCol0.getZ() > 1.0 ) + { + fy = -F_PI/2.0; + cy = 0.0; + } + else if( ::basegfx::fTools::equal( aCol0.getZ(), -1.0 ) + || aCol0.getZ() < -1.0 ) + { + fy = F_PI/2.0; + cy = 0.0; + } + else + { + fy = asin( -aCol0.getZ() ); + cy = cos(fy); + } + + rRotate.setY(fy); + if( ::basegfx::fTools::equalZero( cy ) ) + { + if( aCol0.getZ() > 0.0 ) + rRotate.setX(atan2(-1.0*aCol1.getX(), aCol1.getY())); + else + rRotate.setX(atan2(aCol1.getX(), aCol1.getY())); + rRotate.setZ(0.0); + } + else + { + rRotate.setX(atan2(aCol1.getZ(), aCol2.getZ())); + rRotate.setZ(atan2(aCol0.getY(), aCol0.getX())); + } + + // corrcet rotate values + rRotate.correctValues(); + } + + return true; + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/matrix/makefile.mk b/basegfx/source/matrix/makefile.mk new file mode 100644 index 000000000000..35ea4d0cb86f --- /dev/null +++ b/basegfx/source/matrix/makefile.mk @@ -0,0 +1,49 @@ +#************************************************************************* +# +# 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=matrix + +#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb +#ENABLE_EXCEPTIONS=FALSE +#USE_DEFFILE=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= \ + $(SLO)$/b2dhommatrix.obj \ + $(SLO)$/b2dhommatrixtools.obj \ + $(SLO)$/b3dhommatrix.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/numeric/ftools.cxx b/basegfx/source/numeric/ftools.cxx new file mode 100644 index 000000000000..3111b26a1818 --- /dev/null +++ b/basegfx/source/numeric/ftools.cxx @@ -0,0 +1,38 @@ +/************************************************************************* + * + * 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> + +namespace basegfx +{ + // init static member of class fTools + double ::basegfx::fTools::mfSmallValue = 0.000000001; +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/numeric/makefile.mk b/basegfx/source/numeric/makefile.mk new file mode 100644 index 000000000000..83abbbde4d82 --- /dev/null +++ b/basegfx/source/numeric/makefile.mk @@ -0,0 +1,47 @@ +#************************************************************************* +# +# 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=numeric + +#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb +#ENABLE_EXCEPTIONS=FALSE +#USE_DEFFILE=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= \ + $(SLO)$/ftools.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/pixel/bpixel.cxx b/basegfx/source/pixel/bpixel.cxx new file mode 100644 index 000000000000..57de666787ab --- /dev/null +++ b/basegfx/source/pixel/bpixel.cxx @@ -0,0 +1,51 @@ +/************************************************************************* + * + * 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/pixel/bpixel.hxx> +#include <rtl/instance.hxx> + +namespace { struct EmptyBPixel : public rtl::Static<basegfx::BPixel, EmptyBPixel> {}; } + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + const BPixel& BPixel::getEmptyBPixel() + { + return EmptyBPixel::get(); + } + + ////////////////////////////////////////////////////////////////////////// + // external operators + +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/pixel/makefile.mk b/basegfx/source/pixel/makefile.mk new file mode 100644 index 000000000000..75192dc5c180 --- /dev/null +++ b/basegfx/source/pixel/makefile.mk @@ -0,0 +1,47 @@ +#************************************************************************* +# +# 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=pixel + +#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb +#ENABLE_EXCEPTIONS=FALSE +#USE_DEFFILE=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= \ + $(SLO)$/bpixel.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/point/b2dhompoint.cxx b/basegfx/source/point/b2dhompoint.cxx new file mode 100644 index 000000000000..979fbd4cdd3b --- /dev/null +++ b/basegfx/source/point/b2dhompoint.cxx @@ -0,0 +1,259 @@ +/************************************************************************* + * + * 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/point/b2dhompoint.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> + +namespace basegfx +{ + bool B2DHomPoint::implIsHomogenized() const + { + const double fOne(1.0); + return ::basegfx::fTools::equal(fOne, mfW); + } + + void B2DHomPoint::implHomogenize() + { + const double fFactor(1.0 / mfW); + maTuple.setX(maTuple.getX() * fFactor); + maTuple.setY(maTuple.getY() * fFactor); + mfW = 1.0; + } + + void B2DHomPoint::implTestAndHomogenize() const + { + if(!implIsHomogenized()) + ((B2DHomPoint*)this)->implHomogenize(); + } + + B2DPoint B2DHomPoint::getB2DPoint() const + { + implTestAndHomogenize(); + return B2DPoint(maTuple.getX(), maTuple.getY()); + } + + double B2DHomPoint::getX() const + { + implTestAndHomogenize(); + return maTuple.getX(); + } + + double B2DHomPoint::getY() const + { + implTestAndHomogenize(); + return maTuple.getY(); + } + + void B2DHomPoint::setX(double fX) + { + maTuple.setX(implIsHomogenized() ? fX : fX * mfW ); + } + + void B2DHomPoint::setY(double fY) + { + maTuple.setY(implIsHomogenized() ? fY : fY * mfW ); + } + + B2DHomPoint& B2DHomPoint::operator+=( const B2DHomPoint& rPnt ) + { + maTuple.setX(getX() * rPnt.mfW + rPnt.getX() * mfW); + maTuple.setY(getY() * rPnt.mfW + rPnt.getY() * mfW); + mfW = mfW * rPnt.mfW; + + return *this; + } + + B2DHomPoint& B2DHomPoint::operator-=( const B2DHomPoint& rPnt ) + { + maTuple.setX(getX() * rPnt.mfW - rPnt.getX() * mfW); + maTuple.setY(getY() * rPnt.mfW - rPnt.getY() * mfW); + mfW = mfW * rPnt.mfW; + + return *this; + } + + B2DHomPoint& B2DHomPoint::operator*=(double t) + { + if(!::basegfx::fTools::equalZero(t)) + { + mfW /= t; + } + + return *this; + } + + B2DHomPoint& B2DHomPoint::operator*=( const B2DHomMatrix& rMat ) + { + const double fTempX( rMat.get(0,0)*maTuple.getX() + + rMat.get(0,1)*maTuple.getY() + + rMat.get(0,2)*mfW ); + + const double fTempY( rMat.get(1,0)*maTuple.getX() + + rMat.get(1,1)*maTuple.getY() + + rMat.get(1,2)*mfW ); + + const double fTempZ( rMat.get(2,0)*maTuple.getX() + + rMat.get(2,1)*maTuple.getY() + + rMat.get(2,2)*mfW ); + maTuple.setX( fTempX ); + maTuple.setY( fTempY ); + mfW = fTempZ; + + return *this; + } + + B2DHomPoint& B2DHomPoint::operator/=(double t) + { + mfW *= t; + return *this; + } + + B2DHomPoint& B2DHomPoint::operator-(void) + { + mfW = -mfW; + return *this; + } + + bool B2DHomPoint::operator==( const B2DHomPoint& rPnt ) const + { + implTestAndHomogenize(); + return (maTuple == rPnt.maTuple); + } + + bool B2DHomPoint::operator!=( const B2DHomPoint& rPnt ) const + { + implTestAndHomogenize(); + return (maTuple != rPnt.maTuple); + } + + B2DHomPoint& B2DHomPoint::operator=( const B2DHomPoint& rPnt ) + { + maTuple = rPnt.maTuple; + mfW = rPnt.mfW; + return *this; + } + + B2DHomPoint minimum(const B2DHomPoint& rVecA, const B2DHomPoint& rVecB) + { + B2DHomPoint aMin( + (rVecB.getX() < rVecA.getX()) ? rVecB.getX() : rVecA.getX(), + (rVecB.getY() < rVecA.getY()) ? rVecB.getY() : rVecA.getY()); + return aMin; + } + + B2DHomPoint maximum(const B2DHomPoint& rVecA, const B2DHomPoint& rVecB) + { + B2DHomPoint aMax( + (rVecB.getX() > rVecA.getX()) ? rVecB.getX() : rVecA.getX(), + (rVecB.getY() > rVecA.getY()) ? rVecB.getY() : rVecA.getY()); + return aMax; + } + B2DHomPoint absolute(const B2DHomPoint& rVec) + { + B2DHomPoint aAbs( + (0.0 > rVec.getX()) ? -rVec.getX() : rVec.getX(), + (0.0 > rVec.getY()) ? -rVec.getY() : rVec.getY()); + return aAbs; + } + + B2DHomPoint interpolate(B2DHomPoint& rOld1, B2DHomPoint& rOld2, double t) + { + B2DHomPoint aInt( + ((rOld2.getX() - rOld1.getX()) * t) + rOld1.getX(), + ((rOld2.getY() - rOld1.getY()) * t) + rOld1.getY()); + return aInt; + } + + B2DHomPoint average(B2DHomPoint& rOld1, B2DHomPoint& rOld2) + { + B2DHomPoint aAvg( + (rOld1.getX() + rOld2.getX()) * 0.5, + (rOld1.getY() + rOld2.getY()) * 0.5); + return aAvg; + } + + B2DHomPoint average(B2DHomPoint& rOld1, B2DHomPoint& rOld2, B2DHomPoint& rOld3) + { + B2DHomPoint aAvg( + (rOld1.getX() + rOld2.getX() + rOld3.getX()) * (1.0 / 3.0), + (rOld1.getY() + rOld2.getY() + rOld3.getY()) * (1.0 / 3.0)); + return aAvg; + } + + B2DHomPoint operator+(const B2DHomPoint& rVecA, const B2DHomPoint& rVecB) + { + B2DHomPoint aSum(rVecA); + aSum += rVecB; + return aSum; + } + + B2DHomPoint operator-(const B2DHomPoint& rVecA, const B2DHomPoint& rVecB) + { + B2DHomPoint aSub(rVecA); + aSub -= rVecB; + return aSub; + } + + B2DHomPoint operator*(const B2DHomPoint& rVec, double t) + { + B2DHomPoint aNew(rVec); + aNew *= t; + return aNew; + } + + B2DHomPoint operator*(double t, const B2DHomPoint& rVec) + { + B2DHomPoint aNew(rVec); + aNew *= t; + return aNew; + } + + B2DHomPoint operator*( const B2DHomMatrix& rMat, const B2DHomPoint& rPoint ) + { + B2DHomPoint aNew(rPoint); + return aNew*=rMat; + } + + B2DHomPoint operator/(const B2DHomPoint& rVec, double t) + { + B2DHomPoint aNew(rVec); + aNew /= t; + return aNew; + } + + B2DHomPoint operator/(double t, const B2DHomPoint& rVec) + { + B2DHomPoint aNew(rVec); + aNew /= t; + return aNew; + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/point/b2dpoint.cxx b/basegfx/source/point/b2dpoint.cxx new file mode 100644 index 000000000000..39b5eaa6fcbf --- /dev/null +++ b/basegfx/source/point/b2dpoint.cxx @@ -0,0 +1,85 @@ +/************************************************************************* + * + * 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/point/b2dpoint.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + B2DPoint& B2DPoint::operator=( const ::basegfx::B2DTuple& rPoint ) + { + mfX = rPoint.getX(); + mfY = rPoint.getY(); + return *this; + } + + B2DPoint& B2DPoint::operator*=( const ::basegfx::B2DHomMatrix& rMat ) + { + double fTempX( + rMat.get(0, 0) * mfX + + rMat.get(0, 1) * mfY + + rMat.get(0, 2)); + double fTempY( + rMat.get(1, 0) * mfX + + rMat.get(1, 1) * mfY + + rMat.get(1, 2)); + + if(!rMat.isLastLineDefault()) + { + const double fOne(1.0); + const double fTempM( + rMat.get(2, 0) * mfX + + rMat.get(2, 1) * mfY + + rMat.get(2, 2)); + + if(!fTools::equalZero(fTempM) && !fTools::equal(fOne, fTempM)) + { + fTempX /= fTempM; + fTempY /= fTempM; + } + } + + mfX = fTempX; + mfY = fTempY; + + return *this; + } + + B2DPoint operator*( const ::basegfx::B2DHomMatrix& rMat, const B2DPoint& rPoint ) + { + B2DPoint aRes( rPoint ); + return aRes *= rMat; + } +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/point/b2ipoint.cxx b/basegfx/source/point/b2ipoint.cxx new file mode 100644 index 000000000000..09af767518b3 --- /dev/null +++ b/basegfx/source/point/b2ipoint.cxx @@ -0,0 +1,76 @@ +/************************************************************************* + * + * 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/point/b2ipoint.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> + +namespace basegfx +{ + B2IPoint& B2IPoint::operator=( const ::basegfx::B2ITuple& rPoint ) + { + mnX = rPoint.getX(); + mnY = rPoint.getY(); + return *this; + } + + B2IPoint& B2IPoint::operator*=( const ::basegfx::B2DHomMatrix& rMat ) + { + double fTempX( + rMat.get(0, 0) * mnX + + rMat.get(0, 1) * mnY + + rMat.get(0, 2)); + double fTempY( + rMat.get(1, 0) * mnX + + rMat.get(1, 1) * mnY + + rMat.get(1, 2)); + + if(!rMat.isLastLineDefault()) + { + const double fOne(1.0); + const double fTempM( + rMat.get(2, 0) * mnX + + rMat.get(2, 1) * mnY + + rMat.get(2, 2)); + + if(!fTools::equalZero(fTempM) && !fTools::equal(fOne, fTempM)) + { + fTempX /= fTempM; + fTempY /= fTempM; + } + } + + mnX = fround(fTempX); + mnY = fround(fTempY); + + return *this; + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/point/b3dhompoint.cxx b/basegfx/source/point/b3dhompoint.cxx new file mode 100644 index 000000000000..34dee5bc01d0 --- /dev/null +++ b/basegfx/source/point/b3dhompoint.cxx @@ -0,0 +1,44 @@ +/************************************************************************* + * + * 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/point/b3dhompoint.hxx> + +namespace basegfx +{ + void B3DHomPoint::implHomogenize() + { + const double fFactor(1.0 / mfW); + maTuple.setX(maTuple.getX() * fFactor); + maTuple.setY(maTuple.getY() * fFactor); + maTuple.setZ(maTuple.getZ() * fFactor); + mfW = 1.0; + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/point/b3dpoint.cxx b/basegfx/source/point/b3dpoint.cxx new file mode 100644 index 000000000000..8bc1f06f3356 --- /dev/null +++ b/basegfx/source/point/b3dpoint.cxx @@ -0,0 +1,85 @@ +/************************************************************************* + * + * 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/point/b3dpoint.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> + +namespace basegfx +{ + B3DPoint& B3DPoint::operator*=( const ::basegfx::B3DHomMatrix& rMat ) + { + double fTempX( + rMat.get(0, 0) * mfX + + rMat.get(0, 1) * mfY + + rMat.get(0, 2) * mfZ + + rMat.get(0, 3)); + double fTempY( + rMat.get(1, 0) * mfX + + rMat.get(1, 1) * mfY + + rMat.get(1, 2) * mfZ + + rMat.get(1, 3)); + double fTempZ( + rMat.get(2, 0) * mfX + + rMat.get(2, 1) * mfY + + rMat.get(2, 2) * mfZ + + rMat.get(2, 3)); + + if(!rMat.isLastLineDefault()) + { + const double fOne(1.0); + const double fTempM( + rMat.get(3, 0) * mfX + + rMat.get(3, 1) * mfY + + rMat.get(3, 2) * mfZ + + rMat.get(3, 3)); + + if(!fTools::equalZero(fTempM) && !fTools::equal(fOne, fTempM)) + { + fTempX /= fTempM; + fTempY /= fTempM; + fTempZ /= fTempM; + } + } + + mfX = fTempX; + mfY = fTempY; + mfZ = fTempZ; + + return *this; + } + + B3DPoint operator*( const ::basegfx::B3DHomMatrix& rMat, const B3DPoint& rPoint ) + { + B3DPoint aRes( rPoint ); + return aRes *= rMat; + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/point/b3ipoint.cxx b/basegfx/source/point/b3ipoint.cxx new file mode 100644 index 000000000000..19bb25b8bf46 --- /dev/null +++ b/basegfx/source/point/b3ipoint.cxx @@ -0,0 +1,79 @@ +/************************************************************************* + * + * 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/point/b3ipoint.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> + +namespace basegfx +{ + B3IPoint& B3IPoint::operator*=( const ::basegfx::B3DHomMatrix& rMat ) + { + double fTempX( + rMat.get(0, 0) * mnX + + rMat.get(0, 1) * mnY + + rMat.get(0, 2) * mnZ + + rMat.get(0, 3)); + double fTempY( + rMat.get(1, 0) * mnX + + rMat.get(1, 1) * mnY + + rMat.get(1, 2) * mnZ + + rMat.get(1, 3)); + double fTempZ( + rMat.get(2, 0) * mnX + + rMat.get(2, 1) * mnY + + rMat.get(2, 2) * mnZ + + rMat.get(2, 3)); + + if(!rMat.isLastLineDefault()) + { + const double fOne(1.0); + const double fTempM( + rMat.get(3, 0) * mnX + + rMat.get(3, 1) * mnY + + rMat.get(3, 2) * mnZ + + rMat.get(3, 3)); + + if(!fTools::equalZero(fTempM) && !fTools::equal(fOne, fTempM)) + { + fTempX /= fTempM; + fTempY /= fTempM; + fTempZ /= fTempM; + } + } + + mnX = fround(fTempX); + mnY = fround(fTempY); + mnZ = fround(fTempZ); + + return *this; + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/point/makefile.mk b/basegfx/source/point/makefile.mk new file mode 100644 index 000000000000..96798eb35cc9 --- /dev/null +++ b/basegfx/source/point/makefile.mk @@ -0,0 +1,52 @@ +#************************************************************************* +# +# 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=point + +#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb +#ENABLE_EXCEPTIONS=FALSE +#USE_DEFFILE=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= \ + $(SLO)$/b2dpoint.obj \ + $(SLO)$/b2dhompoint.obj \ + $(SLO)$/b3dpoint.obj \ + $(SLO)$/b3dhompoint.obj \ + $(SLO)$/b2ipoint.obj \ + $(SLO)$/b3ipoint.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk 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..d8255dc7ec10 --- /dev/null +++ b/basegfx/source/polygon/b2dpolygon.cxx @@ -0,0 +1,1652 @@ +/************************************************************************* + * + * 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> + +////////////////////////////////////////////////////////////////////////////// + +struct CoordinateData2D : public basegfx::B2DPoint +{ +public: + CoordinateData2D() {} + + explicit CoordinateData2D(const basegfx::B2DPoint& rData) + : B2DPoint(rData) + {} + + CoordinateData2D& operator=(const basegfx::B2DPoint& rData) + { + B2DPoint::operator=(rData); + return *this; + } + + void transform(const basegfx::B2DHomMatrix& rMatrix) + { + *this *= 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]; + } + + void setCoordinate(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue) + { + maVector[nIndex] = 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); + } + } + + const basegfx::B2DPoint* begin() const + { + if(maVector.empty()) + return 0; + else + return &maVector.front(); + } + + const basegfx::B2DPoint* end() const + { + if(maVector.empty()) + return 0; + else + return (&maVector.back())+1; + } + + basegfx::B2DPoint* begin() + { + if(maVector.empty()) + return 0; + else + return &maVector.front(); + } + + basegfx::B2DPoint* end() + { + if(maVector.empty()) + return 0; + else + return (&maVector.back())+1; + } +}; + +////////////////////////////////////////////////////////////////////////////// + +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); + } + } + + const basegfx::B2DPoint* begin() const + { + return maPoints.begin(); + } + + const basegfx::B2DPoint* end() const + { + return maPoints.end(); + } + + basegfx::B2DPoint* begin() + { + mpBufferedData.reset(); + return maPoints.begin(); + } + + basegfx::B2DPoint* end() + { + mpBufferedData.reset(); + return maPoints.end(); + } +}; + +////////////////////////////////////////////////////////////////////////////// + +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); + } + } + + const B2DPoint* B2DPolygon::begin() const + { + return mpPolygon->begin(); + } + + const B2DPoint* B2DPolygon::end() const + { + return mpPolygon->end(); + } + + B2DPoint* B2DPolygon::begin() + { + return mpPolygon->begin(); + } + + B2DPoint* B2DPolygon::end() + { + return mpPolygon->end(); + } +} // 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..e03aadfe1577 --- /dev/null +++ b/basegfx/source/polygon/b2dpolygoncutandtouch.cxx @@ -0,0 +1,1301 @@ +/************************************************************************* + * + * 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 + // #i111715# use fTools::equal instead of fTools::equalZero for better accuracy + if(fTools::equal(fCutA, fZero)) + { + // 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)); + } + + // #i111715# use fTools::equal instead of fTools::equalZero for better accuracy + if(fTools::equal(fCutB, fZero)) + { + // 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; + } + + B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate) + { + if(rCandidate.count()) + { + temporaryPointVector aTempPoints; + + findCuts(rCandidate, aTempPoints); + + return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints); + } + else + { + return rCandidate; + } + } + + B2DPolyPolygon addPointsAtCuts(const B2DPolyPolygon& rCandidate, bool bSelfIntersections) + { + const sal_uInt32 nCount(rCandidate.count()); + + if(nCount) + { + B2DPolyPolygon aRetval; + + if(1 == nCount) + { + if(bSelfIntersections) + { + // remove self intersections + aRetval.append(addPointsAtCuts(rCandidate.getB2DPolygon(0))); + } + else + { + // copy source + aRetval = rCandidate; + } + } + else + { + // first solve self cuts for all contained single polygons + temporaryPolygonData *pTempData = new temporaryPolygonData[nCount]; + sal_uInt32 a, b; + + for(a = 0; a < nCount; a++) + { + if(bSelfIntersections) + { + // use polygons with solved self intersections + pTempData[a].setPolygon(addPointsAtCuts(rCandidate.getB2DPolygon(a))); + } + else + { + // copy given polygons + pTempData[a].setPolygon(rCandidate.getB2DPolygon(a)); + } + } + + // now cuts and touches between the polygons + for(a = 0; a < nCount; a++) + { + for(b = 0; b < nCount; b++) + { + 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; + } + } + + //////////////////////////////////////////////////////////////////////////////// + + } // 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..e9db491ecd48 --- /dev/null +++ b/basegfx/source/polygon/b2dpolygontools.cxx @@ -0,0 +1,3613 @@ +/************************************************************************* + * + * 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 createUnitPolygon() + { + static B2DPolygon aRetval; + + if(!aRetval.count()) + { + aRetval.append( B2DPoint( 0.0, 0.0 ) ); + aRetval.append( B2DPoint( 1.0, 0.0 ) ); + aRetval.append( B2DPoint( 1.0, 1.0 ) ); + aRetval.append( B2DPoint( 0.0, 1.0 ) ); + + // 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 || + rPoly.areControlPointsUsed() ) + { + 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..9b28dffd19af --- /dev/null +++ b/basegfx/source/polygon/b2dpolypolygon.cxx @@ -0,0 +1,432 @@ +/************************************************************************* + * + * 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 )); + } + + const basegfx::B2DPolygon* begin() const + { + if(maPolygons.empty()) + return 0; + else + return &maPolygons.front(); + } + + const basegfx::B2DPolygon* end() const + { + if(maPolygons.empty()) + return 0; + else + return (&maPolygons.back())+1; + } + + basegfx::B2DPolygon* begin() + { + if(maPolygons.empty()) + return 0; + else + return &maPolygons.front(); + } + + basegfx::B2DPolygon* end() + { + if(maPolygons.empty()) + return 0; + else + return &(maPolygons.back())+1; + } +}; + +////////////////////////////////////////////////////////////////////////////// + +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); + } + } + + const B2DPolygon* B2DPolyPolygon::begin() const + { + return mpPolyPolygon->begin(); + } + + const B2DPolygon* B2DPolyPolygon::end() const + { + return mpPolyPolygon->end(); + } + + B2DPolygon* B2DPolyPolygon::begin() + { + return mpPolyPolygon->begin(); + } + + B2DPolygon* B2DPolyPolygon::end() + { + return mpPolyPolygon->end(); + } +} // 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..d2815337edaf --- /dev/null +++ b/basegfx/source/polygon/b2dsvgpolypolygon.cxx @@ -0,0 +1,1108 @@ +/************************************************************************* + * + * 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 sal_Unicode aChar, bool bSignAllowed = true) + { + const bool bPredicate( (sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar) + || (bSignAllowed && sal_Unicode('+') == aChar) + || (bSignAllowed && sal_Unicode('-') == aChar) ); + + return bPredicate; + } + + inline bool lcl_isOnNumberChar(const ::rtl::OUString& rStr, const sal_Int32 nPos, bool bSignAllowed = true) + { + return lcl_isOnNumberChar(rStr[nPos], + bSignAllowed); + } + + 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::OUStringBuffer& rStr, + double fValue ) + { + rStr.append( fValue ); + } + + void lcl_putNumberCharWithSpace( ::rtl::OUStringBuffer& rStr, + double fValue, + double fOldValue, + bool bUseRelativeCoordinates ) + { + if( bUseRelativeCoordinates ) + fValue -= fOldValue; + + const sal_Int32 aLen( rStr.getLength() ); + if(aLen) + { + if( lcl_isOnNumberChar(rStr.charAt(aLen - 1), false) && + fValue >= 0.0 ) + { + rStr.append( 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::OUStringBuffer 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.append(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.append(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.append(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.append(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.append(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.append(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.append(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.append(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.append(lcl_getCommand('Z', 'z', bUseRelativeCoordinates)); + } + } + } + + return aResult.makeStringAndClear(); + } + } +} + +// eof diff --git a/basegfx/source/polygon/b2dtrapezoid.cxx b/basegfx/source/polygon/b2dtrapezoid.cxx new file mode 100644 index 000000000000..d89ec7c6cf73 --- /dev/null +++ b/basegfx/source/polygon/b2dtrapezoid.cxx @@ -0,0 +1,1228 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: b2dpolygontriangulator.cxx,v $ + * $Revision: 1.7 $ + * + * 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/b2dtrapezoid.hxx> +#include <basegfx/range/b1drange.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <list> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace trapezoidhelper + { + ////////////////////////////////////////////////////////////////////////////// + // helper class to hold a simple ege. This is only used for horizontal edges + // currently, thus the YPositions will be equal. I did not create a special + // class for this since holdingthe pointers is more effective and also can be + // used as baseclass for the traversing edges + + class TrDeSimpleEdge + { + protected: + // pointers to start and end point + const B2DPoint* mpStart; + const B2DPoint* mpEnd; + + public: + // constructor + TrDeSimpleEdge( + const B2DPoint* pStart, + const B2DPoint* pEnd) + : mpStart(pStart), + mpEnd(pEnd) + { + } + + // data read access + const B2DPoint& getStart() const { return *mpStart; } + const B2DPoint& getEnd() const { return *mpEnd; } + }; + + ////////////////////////////////////////////////////////////////////////////// + // define vector of simple edges + + typedef ::std::vector< TrDeSimpleEdge > TrDeSimpleEdges; + + ////////////////////////////////////////////////////////////////////////////// + // helper class for holding a traversing edge. It will always have some + // distance in YPos. The slope (in a numerically useful form, see comments) is + // hold and used in SortValue to allow sorting traversing edges by Y, X and slope + // (in that order) + + class TrDeEdgeEntry : public TrDeSimpleEdge + { + private: + // the slope in a numerical useful form for sorting + sal_uInt32 mnSortValue; + + public: + // convenience data read access + double getDeltaX() const { return mpEnd->getX() - mpStart->getX(); } + double getDeltaY() const { return mpEnd->getY() - mpStart->getY(); } + + // convenience data read access. SortValue is created on demand since + // it is not always used + sal_uInt32 getSortValue() const + { + if(0 != mnSortValue) + return mnSortValue; + + // get radiant; has to be in the range ]0.0 .. pi[, thus scale to full + // sal_uInt32 range for maximum precision + const double fRadiant(atan2(getDeltaY(), getDeltaX()) * (SAL_MAX_UINT32 / F_PI)); + + // convert to sal_uInt32 value + const_cast< TrDeEdgeEntry* >(this)->mnSortValue = sal_uInt32(fRadiant); + + return mnSortValue; + } + + // constructor. SortValue can be given when known, use zero otherwise + TrDeEdgeEntry( + const B2DPoint* pStart, + const B2DPoint* pEnd, + sal_uInt32 nSortValue = 0) + : TrDeSimpleEdge(pStart, pEnd), + mnSortValue(nSortValue) + { + // force traversal of deltaY downward + if(mpEnd->getY() < mpStart->getY()) + { + std::swap(mpStart, mpEnd); + } + + // no horizontal edges allowed, all neeed to traverse vertically + OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)"); + } + + // data write access to StartPoint + void setStart( const B2DPoint* pNewStart) + { + OSL_ENSURE(0 != pNewStart, "No null pointer allowed here (!)"); + + if(mpStart != pNewStart) + { + mpStart = pNewStart; + + // no horizontal edges allowed, all neeed to traverse vertivally + OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)"); + } + } + + // data write access to EndPoint + void setEnd( const B2DPoint* pNewEnd) + { + OSL_ENSURE(0 != pNewEnd, "No null pointer allowed here (!)"); + + if(mpEnd != pNewEnd) + { + mpEnd = pNewEnd; + + // no horizontal edges allowed, all neeed to traverse vertivally + OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)"); + } + } + + // operator for sort support. Sort by Y, X and slope (in that order) + bool operator<(const TrDeEdgeEntry& rComp) const + { + if(fTools::equal(getStart().getY(), rComp.getStart().getY(), fTools::getSmallValue())) + { + if(fTools::equal(getStart().getX(), rComp.getStart().getX(), fTools::getSmallValue())) + { + // when start points are equal, use the direction the edge is pointing + // to. That value is created on demand and derived from atan2 in the + // range ]0.0 .. pi[ (without extremas, we always have a deltaY in this + // class) and scaled to sal_uInt32 range for best precision. 0 means no angle, + // while SAL_MAX_UINT32 means pi. Thus, the higher the value, the more left + // the edge traverses. + return (getSortValue() > rComp.getSortValue()); + } + else + { + return fTools::less(getStart().getX(), rComp.getStart().getX()); + } + } + else + { + return fTools::less(getStart().getY(), rComp.getStart().getY()); + } + } + + // method for cut support + B2DPoint getCutPointForGivenY(double fGivenY) + { + // Calculate cut point locally (do not use interpolate) since it is numerically + // necessary to guarantee the new, equal Y-coordinate + const double fFactor((fGivenY - getStart().getY()) / getDeltaY()); + const double fDeltaXNew(fFactor * getDeltaX()); + + return B2DPoint(getStart().getX() + fDeltaXNew, fGivenY); + } + }; + + ////////////////////////////////////////////////////////////////////////////// + // define double linked list of edges (for fast random insert) + + typedef ::std::list< TrDeEdgeEntry > TrDeEdgeEntries; + + } // end of anonymous namespace +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace trapezoidhelper + { + // helper class to handle the complete trapezoid subdivision of a PolyPolygon + class TrapezoidSubdivider + { + private: + // local data + sal_uInt32 mnInitialEdgeEntryCount; + TrDeEdgeEntries maTrDeEdgeEntries; + ::std::vector< B2DPoint > maPoints; + ::std::vector< B2DPoint* > maNewPoints; + + void addEdgeSorted( + TrDeEdgeEntries::iterator aCurrent, + const TrDeEdgeEntry& rNewEdge) + { + // Loop while new entry is bigger, use operator< + while(aCurrent != maTrDeEdgeEntries.end() && (*aCurrent) < rNewEdge) + { + aCurrent++; + } + + // Insert before first which is smaller or equal or at end + maTrDeEdgeEntries.insert(aCurrent, rNewEdge); + } + + bool splitEdgeAtGivenPoint( + TrDeEdgeEntries::reference aEdge, + const B2DPoint& rCutPoint, + TrDeEdgeEntries::iterator aCurrent) + { + // do not create edges without deltaY: do not split when start is identical + if(aEdge.getStart().equal(rCutPoint, fTools::getSmallValue())) + { + return false; + } + + // do not create edges without deltaY: do not split when end is identical + if(aEdge.getEnd().equal(rCutPoint, fTools::getSmallValue())) + { + return false; + } + + const double fOldDeltaYStart(rCutPoint.getY() - aEdge.getStart().getY()); + + if(fTools::lessOrEqual(fOldDeltaYStart, 0.0)) + { + // do not split: the resulting edge would be horizontal + // correct it to new start point + aEdge.setStart(&rCutPoint); + return false; + } + + const double fNewDeltaYStart(aEdge.getEnd().getY() - rCutPoint.getY()); + + if(fTools::lessOrEqual(fNewDeltaYStart, 0.0)) + { + // do not split: the resulting edge would be horizontal + // correct it to new end point + aEdge.setEnd(&rCutPoint); + return false; + } + + // Create new entry + const TrDeEdgeEntry aNewEdge( + &rCutPoint, + &aEdge.getEnd(), + aEdge.getSortValue()); + + // Correct old entry + aEdge.setEnd(&rCutPoint); + + // Insert sorted (to avoid new sort) + addEdgeSorted(aCurrent, aNewEdge); + + return true; + } + + bool testAndCorrectEdgeIntersection( + TrDeEdgeEntries::reference aEdgeA, + TrDeEdgeEntries::reference aEdgeB, + TrDeEdgeEntries::iterator aCurrent) + { + // Exclude simple cases: same start or end point + if(aEdgeA.getStart().equal(aEdgeB.getStart(), fTools::getSmallValue())) + { + return false; + } + + if(aEdgeA.getStart().equal(aEdgeB.getEnd(), fTools::getSmallValue())) + { + return false; + } + + if(aEdgeA.getEnd().equal(aEdgeB.getStart(), fTools::getSmallValue())) + { + return false; + } + + if(aEdgeA.getEnd().equal(aEdgeB.getEnd(), fTools::getSmallValue())) + { + return false; + } + + // Exclude simple cases: one of the edges has no length anymore + if(aEdgeA.getStart().equal(aEdgeA.getEnd(), fTools::getSmallValue())) + { + return false; + } + + if(aEdgeB.getStart().equal(aEdgeB.getEnd(), fTools::getSmallValue())) + { + return false; + } + + // check if one point is on the other edge (a touch, not a cut) + const B2DVector aDeltaB(aEdgeB.getDeltaX(), aEdgeB.getDeltaY()); + + if(tools::isPointOnEdge(aEdgeA.getStart(), aEdgeB.getStart(), aDeltaB)) + { + return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getStart(), aCurrent); + } + + if(tools::isPointOnEdge(aEdgeA.getEnd(), aEdgeB.getStart(), aDeltaB)) + { + return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getEnd(), aCurrent); + } + + const B2DVector aDeltaA(aEdgeA.getDeltaX(), aEdgeA.getDeltaY()); + + if(tools::isPointOnEdge(aEdgeB.getStart(), aEdgeA.getStart(), aDeltaA)) + { + return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getStart(), aCurrent); + } + + if(tools::isPointOnEdge(aEdgeB.getEnd(), aEdgeA.getStart(), aDeltaA)) + { + return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getEnd(), aCurrent); + } + + // check for cut inside edges. Use both t-values to choose the more precise + // one later + double fCutA(0.0); + double fCutB(0.0); + + if(tools::findCut( + aEdgeA.getStart(), aDeltaA, + aEdgeB.getStart(), aDeltaB, + CUTFLAG_LINE, + &fCutA, + &fCutB)) + { + // use a simple metric (length criteria) for choosing the numerically + // better cut + const double fSimpleLengthA(aDeltaA.getX() + aDeltaA.getY()); + const double fSimpleLengthB(aDeltaB.getX() + aDeltaB.getY()); + const bool bAIsLonger(fSimpleLengthA > fSimpleLengthB); + B2DPoint* pNewPoint = bAIsLonger + ? new B2DPoint(aEdgeA.getStart() + (fCutA * aDeltaA)) + : new B2DPoint(aEdgeB.getStart() + (fCutB * aDeltaB)); + bool bRetval(false); + + // try to split both edges + bRetval = splitEdgeAtGivenPoint(aEdgeA, *pNewPoint, aCurrent); + bRetval |= splitEdgeAtGivenPoint(aEdgeB, *pNewPoint, aCurrent); + + if(bRetval) + { + maNewPoints.push_back(pNewPoint); + } + else + { + delete pNewPoint; + } + + return bRetval; + } + + return false; + } + + void solveHorizontalEdges(TrDeSimpleEdges& rTrDeSimpleEdges) + { + if(rTrDeSimpleEdges.size() && maTrDeEdgeEntries.size()) + { + // there were horizontal edges. These can be excluded, but + // cuts with other edges need to be solved and added before + // ignoring them + sal_uInt32 a(0); + + for(a = 0; a < rTrDeSimpleEdges.size(); a++) + { + // get horizontal edge as candidate; prepare it's range and fixed Y + const TrDeSimpleEdge& rHorEdge = rTrDeSimpleEdges[a]; + const B1DRange aRange(rHorEdge.getStart().getX(), rHorEdge.getEnd().getX()); + const double fFixedY(rHorEdge.getStart().getY()); + + // loop over traversing edges + TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin()); + + do + { + // get compare edge + TrDeEdgeEntries::reference aCompare(*aCurrent++); + + if(fTools::lessOrEqual(aCompare.getEnd().getY(), fFixedY)) + { + // edge ends above horizontal edge, continue + continue; + } + + if(fTools::moreOrEqual(aCompare.getStart().getY(), fFixedY)) + { + // edge starts below horizontal edge, continue + continue; + } + + // vertical overlap, get horizontal range + const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX()); + + if(aRange.overlaps(aCompareRange)) + { + // possible cut, get cut point + const B2DPoint aSplit(aCompare.getCutPointForGivenY(fFixedY)); + + if(fTools::more(aSplit.getX(), aRange.getMinimum()) + && fTools::less(aSplit.getX(), aRange.getMaximum())) + { + // cut is in XRange of horizontal edge, potenitally needed cut + B2DPoint* pNewPoint = new B2DPoint(aSplit); + + if(splitEdgeAtGivenPoint(aCompare, *pNewPoint, aCurrent)) + { + maNewPoints.push_back(pNewPoint); + } + else + { + delete pNewPoint; + } + } + } + } + while(aCurrent != maTrDeEdgeEntries.end() + && fTools::less(aCurrent->getStart().getY(), fFixedY)); + } + } + } + + public: + TrapezoidSubdivider( + const B2DPolyPolygon& rSourcePolyPolygon) + : mnInitialEdgeEntryCount(0), + maTrDeEdgeEntries(), + maPoints(), + maNewPoints() + { + B2DPolyPolygon aSource(rSourcePolyPolygon); + const sal_uInt32 nPolygonCount(rSourcePolyPolygon.count()); + TrDeSimpleEdges aTrDeSimpleEdges; + sal_uInt32 a(0), b(0); + sal_uInt32 nAllPointCount(0); + + // ensure there are no curves used + if(aSource.areControlPointsUsed()) + { + aSource = aSource.getDefaultAdaptiveSubdivision(); + } + + for(a = 0; a < nPolygonCount; a++) + { + // 1st run: count points + const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a)); + const sal_uInt32 nCount(aPolygonCandidate.count()); + + if(nCount > 2) + { + nAllPointCount += nCount; + } + } + + if(nAllPointCount) + { + // reserve needed points. CAUTION: maPoints size is NOT to be changed anymore + // after 2nd loop since pointers to it are used in the edges + maPoints.reserve(nAllPointCount); + + for(a = 0; a < nPolygonCount; a++) + { + // 2nd run: add points + const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a)); + const sal_uInt32 nCount(aPolygonCandidate.count()); + + if(nCount > 2) + { + for(b = 0; b < nCount; b++) + { + maPoints.push_back(aPolygonCandidate.getB2DPoint(b)); + } + } + } + + // Moved the edge construction to a 3rd run: doing it in the 2nd run is + // possible(and i used it), but requires a working vector::reserve() + // implementation, else the vector will be reallocated and the pointers + // in the edges may be wrong. Security first here. + sal_uInt32 nStartIndex(0); + + for(a = 0; a < nPolygonCount; a++) + { + const B2DPolygon aPolygonCandidate(aSource.getB2DPolygon(a)); + const sal_uInt32 nCount(aPolygonCandidate.count()); + + if(nCount > 2) + { + // get the last point of the current polygon + B2DPoint* pPrev(&maPoints[nCount + nStartIndex - 1]); + + for(b = 0; b < nCount; b++) + { + // get next point + B2DPoint* pCurr(&maPoints[nStartIndex++]); + + if(fTools::equal(pPrev->getY(), pCurr->getY(), fTools::getSmallValue())) + { + // horizontal edge, check for single point + if(!fTools::equal(pPrev->getX(), pCurr->getX(), fTools::getSmallValue())) + { + // X-order not needed, just add + aTrDeSimpleEdges.push_back(TrDeSimpleEdge(pPrev, pCurr)); + + const double fMiddle((pPrev->getY() + pCurr->getY()) * 0.5); + pPrev->setY(fMiddle); + pCurr->setY(fMiddle); + } + } + else + { + // vertical edge. Positive Y-direction is guaranteed by the + // TrDeEdgeEntry constructor + maTrDeEdgeEntries.push_back(TrDeEdgeEntry(pPrev, pCurr, 0)); + mnInitialEdgeEntryCount++; + } + + // prepare next step + pPrev = pCurr; + } + } + } + } + + if(maTrDeEdgeEntries.size()) + { + // single and initial sort of traversing edges + maTrDeEdgeEntries.sort(); + + // solve horizontal edges if there are any detected + solveHorizontalEdges(aTrDeSimpleEdges); + } + } + + ~TrapezoidSubdivider() + { + // delete the extra points created for cuts + const sal_uInt32 nCount(maNewPoints.size()); + + for(sal_uInt32 a(0); a < nCount; a++) + { + delete maNewPoints[a]; + } + } + + void Subdivide(B2DTrapezoidVector& ro_Result) + { + // This is the central subdivider. The strategy is to use the first two entries + // from the traversing edges as a potential trapezoid and do the needed corrections + // and adaptions on the way. + // + // There always must be two edges with the same YStart value: When adding the polygons + // in the constructor, there is always a topmost point from which two edges start; when + // the topmost is an edge, there is a start and end of this edge from which two edges + // start. All cases have two edges with same StartY (QED). + // + // Based on this these edges get corrected when: + // - one is longer than the other + // - they intersect + // - they intersect with other edges + // - another edge starts inside the thought trapezoid + // + // All this cases again produce a valid state so that the first two edges have a common + // Ystart again. Some cases lead to a restart of the process, some allow consuming the + // edges and create the intended trapezoid. + // + // Be careful when doing chages here: It is essential to keep all possible paths + // in valid states and to be numerically correct. This is especially needed e.g. + // by using fTools::equal(..) in the more robust small-value incarnation. + B1DRange aLeftRange; + B1DRange aRightRange; + + if(!maTrDeEdgeEntries.empty()) + { + // measuring shows that the relation between edges and created trapezoids is + // mostly in the 1:1 range, thus reserve as much trapezoids as edges exist. Do + // not use maTrDeEdgeEntries.size() since that may be a non-constant time + // operation for Lists. Instead, use mnInitialEdgeEntryCount which will contain + // the roughly counted adds to the List + ro_Result.reserve(ro_Result.size() + mnInitialEdgeEntryCount); + } + + while(!maTrDeEdgeEntries.empty()) + { + // Prepare current operator and get first edge + TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin()); + TrDeEdgeEntries::reference aLeft(*aCurrent++); + + if(aCurrent == maTrDeEdgeEntries.end()) + { + // Should not happen: No 2nd edge; consume the single edge + // to not have an endless loop and start next. During development + // i constantly had breakpoints here, so i am sure enough to add an + // assertion here + OSL_ENSURE(false, "Trapeziod decomposer in illegal state (!)"); + maTrDeEdgeEntries.pop_front(); + continue; + } + + // get second edge + TrDeEdgeEntries::reference aRight(*aCurrent++); + + if(!fTools::equal(aLeft.getStart().getY(), aRight.getStart().getY(), fTools::getSmallValue())) + { + // Should not happen: We have a 2nd edge, but YStart is on another + // line; consume the single edge to not have an endless loop and start + // next. During development i constantly had breakpoints here, so i am + // sure enough to add an assertion here + OSL_ENSURE(false, "Trapeziod decomposer in illegal state (!)"); + maTrDeEdgeEntries.pop_front(); + continue; + } + + // aLeft and aRight build a thought trapezoid now. They have a common + // start line (same Y for start points). Potentially, one of the edges + // is longer than the other. It is only needed to look at the shorter + // length which build the potential trapezoid. To do so, get the end points + // locally and adapt the evtl. longer one. Use only aLeftEnd and aRightEnd + // from here on, not the aLeft.getEnd() or aRight.getEnd() accesses. + B2DPoint aLeftEnd(aLeft.getEnd()); + B2DPoint aRightEnd(aRight.getEnd()); + + // check if end points are on the same line. If yes, no adaption + // needs to be prepared. Also remember which one actually is longer. + const bool bEndOnSameLine(fTools::equal(aLeftEnd.getY(), aRightEnd.getY(), fTools::getSmallValue())); + bool bLeftIsLonger(false); + + if(!bEndOnSameLine) + { + // check which edge is longer and correct accordingly + bLeftIsLonger = fTools::more(aLeftEnd.getY(), aRightEnd.getY()); + + if(bLeftIsLonger) + { + aLeftEnd = aLeft.getCutPointForGivenY(aRightEnd.getY()); + } + else + { + aRightEnd = aRight.getCutPointForGivenY(aLeftEnd.getY()); + } + } + + // check for same start and end points + const bool bSameStartPoint(aLeft.getStart().equal(aRight.getStart(), fTools::getSmallValue())); + const bool bSameEndPoint(aLeftEnd.equal(aRightEnd, fTools::getSmallValue())); + + // check the simple case that the edges form a 'blind' edge (deadend) + if(bSameStartPoint && bSameEndPoint) + { + // correct the longer edge if prepared + if(!bEndOnSameLine) + { + if(bLeftIsLonger) + { + B2DPoint* pNewPoint = new B2DPoint(aLeftEnd); + + if(splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent)) + { + maNewPoints.push_back(pNewPoint); + } + else + { + delete pNewPoint; + } + } + else + { + B2DPoint* pNewPoint = new B2DPoint(aRightEnd); + + if(splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent)) + { + maNewPoints.push_back(pNewPoint); + } + else + { + delete pNewPoint; + } + } + } + + // consume both edges and start next run + maTrDeEdgeEntries.pop_front(); + maTrDeEdgeEntries.pop_front(); + + continue; + } + + // check if the edges self-intersect. This can only happen when + // start and end point are different + bool bRangesSet(false); + + if(!(bSameStartPoint || bSameEndPoint)) + { + // get XRanges of edges + aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX()); + aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX()); + bRangesSet = true; + + // use fast range test first + if(aLeftRange.overlaps(aRightRange)) + { + // real cut test and correction. If correction was needed, + // start new run + if(testAndCorrectEdgeIntersection(aLeft, aRight, aCurrent)) + { + continue; + } + } + } + + // now we need to check if there are intersections with other edges + // or if other edges start inside the candidate trapezoid + if(aCurrent != maTrDeEdgeEntries.end() + && fTools::less(aCurrent->getStart().getY(), aLeftEnd.getY())) + { + // get XRanges of edges + if(!bRangesSet) + { + aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX()); + aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX()); + } + + // build full XRange for fast check + B1DRange aAllRange(aLeftRange); + aAllRange.expand(aRightRange); + + // prepare loop iterator; aCurrent needs to stay unchanged for + // eventual sorted insertions of new EdgeNodes. Also prepare stop flag + TrDeEdgeEntries::iterator aLoop(aCurrent); + bool bDone(false); + + do + { + // get compare edge and it's XRange + TrDeEdgeEntries::reference aCompare(*aLoop++); + + // avoid edges using the same start point as one of + // the edges. These can neither have their start point + // in the thought trapezoid nor cut with one of the edges + if(aCompare.getStart().equal(aRight.getStart(), fTools::getSmallValue())) + { + continue; + } + + // get compare XRange + const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX()); + + // use fast range test first + if(aAllRange.overlaps(aCompareRange)) + { + // check for start point inside thought trapezoid + if(fTools::more(aCompare.getStart().getY(), aLeft.getStart().getY())) + { + // calculate the two possible split points at compare's Y + const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aCompare.getStart().getY())); + const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aCompare.getStart().getY())); + + // check for start point of aCompare being inside thought + // trapezoid + if(aCompare.getStart().getX() >= aSplitLeft.getX() && + aCompare.getStart().getX() <= aSplitRight.getX()) + { + // is inside, correct and restart loop + B2DPoint* pNewLeft = new B2DPoint(aSplitLeft); + + if(splitEdgeAtGivenPoint(aLeft, *pNewLeft, aCurrent)) + { + maNewPoints.push_back(pNewLeft); + bDone = true; + } + else + { + delete pNewLeft; + } + + B2DPoint* pNewRight = new B2DPoint(aSplitRight); + + if(splitEdgeAtGivenPoint(aRight, *pNewRight, aCurrent)) + { + maNewPoints.push_back(pNewRight); + bDone = true; + } + else + { + delete pNewRight; + } + } + } + + if(!bDone && aLeftRange.overlaps(aCompareRange)) + { + // test for concrete cut of compare edge with left edge + bDone = testAndCorrectEdgeIntersection(aLeft, aCompare, aCurrent); + } + + if(!bDone && aRightRange.overlaps(aCompareRange)) + { + // test for concrete cut of compare edge with Right edge + bDone = testAndCorrectEdgeIntersection(aRight, aCompare, aCurrent); + } + } + } + while(!bDone + && aLoop != maTrDeEdgeEntries.end() + && fTools::less(aLoop->getStart().getY(), aLeftEnd.getY())); + + if(bDone) + { + // something needed to be changed; start next loop + continue; + } + } + + // when we get here, the intended trapezoid can be used. It needs to + // be corrected, eventually (if prepared); but this is no reason not to + // use it in the same loop iteration + if(!bEndOnSameLine) + { + if(bLeftIsLonger) + { + B2DPoint* pNewPoint = new B2DPoint(aLeftEnd); + + if(splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent)) + { + maNewPoints.push_back(pNewPoint); + } + else + { + delete pNewPoint; + } + } + else + { + B2DPoint* pNewPoint = new B2DPoint(aRightEnd); + + if(splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent)) + { + maNewPoints.push_back(pNewPoint); + } + else + { + delete pNewPoint; + } + } + } + + // the two edges start at the same Y, they use the same DeltaY, they + // do not cut themselves and not any other edge in range. Create a + // B2DTrapezoid and consume both edges + ro_Result.push_back( + B2DTrapezoid( + aLeft.getStart().getX(), + aRight.getStart().getX(), + aLeft.getStart().getY(), + aLeftEnd.getX(), + aRightEnd.getX(), + aLeftEnd.getY())); + + maTrDeEdgeEntries.pop_front(); + maTrDeEdgeEntries.pop_front(); + } + } + }; + } // end of anonymous namespace +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + B2DTrapezoid::B2DTrapezoid( + const double& rfTopXLeft, + const double& rfTopXRight, + const double& rfTopY, + const double& rfBottomXLeft, + const double& rfBottomXRight, + const double& rfBottomY) + : mfTopXLeft(rfTopXLeft), + mfTopXRight(rfTopXRight), + mfTopY(rfTopY), + mfBottomXLeft(rfBottomXLeft), + mfBottomXRight(rfBottomXRight), + mfBottomY(rfBottomY) + { + // guarantee mfTopXRight >= mfTopXLeft + if(mfTopXLeft > mfTopXRight) + { + std::swap(mfTopXLeft, mfTopXRight); + } + + // guarantee mfBottomXRight >= mfBottomXLeft + if(mfBottomXLeft > mfBottomXRight) + { + std::swap(mfBottomXLeft, mfBottomXRight); + } + + // guarantee mfBottomY >= mfTopY + if(mfTopY > mfBottomY) + { + std::swap(mfTopY, mfBottomY); + std::swap(mfTopXLeft, mfBottomXLeft); + std::swap(mfTopXRight, mfBottomXRight); + } + } + + B2DPolygon B2DTrapezoid::getB2DPolygon() const + { + B2DPolygon aRetval; + + aRetval.append(B2DPoint(getTopXLeft(), getTopY())); + aRetval.append(B2DPoint(getTopXRight(), getTopY())); + aRetval.append(B2DPoint(getBottomXRight(), getBottomY())); + aRetval.append(B2DPoint(getBottomXLeft(), getBottomY())); + aRetval.setClosed(true); + + return aRetval; + } +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + // convert Source PolyPolygon to trapezoids + void trapezoidSubdivide(B2DTrapezoidVector& ro_Result, const B2DPolyPolygon& rSourcePolyPolygon) + { + trapezoidhelper::TrapezoidSubdivider aTrapezoidSubdivider(rSourcePolyPolygon); + + aTrapezoidSubdivider.Subdivide(ro_Result); + } + + void createLineTrapezoidFromEdge( + B2DTrapezoidVector& ro_Result, + const B2DPoint& rPointA, + const B2DPoint& rPointB, + double fLineWidth) + { + if(fTools::lessOrEqual(fLineWidth, 0.0)) + { + // no line witdh + return; + } + + if(rPointA.equal(rPointB, fTools::getSmallValue())) + { + // points are equal, no edge + return; + } + + const double fHalfLineWidth(0.5 * fLineWidth); + + if(fTools::equal(rPointA.getX(), rPointB.getX(), fTools::getSmallValue())) + { + // vertical line + const double fLeftX(rPointA.getX() - fHalfLineWidth); + const double fRightX(rPointA.getX() + fHalfLineWidth); + + ro_Result.push_back( + B2DTrapezoid( + fLeftX, + fRightX, + std::min(rPointA.getY(), rPointB.getY()), + fLeftX, + fRightX, + std::max(rPointA.getY(), rPointB.getY()))); + } + else if(fTools::equal(rPointA.getY(), rPointB.getY(), fTools::getSmallValue())) + { + // horizontal line + const double fLeftX(std::min(rPointA.getX(), rPointB.getX())); + const double fRightX(std::max(rPointA.getX(), rPointB.getX())); + + ro_Result.push_back( + B2DTrapezoid( + fLeftX, + fRightX, + rPointA.getY() - fHalfLineWidth, + fLeftX, + fRightX, + rPointA.getY() + fHalfLineWidth)); + } + else + { + // diagonal line + // create perpendicular vector + const B2DVector aDelta(rPointB - rPointA); + B2DVector aPerpendicular(-aDelta.getY(), aDelta.getX()); + aPerpendicular.setLength(fHalfLineWidth); + + // create StartLow, StartHigh, EndLow and EndHigh + const B2DPoint aStartLow(rPointA + aPerpendicular); + const B2DPoint aStartHigh(rPointA - aPerpendicular); + const B2DPoint aEndHigh(rPointB - aPerpendicular); + const B2DPoint aEndLow(rPointB + aPerpendicular); + + // create EdgeEntries + basegfx::trapezoidhelper::TrDeEdgeEntries aTrDeEdgeEntries; + + aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aStartLow, &aStartHigh, 0)); + aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aStartHigh, &aEndHigh, 0)); + aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aEndHigh, &aEndLow, 0)); + aTrDeEdgeEntries.push_back(basegfx::trapezoidhelper::TrDeEdgeEntry(&aEndLow, &aStartLow, 0)); + aTrDeEdgeEntries.sort(); + + // here we know we have exactly four edges, and they do not cut, touch or + // intersect. This makes processing much easier. Get the first two as start + // edges for the thought trapezoid + basegfx::trapezoidhelper::TrDeEdgeEntries::iterator aCurrent(aTrDeEdgeEntries.begin()); + basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft(*aCurrent++); + basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight(*aCurrent++); + const bool bEndOnSameLine(fTools::equal(aLeft.getEnd().getY(), aRight.getEnd().getY(), fTools::getSmallValue())); + + if(bEndOnSameLine) + { + // create two triangle trapezoids + ro_Result.push_back( + B2DTrapezoid( + aLeft.getStart().getX(), + aRight.getStart().getX(), + aLeft.getStart().getY(), + aLeft.getEnd().getX(), + aRight.getEnd().getX(), + aLeft.getEnd().getY())); + + basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++); + basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++); + + ro_Result.push_back( + B2DTrapezoid( + aLeft2.getStart().getX(), + aRight2.getStart().getX(), + aLeft2.getStart().getY(), + aLeft2.getEnd().getX(), + aRight2.getEnd().getX(), + aLeft2.getEnd().getY())); + } + else + { + // create three trapezoids. Check which edge is longer and + // correct accordingly + const bool bLeftIsLonger(fTools::more(aLeft.getEnd().getY(), aRight.getEnd().getY())); + + if(bLeftIsLonger) + { + basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++); + basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++); + const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aRight.getEnd().getY())); + const B2DPoint aSplitRight(aRight2.getCutPointForGivenY(aLeft.getEnd().getY())); + + ro_Result.push_back( + B2DTrapezoid( + aLeft.getStart().getX(), + aRight.getStart().getX(), + aLeft.getStart().getY(), + aSplitLeft.getX(), + aRight.getEnd().getX(), + aRight.getEnd().getY())); + + ro_Result.push_back( + B2DTrapezoid( + aSplitLeft.getX(), + aRight.getEnd().getX(), + aRight.getEnd().getY(), + aLeft2.getStart().getX(), + aSplitRight.getX(), + aLeft2.getStart().getY())); + + ro_Result.push_back( + B2DTrapezoid( + aLeft2.getStart().getX(), + aSplitRight.getX(), + aLeft2.getStart().getY(), + aLeft2.getEnd().getX(), + aRight2.getEnd().getX(), + aLeft2.getEnd().getY())); + } + else + { + basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++); + basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++); + const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aLeft.getEnd().getY())); + const B2DPoint aSplitLeft(aLeft2.getCutPointForGivenY(aRight.getEnd().getY())); + + ro_Result.push_back( + B2DTrapezoid( + aLeft.getStart().getX(), + aRight.getStart().getX(), + aLeft.getStart().getY(), + aLeft.getEnd().getX(), + aSplitRight.getX(), + aLeft.getEnd().getY())); + + ro_Result.push_back( + B2DTrapezoid( + aLeft.getEnd().getX(), + aSplitRight.getX(), + aLeft.getEnd().getY(), + aSplitLeft.getX(), + aRight.getEnd().getX(), + aRight2.getStart().getY())); + + ro_Result.push_back( + B2DTrapezoid( + aSplitLeft.getX(), + aRight.getEnd().getX(), + aRight2.getStart().getY(), + aLeft2.getEnd().getX(), + aRight2.getEnd().getX(), + aLeft2.getEnd().getY())); + } + } + } + } + + void createLineTrapezoidFromB2DPolygon( + B2DTrapezoidVector& ro_Result, + const B2DPolygon& rPolygon, + double fLineWidth) + { + if(fTools::lessOrEqual(fLineWidth, 0.0)) + { + return; + } + + // ensure there are no curves used + B2DPolygon aSource(rPolygon); + + if(aSource.areControlPointsUsed()) + { + const double fPrecisionFactor = 0.25; + aSource = adaptiveSubdivideByDistance( aSource, fLineWidth * fPrecisionFactor ); + } + + const sal_uInt32 nPointCount(aSource.count()); + + if(!nPointCount) + { + return; + } + + const sal_uInt32 nEdgeCount(aSource.isClosed() ? nPointCount : nPointCount - 1); + B2DPoint aCurrent(aSource.getB2DPoint(0)); + + ro_Result.reserve(ro_Result.size() + (3 * nEdgeCount)); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B2DPoint aNext(aSource.getB2DPoint(nNextIndex)); + + createLineTrapezoidFromEdge(ro_Result, aCurrent, aNext, fLineWidth); + aCurrent = aNext; + } + } + + void createLineTrapezoidFromB2DPolyPolygon( + B2DTrapezoidVector& ro_Result, + const B2DPolyPolygon& rPolyPolygon, + double fLineWidth) + { + if(fTools::lessOrEqual(fLineWidth, 0.0)) + { + return; + } + + // ensure there are no curves used + B2DPolyPolygon aSource(rPolyPolygon); + + if(aSource.areControlPointsUsed()) + { + aSource = aSource.getDefaultAdaptiveSubdivision(); + } + + const sal_uInt32 nCount(aSource.count()); + + if(!nCount) + { + return; + } + + for(sal_uInt32 a(0); a < nCount; a++) + { + createLineTrapezoidFromB2DPolygon( + ro_Result, + aSource.getB2DPolygon(a), + fLineWidth); + } + } + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof 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..7ac71ada5e8e --- /dev/null +++ b/basegfx/source/polygon/makefile.mk @@ -0,0 +1,63 @@ +#************************************************************************* +# +# 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)$/b2dtrapezoid.obj \ + $(SLO)$/b3dpolygon.obj \ + $(SLO)$/b3dpolygontools.obj \ + $(SLO)$/b3dpolypolygon.obj \ + $(SLO)$/b3dpolypolygontools.obj \ + $(SLO)$/b3dpolygonclipper.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/range/b1drange.cxx b/basegfx/source/range/b1drange.cxx new file mode 100644 index 000000000000..6581b04680e2 --- /dev/null +++ b/basegfx/source/range/b1drange.cxx @@ -0,0 +1,56 @@ +/************************************************************************* + * + * 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/range/b1drange.hxx> +#include <basegfx/range/b1irange.hxx> +#include <basegfx/numeric/ftools.hxx> + +namespace basegfx +{ + B1DRange::B1DRange( const B1IRange& rRange ) : + maRange() + { + if( !rRange.isEmpty() ) + { + maRange = rRange.getMinimum(); + expand(rRange.getMaximum()); + } + } + + B1IRange fround(const B1DRange& rRange) + { + return rRange.isEmpty() ? + B1IRange() : + B1IRange( fround( rRange.getMinimum()), + fround( rRange.getMaximum()) ); + } + +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/range/b2dpolyrange.cxx b/basegfx/source/range/b2dpolyrange.cxx new file mode 100644 index 000000000000..e212e083ef55 --- /dev/null +++ b/basegfx/source/range/b2dpolyrange.cxx @@ -0,0 +1,423 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: b2dmultirange.cxx,v $ + * $Revision: 1.8 $ + * + * 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/range/b2dpolyrange.hxx> + +#include <basegfx/range/b2drange.hxx> +#include <basegfx/range/b2drangeclipper.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include <boost/bind.hpp> +#include <boost/tuple/tuple.hpp> +#include <algorithm> +#include <vector> + +static basegfx::B2VectorOrientation flipOrientation( + basegfx::B2VectorOrientation eOrient) +{ + return eOrient == basegfx::ORIENTATION_POSITIVE ? + basegfx::ORIENTATION_NEGATIVE : basegfx::ORIENTATION_POSITIVE; +} + +namespace basegfx +{ + class ImplB2DPolyRange + { + void updateBounds() + { + maBounds.reset(); + std::for_each(maRanges.begin(), + maRanges.end(), + boost::bind( + (void (B2DRange::*)(const B2DRange&))( + &B2DRange::expand), + boost::ref(maBounds), + _1)); + } + + public: + ImplB2DPolyRange() : + maBounds(), + maRanges(), + maOrient() + {} + + explicit ImplB2DPolyRange( const B2DPolyRange::ElementType& rElem ) : + maBounds( boost::get<0>(rElem) ), + maRanges( 1, boost::get<0>(rElem) ), + maOrient( 1, boost::get<1>(rElem) ) + {} + + explicit ImplB2DPolyRange( const B2DRange& rRange, B2VectorOrientation eOrient ) : + maBounds( rRange ), + maRanges( 1, rRange ), + maOrient( 1, eOrient ) + {} + + bool operator==(const ImplB2DPolyRange& rRHS) const + { + return maRanges == rRHS.maRanges && maOrient == rRHS.maOrient; + } + + sal_uInt32 count() const + { + return maRanges.size(); + } + + B2DPolyRange::ElementType getElement(sal_uInt32 nIndex) const + { + return boost::make_tuple(maRanges[nIndex], + maOrient[nIndex]); + } + + void setElement(sal_uInt32 nIndex, const B2DPolyRange::ElementType& rElement ) + { + maRanges[nIndex] = boost::get<0>(rElement); + maOrient[nIndex] = boost::get<1>(rElement); + updateBounds(); + } + + void setElement(sal_uInt32 nIndex, const B2DRange& rRange, B2VectorOrientation eOrient ) + { + maRanges[nIndex] = rRange; + maOrient[nIndex] = eOrient; + updateBounds(); + } + + void insertElement(sal_uInt32 nIndex, const B2DPolyRange::ElementType& rElement, sal_uInt32 nCount) + { + maRanges.insert(maRanges.begin()+nIndex, nCount, boost::get<0>(rElement)); + maOrient.insert(maOrient.begin()+nIndex, nCount, boost::get<1>(rElement)); + maBounds.expand(boost::get<0>(rElement)); + } + + void insertElement(sal_uInt32 nIndex, const B2DRange& rRange, B2VectorOrientation eOrient, sal_uInt32 nCount) + { + maRanges.insert(maRanges.begin()+nIndex, nCount, rRange); + maOrient.insert(maOrient.begin()+nIndex, nCount, eOrient); + maBounds.expand(rRange); + } + + void appendElement(const B2DPolyRange::ElementType& rElement, sal_uInt32 nCount) + { + maRanges.insert(maRanges.end(), nCount, boost::get<0>(rElement)); + maOrient.insert(maOrient.end(), nCount, boost::get<1>(rElement)); + maBounds.expand(boost::get<0>(rElement)); + } + + void appendElement(const B2DRange& rRange, B2VectorOrientation eOrient, sal_uInt32 nCount) + { + maRanges.insert(maRanges.end(), nCount, rRange); + maOrient.insert(maOrient.end(), nCount, eOrient); + maBounds.expand(rRange); + } + + void insertPolyRange(sal_uInt32 nIndex, const ImplB2DPolyRange& rPolyRange) + { + maRanges.insert(maRanges.begin()+nIndex, rPolyRange.maRanges.begin(), rPolyRange.maRanges.end()); + maOrient.insert(maOrient.begin()+nIndex, rPolyRange.maOrient.begin(), rPolyRange.maOrient.end()); + updateBounds(); + } + + void appendPolyRange(const ImplB2DPolyRange& rPolyRange) + { + maRanges.insert(maRanges.end(), + rPolyRange.maRanges.begin(), + rPolyRange.maRanges.end()); + maOrient.insert(maOrient.end(), + rPolyRange.maOrient.begin(), + rPolyRange.maOrient.end()); + updateBounds(); + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + maRanges.erase(maRanges.begin()+nIndex,maRanges.begin()+nIndex+nCount); + maOrient.erase(maOrient.begin()+nIndex,maOrient.begin()+nIndex+nCount); + updateBounds(); + } + + void clear() + { + std::vector<B2DRange> aTmpRanges; + std::vector<B2VectorOrientation> aTmpOrient; + + maRanges.swap(aTmpRanges); + maOrient.swap(aTmpOrient); + + maBounds.reset(); + } + + void flip() + { + std::for_each(maOrient.begin(), + maOrient.end(), + boost::bind( + &flipOrientation, + _1)); + } + + B2DRange getBounds() const + { + return maBounds; + } + + template< typename ValueType > bool isInside( const ValueType& rValue ) const + { + if( !maBounds.isInside( rValue ) ) + return false; + + // cannot use boost::bind here, since isInside is overloaded. + // It is currently not possible to resolve the overload + // by considering one of the other template arguments. + std::vector<B2DRange>::const_iterator aCurr( maRanges.begin() ); + const std::vector<B2DRange>::const_iterator aEnd ( maRanges.end() ); + while( aCurr != aEnd ) + if( aCurr->isInside( rValue ) ) + return true; + + return false; + } + + bool overlaps( const B2DRange& rRange ) const + { + if( !maBounds.overlaps( rRange ) ) + return false; + + const std::vector<B2DRange>::const_iterator aEnd( maRanges.end() ); + return std::find_if( maRanges.begin(), + aEnd, + boost::bind<bool>( boost::mem_fn( &B2DRange::overlaps ), + _1, + boost::cref(rRange) ) ) != aEnd; + } + + B2DPolyPolygon solveCrossovers() const + { + return tools::solveCrossovers(maRanges,maOrient); + } + + const B2DRange* begin() const + { + if(maRanges.empty()) + return 0; + else + return &maRanges.front(); + } + + const B2DRange* end() const + { + if(maRanges.empty()) + return 0; + else + return (&maRanges.back())+1; + } + + B2DRange* begin() + { + if(maRanges.empty()) + return 0; + else + return &maRanges.front(); + } + + B2DRange* end() + { + if(maRanges.empty()) + return 0; + else + return (&maRanges.back())+1; + } + + private: + B2DRange maBounds; + std::vector<B2DRange> maRanges; + std::vector<B2VectorOrientation> maOrient; + }; + + B2DPolyRange::B2DPolyRange() : + mpImpl() + {} + + B2DPolyRange::~B2DPolyRange() + {} + + B2DPolyRange::B2DPolyRange( const ElementType& rElem ) : + mpImpl( ImplB2DPolyRange( rElem ) ) + {} + + B2DPolyRange::B2DPolyRange( const B2DRange& rRange, B2VectorOrientation eOrient ) : + mpImpl( ImplB2DPolyRange( rRange, eOrient ) ) + {} + + B2DPolyRange::B2DPolyRange( const B2DPolyRange& rRange ) : + mpImpl( rRange.mpImpl ) + {} + + B2DPolyRange& B2DPolyRange::operator=( const B2DPolyRange& rRange ) + { + mpImpl = rRange.mpImpl; + return *this; + } + + void B2DPolyRange::makeUnique() + { + mpImpl.make_unique(); + } + + bool B2DPolyRange::operator==(const B2DPolyRange& rRange) const + { + if(mpImpl.same_object(rRange.mpImpl)) + return true; + + return ((*mpImpl) == (*rRange.mpImpl)); + } + + bool B2DPolyRange::operator!=(const B2DPolyRange& rRange) const + { + return !(*this == rRange); + } + + sal_uInt32 B2DPolyRange::count() const + { + return mpImpl->count(); + } + + B2DPolyRange::ElementType B2DPolyRange::getElement(sal_uInt32 nIndex) const + { + return mpImpl->getElement(nIndex); + } + + void B2DPolyRange::setElement(sal_uInt32 nIndex, const ElementType& rElement ) + { + mpImpl->setElement(nIndex, rElement); + } + + void B2DPolyRange::setElement(sal_uInt32 nIndex, const B2DRange& rRange, B2VectorOrientation eOrient ) + { + mpImpl->setElement(nIndex, rRange, eOrient ); + } + + void B2DPolyRange::insertElement(sal_uInt32 nIndex, const ElementType& rElement, sal_uInt32 nCount) + { + mpImpl->insertElement(nIndex, rElement, nCount ); + } + + void B2DPolyRange::insertElement(sal_uInt32 nIndex, const B2DRange& rRange, B2VectorOrientation eOrient, sal_uInt32 nCount) + { + mpImpl->insertElement(nIndex, rRange, eOrient, nCount ); + } + + void B2DPolyRange::appendElement(const ElementType& rElement, sal_uInt32 nCount) + { + mpImpl->appendElement(rElement, nCount); + } + + void B2DPolyRange::appendElement(const B2DRange& rRange, B2VectorOrientation eOrient, sal_uInt32 nCount) + { + mpImpl->appendElement(rRange, eOrient, nCount ); + } + + void B2DPolyRange::insertPolyRange(sal_uInt32 nIndex, const B2DPolyRange& rRange) + { + mpImpl->insertPolyRange(nIndex, *rRange.mpImpl); + } + + void B2DPolyRange::appendPolyRange(const B2DPolyRange& rRange) + { + mpImpl->appendPolyRange(*rRange.mpImpl); + } + + void B2DPolyRange::remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + mpImpl->remove(nIndex, nCount); + } + + void B2DPolyRange::clear() + { + mpImpl->clear(); + } + + void B2DPolyRange::flip() + { + mpImpl->flip(); + } + + B2DRange B2DPolyRange::getBounds() const + { + return mpImpl->getBounds(); + } + + bool B2DPolyRange::isInside( const B2DTuple& rTuple ) const + { + return mpImpl->isInside(rTuple); + } + + bool B2DPolyRange::isInside( const B2DRange& rRange ) const + { + return mpImpl->isInside(rRange); + } + + bool B2DPolyRange::overlaps( const B2DRange& rRange ) const + { + return mpImpl->overlaps(rRange); + } + + B2DPolyPolygon B2DPolyRange::solveCrossovers() const + { + return mpImpl->solveCrossovers(); + } + + const B2DRange* B2DPolyRange::begin() const + { + return mpImpl->begin(); + } + + const B2DRange* B2DPolyRange::end() const + { + return mpImpl->end(); + } + + B2DRange* B2DPolyRange::begin() + { + return mpImpl->begin(); + } + + B2DRange* B2DPolyRange::end() + { + return mpImpl->end(); + } + +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/range/b2drange.cxx b/basegfx/source/range/b2drange.cxx new file mode 100644 index 000000000000..693470c3ef14 --- /dev/null +++ b/basegfx/source/range/b2drange.cxx @@ -0,0 +1,74 @@ +/************************************************************************* + * + * 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/range/b2drange.hxx> +#include <basegfx/range/b2irange.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> + +namespace basegfx +{ + B2DRange::B2DRange( const B2IRange& rRange ) : + maRangeX(), + maRangeY() + { + if( !rRange.isEmpty() ) + { + maRangeX = rRange.getMinX(); + maRangeY = rRange.getMinY(); + + maRangeX.expand(rRange.getMaxX()); + maRangeY.expand(rRange.getMaxY()); + } + } + + void B2DRange::transform(const B2DHomMatrix& rMatrix) + { + if(!isEmpty() && !rMatrix.isIdentity()) + { + const B2DRange aSource(*this); + reset(); + expand(rMatrix * B2DPoint(aSource.getMinX(), aSource.getMinY())); + expand(rMatrix * B2DPoint(aSource.getMaxX(), aSource.getMinY())); + expand(rMatrix * B2DPoint(aSource.getMinX(), aSource.getMaxY())); + expand(rMatrix * B2DPoint(aSource.getMaxX(), aSource.getMaxY())); + } + } + + B2IRange fround(const B2DRange& rRange) + { + return rRange.isEmpty() ? + B2IRange() : + B2IRange(fround(rRange.getMinimum()), + fround(rRange.getMaximum())); + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/range/b2drangeclipper.cxx b/basegfx/source/range/b2drangeclipper.cxx new file mode 100644 index 000000000000..524479b4fde0 --- /dev/null +++ b/basegfx/source/range/b2drangeclipper.cxx @@ -0,0 +1,950 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: b2dmultirange.cxx,v $ + * $Revision: 1.8 $ + * + * 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 <rtl/math.hxx> + +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/range/b2dpolyrange.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> + +#include <o3tl/vector_pool.hxx> +#include <boost/bind.hpp> +#include <boost/utility.hpp> + +#include <algorithm> +#include <deque> +#include <list> + + +namespace basegfx +{ + namespace + { + // Generating a poly-polygon from a bunch of rectangles + // + // Helper functionality for sweep-line algorithm + // ==================================================== + + typedef std::vector<B2DRange> VectorOfRanges; + + class ImplPolygon; + typedef o3tl::vector_pool<ImplPolygon> VectorOfPolygons; + + + /** This class represents an active edge + + As the sweep line traverses across the overall area, + rectangle edges parallel to it generate events, and + rectangle edges orthogonal to it generate active + edges. This class represents the latter. + */ + class ActiveEdge + { + public: + /** The two possible active rectangle edges differ by one + coordinate value - the upper edge has the lower, the + lower edge the higher value. + */ + enum EdgeType { + /// edge with lower coordinate value + UPPER=0, + /// edge with higher coordinate value + LOWER=1 + }; + + enum EdgeDirection { + /// edge proceeds to the left + PROCEED_LEFT=0, + /// edge proceeds to the right + PROCEED_RIGHT=1 + }; + + /** Create active edge + + @param rRect + Rectangle this edge is part of + + @param fInvariantCoord + The invariant ccordinate value of this edge + + @param eEdgeType + Is fInvariantCoord the lower or the higher value, for + this rect? + */ + ActiveEdge( const B2DRectangle& rRect, + const double& fInvariantCoord, + std::ptrdiff_t nPolyIdx, + EdgeType eEdgeType, + EdgeDirection eEdgeDirection ) : + mfInvariantCoord(fInvariantCoord), + mpAssociatedRect( &rRect ), + mnPolygonIdx( nPolyIdx ), + meEdgeType( eEdgeType ), + meEdgeDirection( eEdgeDirection ) + {} + + double getInvariantCoord() const { return mfInvariantCoord; } + const B2DRectangle& getRect() const { return *mpAssociatedRect; } + std::ptrdiff_t getTargetPolygonIndex() const { return mnPolygonIdx; } + void setTargetPolygonIndex( std::ptrdiff_t nIdx ) { mnPolygonIdx = nIdx; } + EdgeType getEdgeType() const { return meEdgeType; } + EdgeDirection getEdgeDirection() const { return meEdgeDirection; } + + /// For STL sort + bool operator<( const ActiveEdge& rRHS ) const { return mfInvariantCoord < rRHS.mfInvariantCoord; } + + private: + /** The invariant coordinate value of this edge (e.g. the + common y value, for a horizontal edge) + */ + double mfInvariantCoord; + + /** Associated rectangle + + This on the one hand saves some storage space (the + vector of rectangles is persistent, anyway), and on + the other hand provides an identifier to match active + edges and x events (see below) + + Ptr because class needs to be assignable + */ + const B2DRectangle* mpAssociatedRect; + + /** Index of the polygon this edge is currently involved + with. + + Note that this can change for some kinds of edge + intersection, as the algorithm tends to swap + associated polygons there. + + -1 denotes no assigned polygon + */ + std::ptrdiff_t mnPolygonIdx; + + /// 'upper' or 'lower' edge of original rectangle. + EdgeType meEdgeType; + + /// 'left' or 'right' + EdgeDirection meEdgeDirection; + }; + + // Needs to be list - various places hold ptrs to elements + typedef std::list< ActiveEdge > ListOfEdges; + + + /** Element of the sweep line event list + + As the sweep line traverses across the overall area, + rectangle edges parallel to it generate events, and + rectangle edges orthogonal to it generate active + edges. This class represents the former. + + The class defines an element of the sweep line list. The + sweep line's position jumps in steps defined by the + coordinates of the sorted SweepLineEvent entries. + */ + class SweepLineEvent + { + public: + /** The two possible sweep line rectangle edges differ by + one coordinate value - the starting edge has the + lower, the finishing edge the higher value. + */ + enum EdgeType { + /// edge with lower coordinate value + STARTING_EDGE=0, + /// edge with higher coordinate value + FINISHING_EDGE=1 + }; + + /** The two possible sweep line directions + */ + enum EdgeDirection { + PROCEED_UP=0, + PROCEED_DOWN=1 + }; + + /** Create sweep line event + + @param fPos + Coordinate position of the event + + @param rRect + Rectangle this event is generated for. + + @param eEdgeType + Is fPos the lower or the higher value, for the + rectangle this event is generated for? + */ + SweepLineEvent( double fPos, + const B2DRectangle& rRect, + EdgeType eEdgeType, + EdgeDirection eDirection) : + mfPos( fPos ), + mpAssociatedRect( &rRect ), + meEdgeType( eEdgeType ), + meEdgeDirection( eDirection ) + {} + + double getPos() const { return mfPos; } + const B2DRectangle& getRect() const { return *mpAssociatedRect; } + EdgeType getEdgeType() const { return meEdgeType; } + EdgeDirection getEdgeDirection() const { return meEdgeDirection; } + + /// For STL sort + bool operator<( const SweepLineEvent& rRHS ) const { return mfPos < rRHS.mfPos; } + + private: + /// position of the event, in the direction of the line sweep + double mfPos; + + /** Rectangle this event is generated for + + This on the one hand saves some storage space (the + vector of rectangles is persistent, anyway), and on + the other hand provides an identifier to match active + edges and events (see below) + + Ptr because class needs to be assignable + */ + const B2DRectangle* mpAssociatedRect; + + /// 'upper' or 'lower' edge of original rectangle. + EdgeType meEdgeType; + + /// 'up' or 'down' + EdgeDirection meEdgeDirection; + }; + + typedef std::vector< SweepLineEvent > VectorOfEvents; + + + /** Smart point container for B2DMultiRange::getPolyPolygon() + + This class provides methods needed only here, and is used + as a place to store some additional information per + polygon. Also, most of the intersection logic is + implemented here. + */ + class ImplPolygon + { + public: + /** Create polygon + */ + ImplPolygon() : + mpLeadingRightEdge(NULL), + mnIdx(-1), + maPoints(), + mbIsFinished(false) + { + // completely ad-hoc. but what the hell. + maPoints.reserve(11); + } + + void setPolygonPoolIndex( std::ptrdiff_t nIdx ) { mnIdx = nIdx; } + bool isFinished() const { return mbIsFinished; } + + /// Add point to the end of the existing points + void append( const B2DPoint& rPoint ) + { + OSL_PRECOND( maPoints.empty() || + maPoints.back().getX() == rPoint.getX() || + maPoints.back().getY() == rPoint.getY(), + "ImplPolygon::append(): added point violates 90 degree line angle constraint!" ); + + if( maPoints.empty() || + maPoints.back() != rPoint ) + { + // avoid duplicate points + maPoints.push_back( rPoint ); + } + } + + /** Perform the intersection of this polygon with an + active edge. + + @param rEvent + The vertical line event that generated the + intersection + + @param rActiveEdge + The active edge that generated the intersection + + @param rPolygonPool + Polygon pool, we sometimes need to allocate a new one + + @param bIsFinishingEdge + True, when this is hitting the last edge of the + vertical sweep - every vertical sweep starts and ends + with upper and lower edge of the _same_ rectangle. + + @return the new current polygon (that's the one + processing must proceed with, when going through the + list of upcoming active edges). + */ + std::ptrdiff_t intersect( SweepLineEvent& rEvent, + ActiveEdge& rActiveEdge, + VectorOfPolygons& rPolygonPool, + B2DPolyPolygon& rRes, + bool isFinishingEdge ) + { + OSL_PRECOND( !isFinished(), + "ImplPolygon::intersect(): called on already finished polygon!" ); + OSL_PRECOND( !isFinishingEdge + || (isFinishingEdge && &rEvent.getRect() == &rActiveEdge.getRect()), + "ImplPolygon::intersect(): inconsistent ending!" ); + + const B2DPoint aIntersectionPoint( rEvent.getPos(), + rActiveEdge.getInvariantCoord() ); + + // intersection point, goes to our polygon + // unconditionally + append(aIntersectionPoint); + + const bool isSweepLineEnteringRect( + rEvent.getEdgeType() == SweepLineEvent::STARTING_EDGE); + if( isFinishingEdge ) + { + if( isSweepLineEnteringRect ) + handleFinalOwnRightEdge(rActiveEdge); + else + handleFinalOwnLeftEdge(rActiveEdge, + rPolygonPool, + rRes); + + // we're done with this rect & sweep line + return -1; + } + else if( metOwnEdge(rEvent,rActiveEdge) ) + { + handleInitialOwnEdge(rEvent, rActiveEdge); + + // point already added, all init done, continue + // with same poly + return mnIdx; + } + else + { + OSL_ENSURE( rActiveEdge.getTargetPolygonIndex() != -1, + "ImplPolygon::intersect(): non-trivial intersection hit empty polygon!" ); + + const bool isHittingLeftEdge( + rActiveEdge.getEdgeDirection() == ActiveEdge::PROCEED_LEFT); + + if( isHittingLeftEdge ) + return handleComplexLeftEdge(rActiveEdge, + aIntersectionPoint, + rPolygonPool, + rRes); + else + return handleComplexRightEdge(rActiveEdge, + aIntersectionPoint, + rPolygonPool); + } + } + + private: + std::ptrdiff_t getPolygonPoolIndex() const { return mnIdx; } + + void handleInitialOwnEdge(SweepLineEvent& rEvent, + ActiveEdge& rActiveEdge) + { + const bool isActiveEdgeProceedLeft( + rActiveEdge.getEdgeDirection() == ActiveEdge::PROCEED_LEFT); + const bool isSweepLineEnteringRect( + rEvent.getEdgeType() == SweepLineEvent::STARTING_EDGE); + (void)isActiveEdgeProceedLeft; + (void)isSweepLineEnteringRect; + + OSL_ENSURE( isSweepLineEnteringRect == isActiveEdgeProceedLeft, + "ImplPolygon::intersect(): sweep initial own edge hit: wrong polygon order" ); + + OSL_ENSURE( isSweepLineEnteringRect || + mpLeadingRightEdge == &rActiveEdge, + "ImplPolygon::intersect(): sweep initial own edge hit: wrong leading edge" ); + } + + void handleFinalOwnRightEdge(ActiveEdge& rActiveEdge) + { + OSL_ENSURE( rActiveEdge.getEdgeDirection() == ActiveEdge::PROCEED_RIGHT, + "ImplPolygon::handleInitialOwnRightEdge(): start edge wrong polygon order" ); + + rActiveEdge.setTargetPolygonIndex(mnIdx); + mpLeadingRightEdge = &rActiveEdge; + } + + void handleFinalOwnLeftEdge(ActiveEdge& rActiveEdge, + VectorOfPolygons& rPolygonPool, + B2DPolyPolygon& rRes) + { + OSL_ENSURE( rActiveEdge.getEdgeDirection() == ActiveEdge::PROCEED_LEFT, + "ImplPolygon::handleFinalOwnLeftEdge(): end edge wrong polygon order" ); + + const bool isHittingOurTail( + rActiveEdge.getTargetPolygonIndex() == mnIdx); + + if( isHittingOurTail ) + finish(rRes); // just finish. no fuss. + else + { + // temp poly hits final left edge + const std::ptrdiff_t nTmpIdx=rActiveEdge.getTargetPolygonIndex(); + ImplPolygon& rTmp=rPolygonPool.get(nTmpIdx); + + // active edge's polygon has points + // already. ours need to go in front of them. + maPoints.insert(maPoints.end(), + rTmp.maPoints.begin(), + rTmp.maPoints.end()); + + // adjust leading edges, we're switching the polygon + ActiveEdge* const pFarEdge=rTmp.mpLeadingRightEdge; + + mpLeadingRightEdge = pFarEdge; + pFarEdge->setTargetPolygonIndex(mnIdx); + + // nTmpIdx is an empty shell, get rid of it + rPolygonPool.free(nTmpIdx); + } + } + + std::ptrdiff_t handleComplexLeftEdge(ActiveEdge& rActiveEdge, + const B2DPoint& rIntersectionPoint, + VectorOfPolygons& rPolygonPool, + B2DPolyPolygon& rRes) + { + const bool isHittingOurTail( + rActiveEdge.getTargetPolygonIndex() == mnIdx); + if( isHittingOurTail ) + { + finish(rRes); + + // so "this" is done - need new polygon to collect + // further points + const std::ptrdiff_t nIdxNewPolygon=rPolygonPool.alloc(); + rPolygonPool.get(nIdxNewPolygon).setPolygonPoolIndex(nIdxNewPolygon); + rPolygonPool.get(nIdxNewPolygon).append(rIntersectionPoint); + + rActiveEdge.setTargetPolygonIndex(nIdxNewPolygon); + + return nIdxNewPolygon; + } + else + { + const std::ptrdiff_t nTmpIdx=rActiveEdge.getTargetPolygonIndex(); + ImplPolygon& rTmp=rPolygonPool.get(nTmpIdx); + + // active edge's polygon has points + // already. ours need to go in front of them. + maPoints.insert(maPoints.end(), + rTmp.maPoints.begin(), + rTmp.maPoints.end()); + + rTmp.maPoints.clear(); + rTmp.append(rIntersectionPoint); + + // adjust leading edges, we're switching the polygon + ActiveEdge* const pFarEdge=rTmp.mpLeadingRightEdge; + ActiveEdge* const pNearEdge=&rActiveEdge; + + rTmp.mpLeadingRightEdge = NULL; + pNearEdge->setTargetPolygonIndex(nTmpIdx); + + mpLeadingRightEdge = pFarEdge; + pFarEdge->setTargetPolygonIndex(mnIdx); + + return nTmpIdx; + } + } + + std::ptrdiff_t handleComplexRightEdge(ActiveEdge& rActiveEdge, + const B2DPoint& rIntersectionPoint, + VectorOfPolygons& rPolygonPool) + { + const std::ptrdiff_t nTmpIdx=rActiveEdge.getTargetPolygonIndex(); + ImplPolygon& rTmp=rPolygonPool.get(nTmpIdx); + + rTmp.append(rIntersectionPoint); + + rActiveEdge.setTargetPolygonIndex(mnIdx); + mpLeadingRightEdge = &rActiveEdge; + + rTmp.mpLeadingRightEdge = NULL; + + return nTmpIdx; + } + + /// True when sweep line hits our own active edge + bool metOwnEdge(const SweepLineEvent& rEvent, + ActiveEdge& rActiveEdge) + { + const bool bHitOwnEdge=&rEvent.getRect() == &rActiveEdge.getRect(); + return bHitOwnEdge; + } + + /// Retrieve B2DPolygon from this object + B2DPolygon getPolygon() const + { + B2DPolygon aRes; + std::for_each( maPoints.begin(), + maPoints.end(), + boost::bind( + &B2DPolygon::append, + boost::ref(aRes), + _1, + 1 ) ); + aRes.setClosed( true ); + return aRes; + } + + /** Finish this polygon, push to result set. + */ + void finish(B2DPolyPolygon& rRes) + { + OSL_PRECOND( maPoints.empty() || + maPoints.front().getX() == maPoints.back().getX() || + maPoints.front().getY() == maPoints.back().getY(), + "ImplPolygon::finish(): first and last point violate 90 degree line angle constraint!" ); + + mbIsFinished = true; + mpLeadingRightEdge = NULL; + + rRes.append(getPolygon()); + } + + /** Refers to the current leading edge element of this + polygon, or NULL. The leading edge denotes the 'front' + of the polygon vertex sequence, i.e. the coordinates + at the polygon's leading edge are returned from + maPoints.front() + */ + ActiveEdge* mpLeadingRightEdge; + + /// current index into vector pool + std::ptrdiff_t mnIdx; + + /// Container for the actual polygon points + std::vector<B2DPoint> maPoints; + + /// When true, this polygon is 'done', i.e. nothing must be added anymore. + bool mbIsFinished; + }; + + /** Init sweep line event list + + This method fills the event list with the sweep line + events generated from the input rectangles, and sorts them + with increasing x. + */ + void setupSweepLineEventListFromRanges( VectorOfEvents& o_rEventVector, + const std::vector<B2DRange>& rRanges, + const std::vector<B2VectorOrientation>& rOrientations ) + { + // we need exactly 2*rectVec.size() events: one for the + // left, and one for the right edge of each rectangle + o_rEventVector.clear(); + o_rEventVector.reserve( 2*rRanges.size() ); + + // generate events + // =============== + + // first pass: add all left edges in increasing order + std::vector<B2DRange>::const_iterator aCurrRect=rRanges.begin(); + std::vector<B2VectorOrientation>::const_iterator aCurrOrientation=rOrientations.begin(); + const std::vector<B2DRange>::const_iterator aEnd=rRanges.end(); + const std::vector<B2VectorOrientation>::const_iterator aEndOrientation=rOrientations.end(); + while( aCurrRect != aEnd && aCurrOrientation != aEndOrientation ) + { + const B2DRectangle& rCurrRect( *aCurrRect++ ); + + o_rEventVector.push_back( + SweepLineEvent( rCurrRect.getMinX(), + rCurrRect, + SweepLineEvent::STARTING_EDGE, + (*aCurrOrientation++) == ORIENTATION_POSITIVE ? + SweepLineEvent::PROCEED_UP : SweepLineEvent::PROCEED_DOWN) ); + } + + // second pass: add all right edges in reversed order + std::vector<B2DRange>::const_reverse_iterator aCurrRectR=rRanges.rbegin(); + std::vector<B2VectorOrientation>::const_reverse_iterator aCurrOrientationR=rOrientations.rbegin(); + const std::vector<B2DRange>::const_reverse_iterator aEndR=rRanges.rend(); + const std::vector<B2VectorOrientation>::const_reverse_iterator aEndOrientationR=rOrientations.rend(); + while( aCurrRectR != aEndR ) + { + const B2DRectangle& rCurrRect( *aCurrRectR++ ); + + o_rEventVector.push_back( + SweepLineEvent( rCurrRect.getMaxX(), + rCurrRect, + SweepLineEvent::FINISHING_EDGE, + (*aCurrOrientationR++) == ORIENTATION_POSITIVE ? + SweepLineEvent::PROCEED_DOWN : SweepLineEvent::PROCEED_UP ) ); + } + + // sort events + // =========== + + // since we use stable_sort, the order of events with the + // same x value will not change. The elaborate two-pass + // add above thus ensures, that for each two rectangles + // with similar left and right x coordinates, the + // rectangle whose left event comes first will have its + // right event come last. This is advantageous for the + // clip algorithm below, see handleRightEdgeCrossing(). + + // TODO(P3): Use radix sort (from + // b2dpolypolygonrasterconverter, or have your own + // templatized version). + std::stable_sort( o_rEventVector.begin(), + o_rEventVector.end() ); + } + + /** Insert two active edge segments for the given rectangle. + + This method creates two active edge segments from the + given rect, and inserts them into the active edge list, + such that this stays sorted (if it was before). + + @param io_rEdgeList + Active edge list to insert into + + @param io_rPolygons + Vector of polygons. Each rectangle added creates one + tentative result polygon in this vector, and the edge list + entries holds a reference to that polygon (this _requires_ + that the polygon vector does not reallocate, i.e. it must + have at least the maximal number of rectangles reserved) + + @param o_CurrentPolygon + The then-current polygon when processing this sweep line + event + + @param rCurrEvent + The actual event that caused this call + */ + void createActiveEdgesFromStartEvent( ListOfEdges& io_rEdgeList, + VectorOfPolygons& io_rPolygonPool, + SweepLineEvent& rCurrEvent ) + { + ListOfEdges aNewEdges; + const B2DRectangle& rRect=rCurrEvent.getRect(); + const bool bGoesDown=rCurrEvent.getEdgeDirection() == SweepLineEvent::PROCEED_DOWN; + + // start event - new rect starts here, needs polygon to + // collect points into + const std::ptrdiff_t nIdxPolygon=io_rPolygonPool.alloc(); + io_rPolygonPool.get(nIdxPolygon).setPolygonPoolIndex(nIdxPolygon); + + // upper edge + aNewEdges.push_back( + ActiveEdge( + rRect, + rRect.getMinY(), + bGoesDown ? nIdxPolygon : -1, + ActiveEdge::UPPER, + bGoesDown ? ActiveEdge::PROCEED_LEFT : ActiveEdge::PROCEED_RIGHT) ); + // lower edge + aNewEdges.push_back( + ActiveEdge( + rRect, + rRect.getMaxY(), + bGoesDown ? -1 : nIdxPolygon, + ActiveEdge::LOWER, + bGoesDown ? ActiveEdge::PROCEED_RIGHT : ActiveEdge::PROCEED_LEFT ) ); + + // furthermore, have to respect a special tie-breaking + // rule here, for edges which share the same y value: + // newly added upper edges must be inserted _before_ any + // other edge with the same y value, and newly added lower + // edges must be _after_ all other edges with the same + // y. This ensures that the left vertical edge processing + // below encounters the upper edge of the current rect + // first, and the lower edge last, which automatically + // starts and finishes this rect correctly (as only then, + // the polygon will have their associated active edges + // set). + const double nMinY( rRect.getMinY() ); + const double nMaxY( rRect.getMaxY() ); + ListOfEdges::iterator aCurr( io_rEdgeList.begin() ); + const ListOfEdges::iterator aEnd ( io_rEdgeList.end() ); + while( aCurr != aEnd ) + { + const double nCurrY( aCurr->getInvariantCoord() ); + + if( nCurrY >= nMinY && + aNewEdges.size() == 2 ) // only add, if not yet done. + { + // insert upper edge _before_ aCurr. Thus, it will + // be the first entry for a range of equal y + // values. Using splice here, since we hold + // references to the moved list element! + io_rEdgeList.splice( aCurr, + aNewEdges, + aNewEdges.begin() ); + } + + if( nCurrY > nMaxY ) + { + // insert lower edge _before_ aCurr. Thus, it will + // be the last entry for a range of equal y values + // (aCurr is the first entry strictly larger than + // nMaxY). Using splice here, since we hold + // references to the moved list element! + io_rEdgeList.splice( aCurr, + aNewEdges, + aNewEdges.begin() ); + // done with insertion, can early-exit here. + return; + } + + ++aCurr; + } + + // append remainder of aNewList (might still contain 2 or + // 1 elements, depending of the contents of io_rEdgeList). + io_rEdgeList.splice( aCurr, + aNewEdges ); + } + + inline bool isSameRect(ActiveEdge& rEdge, + const basegfx::B2DRange& rRect) + { + return &rEdge.getRect() == &rRect; + } + + // wow what a hack. necessary because stl's list::erase does + // not eat reverse_iterator + template<typename Cont, typename Iter> Iter eraseFromList(Cont&, Iter); + template<> inline ListOfEdges::iterator eraseFromList( + ListOfEdges& rList, ListOfEdges::iterator aIter) + { + return rList.erase(aIter); + } + template<> inline ListOfEdges::reverse_iterator eraseFromList( + ListOfEdges& rList, ListOfEdges::reverse_iterator aIter) + { + return ListOfEdges::reverse_iterator( + rList.erase(boost::prior(aIter.base()))); + } + + template<int bPerformErase, + typename Iterator> inline void processActiveEdges( + Iterator first, + Iterator last, + ListOfEdges& rActiveEdgeList, + SweepLineEvent& rCurrEvent, + VectorOfPolygons& rPolygonPool, + B2DPolyPolygon& rRes ) + { + const basegfx::B2DRange& rCurrRect=rCurrEvent.getRect(); + + // fast-forward to rCurrEvent's first active edge (holds + // for both starting and finishing sweep line events, a + // rect is regarded _outside_ any rects whose events have + // started earlier + first = std::find_if(first, last, + boost::bind( + &isSameRect, + _1, + boost::cref(rCurrRect))); + + if(first == last) + return; + + int nCount=0; + std::ptrdiff_t nCurrPolyIdx=-1; + while(first != last) + { + if( nCurrPolyIdx == -1 ) + nCurrPolyIdx=first->getTargetPolygonIndex(); + + OSL_ASSERT(nCurrPolyIdx != -1); + + // second encounter of my rect -> second edge + // encountered, done + const bool bExit= + nCount && + isSameRect(*first, + rCurrRect); + + // deal with current active edge + nCurrPolyIdx = + rPolygonPool.get(nCurrPolyIdx).intersect( + rCurrEvent, + *first, + rPolygonPool, + rRes, + bExit); + + // prune upper & lower active edges, if requested + if( bPerformErase && (bExit || !nCount) ) + first = eraseFromList(rActiveEdgeList,first); + else + ++first; + + // delayed exit, had to prune first + if( bExit ) + return; + + ++nCount; + } + } + + template<int bPerformErase> inline void processActiveEdgesTopDown( + SweepLineEvent& rCurrEvent, + ListOfEdges& rActiveEdgeList, + VectorOfPolygons& rPolygonPool, + B2DPolyPolygon& rRes ) + { + processActiveEdges<bPerformErase>( + rActiveEdgeList. begin(), + rActiveEdgeList. end(), + rActiveEdgeList, + rCurrEvent, + rPolygonPool, + rRes); + } + + template<int bPerformErase> inline void processActiveEdgesBottomUp( + SweepLineEvent& rCurrEvent, + ListOfEdges& rActiveEdgeList, + VectorOfPolygons& rPolygonPool, + B2DPolyPolygon& rRes ) + { + processActiveEdges<bPerformErase>( + rActiveEdgeList. rbegin(), + rActiveEdgeList. rend(), + rActiveEdgeList, + rCurrEvent, + rPolygonPool, + rRes); + } + + enum{ NoErase=0, PerformErase=1 }; + + void handleStartingEdge( SweepLineEvent& rCurrEvent, + ListOfEdges& rActiveEdgeList, + VectorOfPolygons& rPolygonPool, + B2DPolyPolygon& rRes) + { + // inject two new active edges for rect + createActiveEdgesFromStartEvent( rActiveEdgeList, + rPolygonPool, + rCurrEvent ); + + if( SweepLineEvent::PROCEED_DOWN == rCurrEvent.getEdgeDirection() ) + processActiveEdgesTopDown<NoErase>( + rCurrEvent, rActiveEdgeList, rPolygonPool, rRes); + else + processActiveEdgesBottomUp<NoErase>( + rCurrEvent, rActiveEdgeList, rPolygonPool, rRes); + } + + void handleFinishingEdge( SweepLineEvent& rCurrEvent, + ListOfEdges& rActiveEdgeList, + VectorOfPolygons& rPolygonPool, + B2DPolyPolygon& rRes) + { + if( SweepLineEvent::PROCEED_DOWN == rCurrEvent.getEdgeDirection() ) + processActiveEdgesTopDown<PerformErase>( + rCurrEvent, rActiveEdgeList, rPolygonPool, rRes); + else + processActiveEdgesBottomUp<PerformErase>( + rCurrEvent, rActiveEdgeList, rPolygonPool, rRes); + } + + inline void handleSweepLineEvent( SweepLineEvent& rCurrEvent, + ListOfEdges& rActiveEdgeList, + VectorOfPolygons& rPolygonPool, + B2DPolyPolygon& rRes) + { + if( SweepLineEvent::STARTING_EDGE == rCurrEvent.getEdgeType() ) + handleStartingEdge(rCurrEvent,rActiveEdgeList,rPolygonPool,rRes); + else + handleFinishingEdge(rCurrEvent,rActiveEdgeList,rPolygonPool,rRes); + } + } + + namespace tools + { + B2DPolyPolygon solveCrossovers(const std::vector<B2DRange>& rRanges, + const std::vector<B2VectorOrientation>& rOrientations) + { + // sweep-line algorithm to generate a poly-polygon + // from a bunch of rectangles + // =============================================== + // + // This algorithm uses the well-known sweep line + // concept, explained in every good text book about + // computational geometry. + // + // We start with creating two structures for every + // rectangle, one representing the left x coordinate, + // one representing the right x coordinate (and both + // referencing the original rect). These structs are + // sorted with increasing x coordinates. + // + // Then, we start processing the resulting list from + // the beginning. Every entry in the list defines a + // point in time of the line sweeping from left to + // right across all rectangles. + VectorOfEvents aSweepLineEvents; + setupSweepLineEventListFromRanges( aSweepLineEvents, + rRanges, + rOrientations ); + + B2DPolyPolygon aRes; + VectorOfPolygons aPolygonPool; + ListOfEdges aActiveEdgeList; + + // sometimes not enough, but a usable compromise + aPolygonPool.reserve( rRanges.size() ); + + std::for_each( aSweepLineEvents.begin(), + aSweepLineEvents.end(), + boost::bind( + &handleSweepLineEvent, + _1, + boost::ref(aActiveEdgeList), + boost::ref(aPolygonPool), + boost::ref(aRes)) ); + + return aRes; + } + } +} + diff --git a/basegfx/source/range/b2xrange.cxx b/basegfx/source/range/b2xrange.cxx new file mode 100644 index 000000000000..296b54574800 --- /dev/null +++ b/basegfx/source/range/b2xrange.cxx @@ -0,0 +1,142 @@ +/************************************************************************* + * + * 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/range/b2drange.hxx> +#include <basegfx/range/b2irange.hxx> +#include <basegfx/range/b2ibox.hxx> + + +namespace basegfx +{ + namespace + { + /** Generic implementation of the difference set computation + + @tpl RangeType + Type to operate on. Must provide ValueType and TraitsType + nested types. + */ + template< class RangeType > void doComputeSetDifference( + ::std::vector< RangeType >& o_rRanges, + const RangeType& a, + const RangeType& b ) + { + o_rRanges.clear(); + + // special-casing the empty rect case (this will fail most + // of the times below, because of the DBL_MIN/MAX special + // values denoting emptyness in the rectangle. + if( a.isEmpty() ) + { + o_rRanges.push_back( b ); + return; + } + if( b.isEmpty() ) + { + o_rRanges.push_back( a ); + return; + } + + const typename RangeType::ValueType ax(a.getMinX()); + const typename RangeType::ValueType ay(a.getMinY()); + const typename RangeType::TraitsType::DifferenceType aw(a.getWidth()); + const typename RangeType::TraitsType::DifferenceType ah(a.getHeight()); + const typename RangeType::ValueType bx(b.getMinX()); + const typename RangeType::ValueType by(b.getMinY()); + const typename RangeType::TraitsType::DifferenceType bw(b.getWidth()); + const typename RangeType::TraitsType::DifferenceType bh(b.getHeight()); + + const typename RangeType::TraitsType::DifferenceType h0( (by > ay) ? by - ay : 0 ); + const typename RangeType::TraitsType::DifferenceType h3( (by + bh < ay + ah) ? ay + ah - by - bh : 0 ); + const typename RangeType::TraitsType::DifferenceType w1( (bx > ax) ? bx - ax : 0 ); + const typename RangeType::TraitsType::DifferenceType w2( (ax + aw > bx + bw) ? ax + aw - bx - bw : 0 ); + const typename RangeType::TraitsType::DifferenceType h12( (h0 + h3 < ah) ? ah - h0 - h3 : 0 ); + + // TODO(E2): Use numeric_cast instead of static_cast here, + // need range checks! + if (h0 > 0) + o_rRanges.push_back( + RangeType(ax,ay, + static_cast<typename RangeType::ValueType>(ax+aw), + static_cast<typename RangeType::ValueType>(ay+h0)) ); + + if (w1 > 0 && h12 > 0) + o_rRanges.push_back( + RangeType(ax, + static_cast<typename RangeType::ValueType>(ay+h0), + static_cast<typename RangeType::ValueType>(ax+w1), + static_cast<typename RangeType::ValueType>(ay+h0+h12)) ); + + if (w2 > 0 && h12 > 0) + o_rRanges.push_back( + RangeType(static_cast<typename RangeType::ValueType>(bx+bw), + static_cast<typename RangeType::ValueType>(ay+h0), + static_cast<typename RangeType::ValueType>(bx+bw+w2), + static_cast<typename RangeType::ValueType>(ay+h0+h12)) ); + + if (h3 > 0) + o_rRanges.push_back( + RangeType(ax, + static_cast<typename RangeType::ValueType>(ay+h0+h12), + static_cast<typename RangeType::ValueType>(ax+aw), + static_cast<typename RangeType::ValueType>(ay+h0+h12+h3)) ); + } + } + + ::std::vector< B2IRange >& computeSetDifference( ::std::vector< B2IRange >& o_rResult, + const B2IRange& rFirst, + const B2IRange& rSecond ) + { + doComputeSetDifference( o_rResult, rFirst, rSecond ); + + return o_rResult; + } + + ::std::vector< B2DRange >& computeSetDifference( ::std::vector< B2DRange >& o_rResult, + const B2DRange& rFirst, + const B2DRange& rSecond ) + { + doComputeSetDifference( o_rResult, rFirst, rSecond ); + + return o_rResult; + } + + ::std::vector< B2IBox >& computeSetDifference( ::std::vector< B2IBox >& o_rResult, + const B2IBox& rFirst, + const B2IBox& rSecond ) + { + doComputeSetDifference( o_rResult, rFirst, rSecond ); + + return o_rResult; + } + +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/range/b3drange.cxx b/basegfx/source/range/b3drange.cxx new file mode 100644 index 000000000000..aaeeae684bdd --- /dev/null +++ b/basegfx/source/range/b3drange.cxx @@ -0,0 +1,85 @@ +/************************************************************************* + * + * 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/range/b3drange.hxx> +#include <basegfx/range/b3irange.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> + +namespace basegfx +{ + B3DRange::B3DRange(const B3IRange& rRange) : + maRangeX(), + maRangeY(), + maRangeZ() + { + if( !rRange.isEmpty() ) + { + maRangeX = rRange.getMinX(); + maRangeY = rRange.getMinY(); + maRangeZ = rRange.getMinZ(); + + maRangeX.expand( rRange.getMaxX() ); + maRangeY.expand( rRange.getMaxY() ); + maRangeZ.expand( rRange.getMaxZ() ); + } + } + + void B3DRange::transform(const B3DHomMatrix& rMatrix) + { + if(!isEmpty() && !rMatrix.isIdentity()) + { + const B3DRange aSource(*this); + reset(); + expand(rMatrix * B3DPoint(aSource.getMinX(), aSource.getMinY(), aSource.getMinZ())); + expand(rMatrix * B3DPoint(aSource.getMaxX(), aSource.getMinY(), aSource.getMinZ())); + expand(rMatrix * B3DPoint(aSource.getMinX(), aSource.getMaxY(), aSource.getMinZ())); + expand(rMatrix * B3DPoint(aSource.getMaxX(), aSource.getMaxY(), aSource.getMinZ())); + expand(rMatrix * B3DPoint(aSource.getMinX(), aSource.getMinY(), aSource.getMaxZ())); + expand(rMatrix * B3DPoint(aSource.getMaxX(), aSource.getMinY(), aSource.getMaxZ())); + expand(rMatrix * B3DPoint(aSource.getMinX(), aSource.getMaxY(), aSource.getMaxZ())); + expand(rMatrix * B3DPoint(aSource.getMaxX(), aSource.getMaxY(), aSource.getMaxZ())); + } + } + + B3IRange fround(const B3DRange& rRange ) + { + return rRange.isEmpty() ? + B3IRange() : + B3IRange(fround(rRange.getMinX()), + fround(rRange.getMinY()), + fround(rRange.getMinZ()), + fround(rRange.getMaxX()), + fround(rRange.getMaxY()), + fround(rRange.getMaxZ())); + } + +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/range/makefile.mk b/basegfx/source/range/makefile.mk new file mode 100644 index 000000000000..5e05eeda94d9 --- /dev/null +++ b/basegfx/source/range/makefile.mk @@ -0,0 +1,52 @@ +#************************************************************************* +# +# 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=range + +#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb +ENABLE_EXCEPTIONS=TRUE +#USE_DEFFILE=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= \ + $(SLO)$/b1drange.obj \ + $(SLO)$/b2drange.obj \ + $(SLO)$/b2xrange.obj \ + $(SLO)$/b2dpolyrange.obj \ + $(SLO)$/b2drangeclipper.obj \ + $(SLO)$/b3drange.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/raster/bpixelraster.cxx b/basegfx/source/raster/bpixelraster.cxx new file mode 100644 index 000000000000..dd62ebd67634 --- /dev/null +++ b/basegfx/source/raster/bpixelraster.cxx @@ -0,0 +1,40 @@ +/************************************************************************* + * + * 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/raster/bpixelraster.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/raster/bzpixelraster.cxx b/basegfx/source/raster/bzpixelraster.cxx new file mode 100644 index 000000000000..dd1fe7719b1b --- /dev/null +++ b/basegfx/source/raster/bzpixelraster.cxx @@ -0,0 +1,40 @@ +/************************************************************************* + * + * 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/raster/bzpixelraster.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/raster/makefile.mk b/basegfx/source/raster/makefile.mk new file mode 100644 index 000000000000..1381b9f6d716 --- /dev/null +++ b/basegfx/source/raster/makefile.mk @@ -0,0 +1,49 @@ +#************************************************************************* +# +# 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=raster + +#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb +ENABLE_EXCEPTIONS=TRUE +#USE_DEFFILE=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= \ + $(SLO)$/bpixelraster.obj \ + $(SLO)$/bzpixelraster.obj \ + $(SLO)$/rasterconvert3d.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/raster/rasterconvert3d.cxx b/basegfx/source/raster/rasterconvert3d.cxx new file mode 100644 index 000000000000..2c42dc313451 --- /dev/null +++ b/basegfx/source/raster/rasterconvert3d.cxx @@ -0,0 +1,353 @@ +/************************************************************************* + * + * 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/raster/rasterconvert3d.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <basegfx/polygon/b3dpolypolygon.hxx> +#include <basegfx/point/b3dpoint.hxx> + +////////////////////////////////////////////////////////////////////////////// +// implementations of the 3D raster converter + +namespace basegfx +{ + void RasterConverter3D::addArea(const B3DPolygon& rFill, const B3DHomMatrix* pViewToEye) + { + const sal_uInt32 nPointCount(rFill.count()); + + for(sal_uInt32 a(0); a < nPointCount; a++) + { + addEdge(rFill, a, (a + 1) % nPointCount, pViewToEye); + } + } + + void RasterConverter3D::addArea(const B3DPolyPolygon& rFill, const B3DHomMatrix* pViewToEye) + { + const sal_uInt32 nPolyCount(rFill.count()); + + for(sal_uInt32 a(0); a < nPolyCount; a++) + { + addArea(rFill.getB3DPolygon(a), pViewToEye); + } + } + + RasterConverter3D::RasterConverter3D() + : InterpolatorProvider3D(), + maLineEntries() + {} + + RasterConverter3D::~RasterConverter3D() + {} + + void RasterConverter3D::rasterconvertB3DArea(sal_Int32 nStartLine, sal_Int32 nStopLine) + { + if(maLineEntries.size()) + { + OSL_ENSURE(nStopLine >= nStartLine, "nStopLine is bigger than nStartLine (!)"); + + // sort global entries by Y, X once. After this, the vector + // is seen as frozen. Pointers to it's entries will be used in the following code. + ::std::sort(maLineEntries.begin(), maLineEntries.end()); + + // local parameters + ::std::vector< RasterConversionLineEntry3D >::iterator aCurrentEntry(maLineEntries.begin()); + ::std::vector< RasterConversionLineEntry3D* > aCurrentLine; + ::std::vector< RasterConversionLineEntry3D* > aNextLine; + ::std::vector< RasterConversionLineEntry3D* >::iterator aRasterConversionLineEntry3D; + sal_uInt32 nPairCount(0); + + // get scanlines first LineNumber as start + sal_Int32 nLineNumber(::std::max(aCurrentEntry->getY(), nStartLine)); + + while((aCurrentLine.size() || aCurrentEntry != maLineEntries.end()) && (nLineNumber < nStopLine)) + { + // add all entries which start at current line to current scanline + while(aCurrentEntry != maLineEntries.end()) + { + const sal_Int32 nCurrentLineNumber(aCurrentEntry->getY()); + + if(nCurrentLineNumber > nLineNumber) + { + // line is below current one, done (since array is sorted) + break; + } + else + { + // less or equal. Line is above or at current one. Advance it exactly to + // current line + const sal_uInt32 nStep(nLineNumber - nCurrentLineNumber); + + if(!nStep || aCurrentEntry->decrementRasterConversionLineEntry3D(nStep)) + { + // add when exactly on current line or when incremet to it did not + // completely consume it + if(nStep) + { + aCurrentEntry->incrementRasterConversionLineEntry3D(nStep, *this); + } + + aCurrentLine.push_back(&(*(aCurrentEntry))); + } + } + + aCurrentEntry++; + } + + // sort current scanline using comparator. Only X is used there + // since all entries are already in one processed line. This needs to be done + // everytime since not only new spans may have benn added or old removed, + // but incrementing may also have changed the order + ::std::sort(aCurrentLine.begin(), aCurrentLine.end(), lineComparator()); + + // process current scanline + aRasterConversionLineEntry3D = aCurrentLine.begin(); + aNextLine.clear(); + nPairCount = 0; + + while(aRasterConversionLineEntry3D != aCurrentLine.end()) + { + RasterConversionLineEntry3D& rPrevScanRasterConversionLineEntry3D(**aRasterConversionLineEntry3D++); + + // look for 2nd span + if(aRasterConversionLineEntry3D != aCurrentLine.end()) + { + // work on span from rPrevScanRasterConversionLineEntry3D to aRasterConversionLineEntry3D, fLineNumber is valid + processLineSpan(rPrevScanRasterConversionLineEntry3D, **aRasterConversionLineEntry3D, nLineNumber, nPairCount++); + } + + // increment to next line + if(rPrevScanRasterConversionLineEntry3D.decrementRasterConversionLineEntry3D(1)) + { + rPrevScanRasterConversionLineEntry3D.incrementRasterConversionLineEntry3D(1, *this); + aNextLine.push_back(&rPrevScanRasterConversionLineEntry3D); + } + } + + // copy back next scanline if count has changed + if(aNextLine.size() != aCurrentLine.size()) + { + aCurrentLine = aNextLine; + } + + // increment fLineNumber + nLineNumber++; + } + } + } + + void RasterConverter3D::addEdge(const B3DPolygon& rFill, sal_uInt32 a, sal_uInt32 b, const B3DHomMatrix* pViewToEye) + { + B3DPoint aStart(rFill.getB3DPoint(a)); + B3DPoint aEnd(rFill.getB3DPoint(b)); + sal_Int32 nYStart(fround(aStart.getY())); + sal_Int32 nYEnd(fround(aEnd.getY())); + + if(nYStart != nYEnd) + { + if(nYStart > nYEnd) + { + ::std::swap(aStart, aEnd); + ::std::swap(nYStart, nYEnd); + ::std::swap(a, b); + } + + const sal_uInt32 nYDelta(nYEnd - nYStart); + const double fInvYDelta(1.0 / nYDelta); + maLineEntries.push_back(RasterConversionLineEntry3D( + aStart.getX(), (aEnd.getX() - aStart.getX()) * fInvYDelta, + aStart.getZ(), (aEnd.getZ() - aStart.getZ()) * fInvYDelta, + nYStart, nYDelta)); + + // if extra interpolation data is used, add it to the last created entry + RasterConversionLineEntry3D& rEntry = maLineEntries[maLineEntries.size() - 1]; + + if(rFill.areBColorsUsed()) + { + rEntry.setColorIndex(addColorInterpolator(rFill.getBColor(a), rFill.getBColor(b), fInvYDelta)); + } + + if(rFill.areNormalsUsed()) + { + rEntry.setNormalIndex(addNormalInterpolator(rFill.getNormal(a), rFill.getNormal(b), fInvYDelta)); + } + + if(rFill.areTextureCoordinatesUsed()) + { + if(pViewToEye) + { + const double fEyeA(((*pViewToEye) * aStart).getZ()); + const double fEyeB(((*pViewToEye) * aEnd).getZ()); + + rEntry.setInverseTextureIndex(addInverseTextureInterpolator( + rFill.getTextureCoordinate(a), + rFill.getTextureCoordinate(b), + fEyeA, fEyeB, fInvYDelta)); + } + else + { + rEntry.setTextureIndex(addTextureInterpolator( + rFill.getTextureCoordinate(a), + rFill.getTextureCoordinate(b), + fInvYDelta)); + } + } + } + } + + void RasterConverter3D::rasterconvertB3DEdge(const B3DPolygon& rLine, sal_uInt32 nA, sal_uInt32 nB, sal_Int32 nStartLine, sal_Int32 nStopLine, sal_uInt16 nLineWidth) + { + B3DPoint aStart(rLine.getB3DPoint(nA)); + B3DPoint aEnd(rLine.getB3DPoint(nB)); + const double fZBufferLineAdd(0x00ff); + static bool bForceToPolygon(false); + + if(nLineWidth > 1 || bForceToPolygon) + { + // this is not a hairline anymore, in most cases since it's an oversampled + // hairline to get e.g. AA for Z-Buffering. Create fill geometry. + if(!aStart.equal(aEnd)) + { + reset(); + maLineEntries.clear(); + + B2DVector aVector(aEnd.getX() - aStart.getX(), aEnd.getY() - aStart.getY()); + aVector.normalize(); + const B2DVector aPerpend(getPerpendicular(aVector) * ((static_cast<double>(nLineWidth) + 0.5) * 0.5)); + const double fZStartWithAdd(aStart.getZ() + fZBufferLineAdd); + const double fZEndWithAdd(aEnd.getZ() + fZBufferLineAdd); + + B3DPolygon aPolygon; + aPolygon.append(B3DPoint(aStart.getX() + aPerpend.getX(), aStart.getY() + aPerpend.getY(), fZStartWithAdd)); + aPolygon.append(B3DPoint(aEnd.getX() + aPerpend.getX(), aEnd.getY() + aPerpend.getY(), fZEndWithAdd)); + aPolygon.append(B3DPoint(aEnd.getX() - aPerpend.getX(), aEnd.getY() - aPerpend.getY(), fZEndWithAdd)); + aPolygon.append(B3DPoint(aStart.getX() - aPerpend.getX(), aStart.getY() - aPerpend.getY(), fZStartWithAdd)); + aPolygon.setClosed(true); + + addArea(aPolygon, 0); + } + } + else + { + // it's a hairline. Use direct RasterConversionLineEntry creation to + // rasterconvert lines as similar to areas as possible to avoid Z-Fighting + sal_Int32 nYStart(fround(aStart.getY())); + sal_Int32 nYEnd(fround(aEnd.getY())); + + if(nYStart == nYEnd) + { + // horizontal line, check X + const sal_Int32 nXStart(static_cast<sal_Int32>(aStart.getX())); + const sal_Int32 nXEnd(static_cast<sal_Int32>(aEnd.getX())); + + if(nXStart != nXEnd) + { + reset(); + maLineEntries.clear(); + + // horizontal line, create vertical entries. These will be sorted by + // X anyways, so no need to distinguish the case here + maLineEntries.push_back(RasterConversionLineEntry3D( + aStart.getX(), 0.0, + aStart.getZ() + fZBufferLineAdd, 0.0, + nYStart, 1)); + maLineEntries.push_back(RasterConversionLineEntry3D( + aEnd.getX(), 0.0, + aEnd.getZ() + fZBufferLineAdd, 0.0, + nYStart, 1)); + } + } + else + { + reset(); + maLineEntries.clear(); + + if(nYStart > nYEnd) + { + ::std::swap(aStart, aEnd); + ::std::swap(nYStart, nYEnd); + } + + const sal_uInt32 nYDelta(static_cast<sal_uInt32>(nYEnd - nYStart)); + const double fInvYDelta(1.0 / nYDelta); + + // non-horizontal line, create two parallell entries. These will be sorted by + // X anyways, so no need to distinguish the case here + maLineEntries.push_back(RasterConversionLineEntry3D( + aStart.getX(), (aEnd.getX() - aStart.getX()) * fInvYDelta, + aStart.getZ() + fZBufferLineAdd, (aEnd.getZ() - aStart.getZ()) * fInvYDelta, + nYStart, nYDelta)); + + RasterConversionLineEntry3D& rEntry = maLineEntries[maLineEntries.size() - 1]; + + // need to choose a X-Distance for the 2nd edge which guarantees all pixels + // of the line to be set. This is exactly the X-Increment for one Y-Step. + // Same is true for Z, so in both cases, add one increment to them. To also + // guarantee one pixel per line, add a minimum of one for X. + const double fDistanceX(fabs(rEntry.getX().getInc()) >= 1.0 ? rEntry.getX().getInc() : 1.0); + + maLineEntries.push_back(RasterConversionLineEntry3D( + rEntry.getX().getVal() + fDistanceX, rEntry.getX().getInc(), + rEntry.getZ().getVal() + rEntry.getZ().getInc(), rEntry.getZ().getInc(), + nYStart, nYDelta)); + } + } + + if(maLineEntries.size()) + { + rasterconvertB3DArea(nStartLine, nStopLine); + } + } + + void RasterConverter3D::rasterconvertB3DPolyPolygon(const B3DPolyPolygon& rFill, const B3DHomMatrix* pViewToEye, sal_Int32 nStartLine, sal_Int32 nStopLine) + { + reset(); + maLineEntries.clear(); + addArea(rFill, pViewToEye); + rasterconvertB3DArea(nStartLine, nStopLine); + } + + void RasterConverter3D::rasterconvertB3DPolygon(const B3DPolygon& rLine, sal_Int32 nStartLine, sal_Int32 nStopLine, sal_uInt16 nLineWidth) + { + const sal_uInt32 nPointCount(rLine.count()); + + if(nPointCount) + { + const sal_uInt32 nEdgeCount(rLine.isClosed() ? nPointCount : nPointCount - 1); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + rasterconvertB3DEdge(rLine, a, (a + 1) % nPointCount, nStartLine, nStopLine, nLineWidth); + } + } + } +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/tools/b2dclipstate.cxx b/basegfx/source/tools/b2dclipstate.cxx new file mode 100644 index 000000000000..005dca1aa66a --- /dev/null +++ b/basegfx/source/tools/b2dclipstate.cxx @@ -0,0 +1,662 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: b2dmultirange.cxx,v $ + * $Revision: 1.8 $ + * + * 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/tools/b2dclipstate.hxx> + +#include <basegfx/range/b2drange.hxx> +#include <basegfx/range/b2dpolyrange.hxx> +#include <basegfx/range/b2drangeclipper.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> + +namespace basegfx +{ +namespace tools +{ + struct ImplB2DClipState + { + public: + enum Operation {UNION, INTERSECT, XOR, SUBTRACT}; + + ImplB2DClipState() : + maPendingPolygons(), + maPendingRanges(), + maClipPoly(), + mePendingOps(UNION) + {} + + explicit ImplB2DClipState( const B2DRange& rRange ) : + maPendingPolygons(), + maPendingRanges(), + maClipPoly( + tools::createPolygonFromRect(rRange)), + mePendingOps(UNION) + {} + + explicit ImplB2DClipState( const B2DPolygon& rPoly ) : + maPendingPolygons(), + maPendingRanges(), + maClipPoly(rPoly), + mePendingOps(UNION) + {} + + explicit ImplB2DClipState( const B2DPolyPolygon& rPoly ) : + maPendingPolygons(), + maPendingRanges(), + maClipPoly(rPoly), + mePendingOps(UNION) + {} + + bool isCleared() const + { + return !maClipPoly.count() + && !maPendingPolygons.count() + && !maPendingRanges.count(); + } + + void makeClear() + { + maPendingPolygons.clear(); + maPendingRanges.clear(); + maClipPoly.clear(); + mePendingOps = UNION; + } + + bool isNullClipPoly() const + { + return maClipPoly.count() == 1 + && !maClipPoly.getB2DPolygon(0).count(); + } + + bool isNull() const + { + return !maPendingPolygons.count() + && !maPendingRanges.count() + && isNullClipPoly(); + } + + void makeNull() + { + maPendingPolygons.clear(); + maPendingRanges.clear(); + maClipPoly.clear(); + maClipPoly.append(B2DPolygon()); + mePendingOps = UNION; + } + + bool operator==(const ImplB2DClipState& rRHS) const + { + return maPendingPolygons == rRHS.maPendingPolygons + && maPendingRanges == rRHS.maPendingRanges + && maClipPoly == rRHS.maClipPoly + && mePendingOps == rRHS.mePendingOps; + } + + void addRange(const B2DRange& rRange, Operation eOp) + { + if( rRange.isEmpty() ) + return; + + commitPendingPolygons(); + if( mePendingOps != eOp ) + commitPendingRanges(); + + mePendingOps = eOp; + maPendingRanges.appendElement( + rRange, + ORIENTATION_POSITIVE); + } + + void addPolygon(B2DPolygon aPoly, Operation eOp) + { + commitPendingRanges(); + if( mePendingOps != eOp ) + commitPendingPolygons(); + + mePendingOps = eOp; + maPendingPolygons.append(aPoly); + } + + void addPolyPolygon(B2DPolyPolygon aPoly, Operation eOp) + { + commitPendingRanges(); + if( mePendingOps != eOp ) + commitPendingPolygons(); + + mePendingOps = eOp; + maPendingPolygons.append(aPoly); + } + + void addClipState(const ImplB2DClipState& rOther, Operation eOp) + { + if( rOther.mePendingOps == mePendingOps + && !rOther.maClipPoly.count() + && !rOther.maPendingPolygons.count() ) + { + maPendingRanges.appendPolyRange( rOther.maPendingRanges ); + } + else + { + commitPendingRanges(); + commitPendingPolygons(); + rOther.commitPendingRanges(); + rOther.commitPendingPolygons(); + + maPendingPolygons = rOther.maClipPoly; + mePendingOps = eOp; + } + } + + void unionRange(const B2DRange& rRange) + { + if( isCleared() ) + return; + + addRange(rRange,UNION); + } + + void unionPolygon(const B2DPolygon& rPoly) + { + if( isCleared() ) + return; + + addPolygon(rPoly,UNION); + } + + void unionPolyPolygon(const B2DPolyPolygon& rPolyPoly) + { + if( isCleared() ) + return; + + addPolyPolygon(rPolyPoly,UNION); + } + + void unionClipState(const ImplB2DClipState& rOther) + { + if( isCleared() ) + return; + + addClipState(rOther, UNION); + } + + void intersectRange(const B2DRange& rRange) + { + if( isNull() ) + return; + + addRange(rRange,INTERSECT); + } + + void intersectPolygon(const B2DPolygon& rPoly) + { + if( isNull() ) + return; + + addPolygon(rPoly,INTERSECT); + } + + void intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly) + { + if( isNull() ) + return; + + addPolyPolygon(rPolyPoly,INTERSECT); + } + + void intersectClipState(const ImplB2DClipState& rOther) + { + if( isNull() ) + return; + + addClipState(rOther, INTERSECT); + } + + void subtractRange(const B2DRange& rRange ) + { + if( isNull() ) + return; + + addRange(rRange,SUBTRACT); + } + + void subtractPolygon(const B2DPolygon& rPoly) + { + if( isNull() ) + return; + + addPolygon(rPoly,SUBTRACT); + } + + void subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly) + { + if( isNull() ) + return; + + addPolyPolygon(rPolyPoly,SUBTRACT); + } + + void subtractClipState(const ImplB2DClipState& rOther) + { + if( isNull() ) + return; + + addClipState(rOther, SUBTRACT); + } + + void xorRange(const B2DRange& rRange) + { + addRange(rRange,XOR); + } + + void xorPolygon(const B2DPolygon& rPoly) + { + addPolygon(rPoly,XOR); + } + + void xorPolyPolygon(const B2DPolyPolygon& rPolyPoly) + { + addPolyPolygon(rPolyPoly,XOR); + } + + void xorClipState(const ImplB2DClipState& rOther) + { + addClipState(rOther, XOR); + } + + B2DPolyPolygon getClipPoly() const + { + commitPendingRanges(); + commitPendingPolygons(); + + return maClipPoly; + } + + private: + void commitPendingPolygons() const + { + if( !maPendingPolygons.count() ) + return; + + // assumption: maClipPoly has kept polygons prepared for + // clipping; i.e. no neutral polygons & correct + // orientation + maPendingPolygons = tools::prepareForPolygonOperation(maPendingPolygons); + const bool bIsEmpty=isNullClipPoly(); + const bool bIsCleared=!maClipPoly.count(); + switch(mePendingOps) + { + case UNION: + OSL_ASSERT( !bIsCleared ); + + if( bIsEmpty ) + maClipPoly = maPendingPolygons; + else + maClipPoly = tools::solvePolygonOperationOr( + maClipPoly, + maPendingPolygons); + break; + case INTERSECT: + OSL_ASSERT( !bIsEmpty ); + + if( bIsCleared ) + maClipPoly = maPendingPolygons; + else + maClipPoly = tools::solvePolygonOperationAnd( + maClipPoly, + maPendingPolygons); + break; + case XOR: + if( bIsEmpty ) + maClipPoly = maPendingPolygons; + else if( bIsCleared ) + { + // not representable, strictly speaking, + // using polygons with the common even/odd + // or nonzero winding number fill rule. If + // we'd want to represent it, fill rule + // would need to be "non-negative winding + // number" (and we then would return + // 'holes' here) + + // going for an ugly hack meanwhile + maClipPoly = tools::solvePolygonOperationXor( + B2DPolyPolygon( + tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))), + maPendingPolygons); + } + else + maClipPoly = tools::solvePolygonOperationXor( + maClipPoly, + maPendingPolygons); + break; + case SUBTRACT: + OSL_ASSERT( !bIsEmpty ); + + // first union all pending ones, subtract en bloc then + maPendingPolygons = solveCrossovers(maPendingPolygons); + maPendingPolygons = stripNeutralPolygons(maPendingPolygons); + maPendingPolygons = stripDispensablePolygons(maPendingPolygons, false); + + if( bIsCleared ) + { + // not representable, strictly speaking, + // using polygons with the common even/odd + // or nonzero winding number fill rule. If + // we'd want to represent it, fill rule + // would need to be "non-negative winding + // number" (and we then would return + // 'holes' here) + + // going for an ugly hack meanwhile + maClipPoly = tools::solvePolygonOperationDiff( + B2DPolyPolygon( + tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))), + maPendingPolygons); + } + else + maClipPoly = tools::solvePolygonOperationDiff( + maClipPoly, + maPendingPolygons); + break; + } + + maPendingPolygons.clear(); + mePendingOps = UNION; + } + + void commitPendingRanges() const + { + if( !maPendingRanges.count() ) + return; + + // use the specialized range clipper for the win + B2DPolyPolygon aCollectedRanges; + const bool bIsEmpty=isNullClipPoly(); + const bool bIsCleared=!maClipPoly.count(); + switch(mePendingOps) + { + case UNION: + OSL_ASSERT( !bIsCleared ); + + aCollectedRanges = maPendingRanges.solveCrossovers(); + aCollectedRanges = stripNeutralPolygons(aCollectedRanges); + aCollectedRanges = stripDispensablePolygons(aCollectedRanges, false); + if( bIsEmpty ) + maClipPoly = aCollectedRanges; + else + maClipPoly = tools::solvePolygonOperationOr( + maClipPoly, + aCollectedRanges); + break; + case INTERSECT: + OSL_ASSERT( !bIsEmpty ); + + aCollectedRanges = maPendingRanges.solveCrossovers(); + aCollectedRanges = stripNeutralPolygons(aCollectedRanges); + if( maPendingRanges.count() > 1 ) + aCollectedRanges = stripDispensablePolygons(aCollectedRanges, true); + + if( bIsCleared ) + maClipPoly = aCollectedRanges; + else + maClipPoly = tools::solvePolygonOperationAnd( + maClipPoly, + aCollectedRanges); + break; + case XOR: + aCollectedRanges = maPendingRanges.solveCrossovers(); + aCollectedRanges = stripNeutralPolygons(aCollectedRanges); + aCollectedRanges = correctOrientations(aCollectedRanges); + + if( bIsEmpty ) + maClipPoly = aCollectedRanges; + else if( bIsCleared ) + { + // not representable, strictly speaking, + // using polygons with the common even/odd + // or nonzero winding number fill rule. If + // we'd want to represent it, fill rule + // would need to be "non-negative winding + // number" (and we then would return + // 'holes' here) + + // going for an ugly hack meanwhile + maClipPoly = tools::solvePolygonOperationXor( + B2DPolyPolygon( + tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))), + aCollectedRanges); + } + else + maClipPoly = tools::solvePolygonOperationXor( + maClipPoly, + aCollectedRanges); + break; + case SUBTRACT: + OSL_ASSERT( !bIsEmpty ); + + // first union all pending ranges, subtract en bloc then + aCollectedRanges = maPendingRanges.solveCrossovers(); + aCollectedRanges = stripNeutralPolygons(aCollectedRanges); + aCollectedRanges = stripDispensablePolygons(aCollectedRanges, false); + + if( bIsCleared ) + { + // not representable, strictly speaking, + // using polygons with the common even/odd + // or nonzero winding number fill rule. If + // we'd want to represent it, fill rule + // would need to be "non-negative winding + // number" (and we then would return + // 'holes' here) + + // going for an ugly hack meanwhile + maClipPoly = tools::solvePolygonOperationDiff( + B2DPolyPolygon( + tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))), + aCollectedRanges); + } + else + maClipPoly = tools::solvePolygonOperationDiff( + maClipPoly, + aCollectedRanges); + break; + } + + maPendingRanges.clear(); + mePendingOps = UNION; + } + + mutable B2DPolyPolygon maPendingPolygons; + mutable B2DPolyRange maPendingRanges; + mutable B2DPolyPolygon maClipPoly; + mutable Operation mePendingOps; + }; + + B2DClipState::B2DClipState() : + mpImpl() + {} + + B2DClipState::~B2DClipState() + {} + + B2DClipState::B2DClipState( const B2DClipState& rOrig ) : + mpImpl(rOrig.mpImpl) + {} + + B2DClipState::B2DClipState( const B2DRange& rRange ) : + mpImpl( ImplB2DClipState(rRange) ) + {} + + B2DClipState::B2DClipState( const B2DPolygon& rPoly ) : + mpImpl( ImplB2DClipState(rPoly) ) + {} + + B2DClipState::B2DClipState( const B2DPolyPolygon& rPolyPoly ) : + mpImpl( ImplB2DClipState(rPolyPoly) ) + {} + + B2DClipState& B2DClipState::operator=( const B2DClipState& rRHS ) + { + mpImpl = rRHS.mpImpl; + return *this; + } + + void B2DClipState::makeUnique() + { + mpImpl.make_unique(); + } + + void B2DClipState::makeNull() + { + mpImpl->makeNull(); + } + + bool B2DClipState::isNull() const + { + return mpImpl->isNull(); + } + + void B2DClipState::makeClear() + { + mpImpl->makeClear(); + } + + bool B2DClipState::isCleared() const + { + return mpImpl->isCleared(); + } + + bool B2DClipState::operator==(const B2DClipState& rRHS) const + { + if(mpImpl.same_object(rRHS.mpImpl)) + return true; + + return ((*mpImpl) == (*rRHS.mpImpl)); + } + + bool B2DClipState::operator!=(const B2DClipState& rRHS) const + { + return !(*this == rRHS); + } + + void B2DClipState::unionRange(const B2DRange& rRange) + { + mpImpl->unionRange(rRange); + } + + void B2DClipState::unionPolygon(const B2DPolygon& rPoly) + { + mpImpl->unionPolygon(rPoly); + } + + void B2DClipState::unionPolyPolygon(const B2DPolyPolygon& rPolyPoly) + { + mpImpl->unionPolyPolygon(rPolyPoly); + } + + void B2DClipState::unionClipState(const B2DClipState& rState) + { + mpImpl->unionClipState(*rState.mpImpl); + } + + void B2DClipState::intersectRange(const B2DRange& rRange) + { + mpImpl->intersectRange(rRange); + } + + void B2DClipState::intersectPolygon(const B2DPolygon& rPoly) + { + mpImpl->intersectPolygon(rPoly); + } + + void B2DClipState::intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly) + { + mpImpl->intersectPolyPolygon(rPolyPoly); + } + + void B2DClipState::intersectClipState(const B2DClipState& rState) + { + mpImpl->intersectClipState(*rState.mpImpl); + } + + void B2DClipState::subtractRange(const B2DRange& rRange) + { + mpImpl->subtractRange(rRange); + } + + void B2DClipState::subtractPolygon(const B2DPolygon& rPoly) + { + mpImpl->subtractPolygon(rPoly); + } + + void B2DClipState::subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly) + { + mpImpl->subtractPolyPolygon(rPolyPoly); + } + + void B2DClipState::subtractClipState(const B2DClipState& rState) + { + mpImpl->subtractClipState(*rState.mpImpl); + } + + void B2DClipState::xorRange(const B2DRange& rRange) + { + mpImpl->xorRange(rRange); + } + + void B2DClipState::xorPolygon(const B2DPolygon& rPoly) + { + mpImpl->xorPolygon(rPoly); + } + + void B2DClipState::xorPolyPolygon(const B2DPolyPolygon& rPolyPoly) + { + mpImpl->xorPolyPolygon(rPolyPoly); + } + + void B2DClipState::xorClipState(const B2DClipState& rState) + { + mpImpl->xorClipState(*rState.mpImpl); + } + + B2DPolyPolygon B2DClipState::getClipPoly() const + { + return mpImpl->getClipPoly(); + } + +} // end of namespace tools +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/tools/canvastools.cxx b/basegfx/source/tools/canvastools.cxx new file mode 100755 index 000000000000..2192148461dc --- /dev/null +++ b/basegfx/source/tools/canvastools.cxx @@ -0,0 +1,674 @@ +/************************************************************************* + * + * 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 <com/sun/star/geometry/RealSize2D.hpp> +#include <com/sun/star/geometry/RealPoint2D.hpp> +#include <com/sun/star/geometry/RealRectangle2D.hpp> +#include <com/sun/star/geometry/RealRectangle3D.hpp> +#include <com/sun/star/geometry/RealBezierSegment2D.hpp> +#include <com/sun/star/geometry/AffineMatrix2D.hpp> +#include <com/sun/star/geometry/AffineMatrix3D.hpp> +#include <com/sun/star/geometry/Matrix2D.hpp> +#include <com/sun/star/geometry/IntegerSize2D.hpp> +#include <com/sun/star/geometry/IntegerPoint2D.hpp> +#include <com/sun/star/geometry/IntegerRectangle2D.hpp> +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <basegfx/tools/unopolypolygon.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/range/b3drange.hxx> +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/point/b2ipoint.hxx> +#include <basegfx/range/b2irectangle.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/tools/canvastools.hxx> +#include <limits> + +using namespace ::com::sun::star; + +namespace basegfx +{ + + namespace unotools + { + namespace + { + uno::Sequence< geometry::RealBezierSegment2D > bezierSequenceFromB2DPolygon(const ::basegfx::B2DPolygon& rPoly) + { + const sal_uInt32 nPointCount(rPoly.count()); + uno::Sequence< geometry::RealBezierSegment2D > outputSequence(nPointCount); + geometry::RealBezierSegment2D* pOutput = outputSequence.getArray(); + + // fill sequences and imply clodes polygon on this implementation layer + for(sal_uInt32 a(0); a < nPointCount; a++) + { + const basegfx::B2DPoint aStart(rPoly.getB2DPoint(a)); + const basegfx::B2DPoint aControlA(rPoly.getNextControlPoint(a)); + const basegfx::B2DPoint aControlB(rPoly.getPrevControlPoint((a + 1) % nPointCount)); + + pOutput[a] = geometry::RealBezierSegment2D( + aStart.getX(), aStart.getY(), + aControlA.getX(), aControlA.getY(), + aControlB.getX(), aControlB.getY()); + } + + return outputSequence; + } + + uno::Sequence< geometry::RealPoint2D > pointSequenceFromB2DPolygon( const ::basegfx::B2DPolygon& rPoly ) + { + const sal_uInt32 nNumPoints( rPoly.count() ); + + uno::Sequence< geometry::RealPoint2D > outputSequence( nNumPoints ); + geometry::RealPoint2D* pOutput = outputSequence.getArray(); + + // fill sequence from polygon + sal_uInt32 i; + for( i=0; i<nNumPoints; ++i ) + { + const ::basegfx::B2DPoint aPoint( rPoly.getB2DPoint(i) ); + + pOutput[i] = geometry::RealPoint2D( aPoint.getX(), + aPoint.getY() ); + } + + return outputSequence; + } + } + + //--------------------------------------------------------------------------------------- + + uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > bezierSequenceSequenceFromB2DPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly ) + { + const sal_uInt32 nNumPolies( rPolyPoly.count() ); + sal_uInt32 i; + + uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > outputSequence( nNumPolies ); + uno::Sequence< geometry::RealBezierSegment2D >* pOutput = outputSequence.getArray(); + + for( i=0; i<nNumPolies; ++i ) + { + pOutput[i] = bezierSequenceFromB2DPolygon( rPolyPoly.getB2DPolygon(i) ); + } + + return outputSequence; + } + + //--------------------------------------------------------------------------------------- + + uno::Sequence< uno::Sequence< geometry::RealPoint2D > > pointSequenceSequenceFromB2DPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly ) + { + const sal_uInt32 nNumPolies( rPolyPoly.count() ); + sal_uInt32 i; + + uno::Sequence< uno::Sequence< geometry::RealPoint2D > > outputSequence( nNumPolies ); + uno::Sequence< geometry::RealPoint2D >* pOutput = outputSequence.getArray(); + + for( i=0; i<nNumPolies; ++i ) + { + pOutput[i] = pointSequenceFromB2DPolygon( rPolyPoly.getB2DPolygon(i) ); + } + + return outputSequence; + } + + //--------------------------------------------------------------------------------------- + + uno::Reference< rendering::XPolyPolygon2D > xPolyPolygonFromB2DPolygon( const uno::Reference< rendering::XGraphicDevice >& xGraphicDevice, + const ::basegfx::B2DPolygon& rPoly ) + { + uno::Reference< rendering::XPolyPolygon2D > xRes; + + if( !xGraphicDevice.is() ) + return xRes; + + if( rPoly.areControlPointsUsed() ) + { + uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > outputSequence( 1 ); + outputSequence[0] = bezierSequenceFromB2DPolygon( rPoly ); + + xRes.set( xGraphicDevice->createCompatibleBezierPolyPolygon( outputSequence ), + uno::UNO_QUERY ); + } + else + { + uno::Sequence< uno::Sequence< geometry::RealPoint2D > > outputSequence( 1 ); + outputSequence[0] = pointSequenceFromB2DPolygon( rPoly ); + + xRes.set( xGraphicDevice->createCompatibleLinePolyPolygon( outputSequence ), + uno::UNO_QUERY ); + } + + if( xRes.is() && rPoly.isClosed() ) + xRes->setClosed( 0, sal_True ); + + return xRes; + } + + //--------------------------------------------------------------------------------------- + + uno::Reference< rendering::XPolyPolygon2D > xPolyPolygonFromB2DPolyPolygon( const uno::Reference< rendering::XGraphicDevice >& xGraphicDevice, + const ::basegfx::B2DPolyPolygon& rPolyPoly ) + { + uno::Reference< rendering::XPolyPolygon2D > xRes; + + if( !xGraphicDevice.is() ) + return xRes; + + const sal_uInt32 nNumPolies( rPolyPoly.count() ); + sal_uInt32 i; + + if( rPolyPoly.areControlPointsUsed() ) + { + xRes.set( xGraphicDevice->createCompatibleBezierPolyPolygon( + bezierSequenceSequenceFromB2DPolyPolygon( rPolyPoly ) ), + uno::UNO_QUERY ); + } + else + { + xRes.set( xGraphicDevice->createCompatibleLinePolyPolygon( + pointSequenceSequenceFromB2DPolyPolygon( rPolyPoly ) ), + uno::UNO_QUERY ); + } + + for( i=0; i<nNumPolies; ++i ) + { + xRes->setClosed( i, rPolyPoly.getB2DPolygon(i).isClosed() ); + } + + return xRes; + } + + //--------------------------------------------------------------------------------------- + + ::basegfx::B2DPolygon polygonFromPoint2DSequence( const uno::Sequence< geometry::RealPoint2D >& points ) + { + const sal_Int32 nCurrSize( points.getLength() ); + + ::basegfx::B2DPolygon aPoly; + + for( sal_Int32 nCurrPoint=0; nCurrPoint<nCurrSize; ++nCurrPoint ) + aPoly.append( b2DPointFromRealPoint2D( points[nCurrPoint] ) ); + + return aPoly; + } + + //--------------------------------------------------------------------------------------- + + ::basegfx::B2DPolyPolygon polyPolygonFromPoint2DSequenceSequence( const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points ) + { + ::basegfx::B2DPolyPolygon aRes; + + for( sal_Int32 nCurrPoly=0; nCurrPoly<points.getLength(); ++nCurrPoly ) + { + aRes.append( polygonFromPoint2DSequence( points[nCurrPoly] ) ); + } + + return aRes; + } + + //--------------------------------------------------------------------------------------- + + ::basegfx::B2DPolygon polygonFromBezier2DSequence( const uno::Sequence< geometry::RealBezierSegment2D >& curves ) + { + const sal_Int32 nSize(curves.getLength()); + basegfx::B2DPolygon aRetval; + + if(nSize) + { + // prepare start with providing a start point. Use the first point from + // the sequence for this + const geometry::RealBezierSegment2D& rFirstSegment(curves[0]); // #i79917# first segment, not last + aRetval.append(basegfx::B2DPoint(rFirstSegment.Px, rFirstSegment.Py)); + + for(sal_Int32 a(0); a < nSize; a++) + { + const geometry::RealBezierSegment2D& rCurrSegment(curves[a]); + const geometry::RealBezierSegment2D& rNextSegment(curves[(a + 1) % nSize]); + + // append curved edge with the control points and the next point + aRetval.appendBezierSegment( + basegfx::B2DPoint(rCurrSegment.C1x, rCurrSegment.C1y), + basegfx::B2DPoint(rCurrSegment.C2x, rCurrSegment.C2y), // #i79917# Argh! An x for an y!! + basegfx::B2DPoint(rNextSegment.Px, rNextSegment.Py)); + } + + // rescue the control point and remove the now double-added point + aRetval.setPrevControlPoint(0, aRetval.getPrevControlPoint(aRetval.count() - 1)); + aRetval.remove(aRetval.count() - 1); + } + + return aRetval; + } + + //--------------------------------------------------------------------------------------- + + ::basegfx::B2DPolyPolygon polyPolygonFromBezier2DSequenceSequence( const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& curves ) + { + ::basegfx::B2DPolyPolygon aRes; + + for( sal_Int32 nCurrPoly=0; nCurrPoly<curves.getLength(); ++nCurrPoly ) + { + aRes.append( polygonFromBezier2DSequence( curves[nCurrPoly] ) ); + } + + return aRes; + } + + //--------------------------------------------------------------------------------------- + + ::basegfx::B2DPolyPolygon b2DPolyPolygonFromXPolyPolygon2D( const uno::Reference< rendering::XPolyPolygon2D >& xPoly ) + { + ::basegfx::unotools::UnoPolyPolygon* pPolyImpl = + dynamic_cast< ::basegfx::unotools::UnoPolyPolygon* >( xPoly.get() ); + + if( pPolyImpl ) + { + return pPolyImpl->getPolyPolygon(); + } + else + { + // not a known implementation object - try data source + // interfaces + const sal_Int32 nPolys( xPoly->getNumberOfPolygons() ); + + uno::Reference< rendering::XBezierPolyPolygon2D > xBezierPoly( + xPoly, + uno::UNO_QUERY ); + + if( xBezierPoly.is() ) + { + return ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence( + xBezierPoly->getBezierSegments( 0, + nPolys, + 0, + -1 ) ); + } + else + { + uno::Reference< rendering::XLinePolyPolygon2D > xLinePoly( + xPoly, + uno::UNO_QUERY ); + + // no implementation class and no data provider + // found - contract violation. + if( !xLinePoly.is() ) + { + throw lang::IllegalArgumentException( + ::rtl::OUString::createFromAscii( + "basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(): Invalid input" + "poly-polygon, cannot retrieve vertex data"), + uno::Reference< uno::XInterface >(), + 0 ); + } + + return ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence( + xLinePoly->getPoints( 0, + nPolys, + 0, + -1 )); + } + } + } + + //--------------------------------------------------------------------------------------- + + ::basegfx::B2DHomMatrix& homMatrixFromAffineMatrix( ::basegfx::B2DHomMatrix& output, + const geometry::AffineMatrix2D& input ) + { + // ensure last row is [0,0,1] (and optimized away) + output.identity(); + + output.set(0,0, input.m00); + output.set(0,1, input.m01); + output.set(0,2, input.m02); + output.set(1,0, input.m10); + output.set(1,1, input.m11); + output.set(1,2, input.m12); + + return output; + } + + ::basegfx::B2DHomMatrix homMatrixFromAffineMatrix( const geometry::AffineMatrix2D& input ) + { + ::basegfx::B2DHomMatrix output; + + output.set(0,0, input.m00); + output.set(0,1, input.m01); + output.set(0,2, input.m02); + output.set(1,0, input.m10); + output.set(1,1, input.m11); + output.set(1,2, input.m12); + + return output; + } + + ::basegfx::B3DHomMatrix homMatrixFromAffineMatrix3D( const ::com::sun::star::geometry::AffineMatrix3D& input ) + { + ::basegfx::B3DHomMatrix output; + + output.set(0,0, input.m00); + output.set(0,1, input.m01); + output.set(0,2, input.m02); + output.set(0,3, input.m03); + + output.set(1,0, input.m10); + output.set(1,1, input.m11); + output.set(1,2, input.m12); + output.set(1,3, input.m13); + + output.set(2,0, input.m20); + output.set(2,1, input.m21); + output.set(2,2, input.m22); + output.set(2,3, input.m23); + + return output; + } + + geometry::AffineMatrix2D& affineMatrixFromHomMatrix( geometry::AffineMatrix2D& output, + const ::basegfx::B2DHomMatrix& input) + { + output.m00 = input.get(0,0); + output.m01 = input.get(0,1); + output.m02 = input.get(0,2); + output.m10 = input.get(1,0); + output.m11 = input.get(1,1); + output.m12 = input.get(1,2); + + return output; + } + + geometry::AffineMatrix3D& affineMatrixFromHomMatrix3D( + geometry::AffineMatrix3D& output, + const ::basegfx::B3DHomMatrix& input) + { + output.m00 = input.get(0,0); + output.m01 = input.get(0,1); + output.m02 = input.get(0,2); + output.m03 = input.get(0,3); + + output.m10 = input.get(1,0); + output.m11 = input.get(1,1); + output.m12 = input.get(1,2); + output.m13 = input.get(1,3); + + output.m20 = input.get(2,0); + output.m21 = input.get(2,1); + output.m22 = input.get(2,2); + output.m23 = input.get(2,3); + + return output; + } + + //--------------------------------------------------------------------------------------- + + ::basegfx::B2DHomMatrix& homMatrixFromMatrix( ::basegfx::B2DHomMatrix& output, + const geometry::Matrix2D& input ) + { + // ensure last row is [0,0,1] (and optimized away) + output.identity(); + + output.set(0,0, input.m00); + output.set(0,1, input.m01); + output.set(1,0, input.m10); + output.set(1,1, input.m11); + + return output; + } + + //--------------------------------------------------------------------------------------- + + geometry::RealSize2D size2DFromB2DSize( const ::basegfx::B2DVector& rVec ) + { + return geometry::RealSize2D( rVec.getX(), + rVec.getY() ); + } + + geometry::RealPoint2D point2DFromB2DPoint( const ::basegfx::B2DPoint& rPoint ) + { + return geometry::RealPoint2D( rPoint.getX(), + rPoint.getY() ); + } + + geometry::RealRectangle2D rectangle2DFromB2DRectangle( const ::basegfx::B2DRange& rRect ) + { + return geometry::RealRectangle2D( rRect.getMinX(), + rRect.getMinY(), + rRect.getMaxX(), + rRect.getMaxY() ); + } + + geometry::RealRectangle3D rectangle3DFromB3DRectangle( const ::basegfx::B3DRange& rRect ) + { + return geometry::RealRectangle3D( rRect.getMinX(), + rRect.getMinY(), + rRect.getMinZ(), + rRect.getMaxX(), + rRect.getMaxY(), + rRect.getMaxZ()); + } + + ::basegfx::B2DVector b2DSizeFromRealSize2D( const geometry::RealSize2D& rSize ) + { + return ::basegfx::B2DVector( rSize.Width, + rSize.Height ); + } + + ::basegfx::B2DPoint b2DPointFromRealPoint2D( const geometry::RealPoint2D& rPoint ) + { + return ::basegfx::B2DPoint( rPoint.X, + rPoint.Y ); + } + + ::basegfx::B2DRange b2DRectangleFromRealRectangle2D( const geometry::RealRectangle2D& rRect ) + { + return ::basegfx::B2DRange( rRect.X1, + rRect.Y1, + rRect.X2, + rRect.Y2 ); + } + + ::basegfx::B3DRange b3DRectangleFromRealRectangle3D( const geometry::RealRectangle3D& rRect ) + { + return ::basegfx::B3DRange( rRect.X1, + rRect.Y1, + rRect.Z1, + rRect.X2, + rRect.Y2, + rRect.Z2); + } + + geometry::IntegerSize2D integerSize2DFromB2ISize( const ::basegfx::B2IVector& rSize ) + { + return geometry::IntegerSize2D( rSize.getX(), + rSize.getY() ); + } + + geometry::IntegerPoint2D integerPoint2DFromB2IPoint( const ::basegfx::B2IPoint& rPoint ) + { + return geometry::IntegerPoint2D( rPoint.getX(), + rPoint.getY() ); + } + + geometry::IntegerRectangle2D integerRectangle2DFromB2IRectangle( const ::basegfx::B2IRange& rRectangle ) + { + return geometry::IntegerRectangle2D( rRectangle.getMinX(), rRectangle.getMinY(), + rRectangle.getMaxX(), rRectangle.getMaxY() ); + } + + ::basegfx::B2IVector b2ISizeFromIntegerSize2D( const geometry::IntegerSize2D& rSize ) + { + return ::basegfx::B2IVector( rSize.Width, + rSize.Height ); + } + + ::basegfx::B2IPoint b2IPointFromIntegerPoint2D( const geometry::IntegerPoint2D& rPoint ) + { + return ::basegfx::B2IPoint( rPoint.X, + rPoint.Y ); + } + + ::basegfx::B2IRange b2IRectangleFromIntegerRectangle2D( const geometry::IntegerRectangle2D& rRectangle ) + { + return ::basegfx::B2IRange( rRectangle.X1, rRectangle.Y1, + rRectangle.X2, rRectangle.Y2 ); + } + + awt::Size awtSizeFromB2ISize( const ::basegfx::B2IVector& rVec ) + { + return awt::Size( rVec.getX(), + rVec.getY() ); + } + + awt::Point awtPointFromB2IPoint( const ::basegfx::B2IPoint& rPoint ) + { + return awt::Point( rPoint.getX(), + rPoint.getY() ); + } + + awt::Rectangle awtRectangleFromB2IRectangle( const ::basegfx::B2IRange& rRect ) + { + OSL_ENSURE( rRect.getWidth() < ::std::numeric_limits< sal_Int32 >::max() && + rRect.getWidth() > ::std::numeric_limits< sal_Int32 >::min(), + "awtRectangleFromB2IRectangle(): width overflow" ); + OSL_ENSURE( rRect.getHeight() < ::std::numeric_limits< sal_Int32 >::max() && + rRect.getHeight() > ::std::numeric_limits< sal_Int32 >::min(), + "awtRectangleFromB2IRectangle(): height overflow" ); + + return awt::Rectangle( rRect.getMinX(), + rRect.getMinY(), + static_cast< sal_Int32 >(rRect.getWidth()), + static_cast< sal_Int32 >(rRect.getHeight()) ); + } + + ::basegfx::B2IVector b2ISizeFromAwtSize( const awt::Size& rSize ) + { + return ::basegfx::B2IVector( rSize.Width, + rSize.Height ); + } + + ::basegfx::B2IPoint b2IPointFromAwtPoint( const awt::Point& rPoint ) + { + return ::basegfx::B2IPoint( rPoint.X, + rPoint.Y ); + } + + ::basegfx::B2IRange b2IRectangleFromAwtRectangle( const awt::Rectangle& rRect ) + { + return ::basegfx::B2IRange( rRect.X, + rRect.Y, + rRect.X + rRect.Width, + rRect.Y + rRect.Height ); + } + + ::basegfx::B2IRange b2ISurroundingRangeFromB2DRange( const ::basegfx::B2DRange& rRange ) + { + return ::basegfx::B2IRange( static_cast<sal_Int32>( floor(rRange.getMinX()) ), + static_cast<sal_Int32>( floor(rRange.getMinY()) ), + static_cast<sal_Int32>( ceil(rRange.getMaxX()) ), + static_cast<sal_Int32>( ceil(rRange.getMaxY()) ) ); + } + + ::basegfx::B2DRange b2DSurroundingIntegerRangeFromB2DRange( const ::basegfx::B2DRange& rRange ) + { + return ::basegfx::B2DRange( floor(rRange.getMinX()), + floor(rRange.getMinY()), + ceil(rRange.getMaxX()), + ceil(rRange.getMaxY()) ); + } + + // Geometry comparisons + // =================================================================== + + bool RealSize2DAreEqual( const ::com::sun::star::geometry::RealSize2D& rA, const ::com::sun::star::geometry::RealSize2D& rB ) + { + return (rA.Width == rB.Width && rA.Height == rB.Height); + } + + bool RealPoint2DAreEqual( const ::com::sun::star::geometry::RealPoint2D& rA, const ::com::sun::star::geometry::RealPoint2D& rB ) + { + return (rA.X == rB.X && rA.Y == rB.Y); + } + + bool RealRectangle2DAreEqual( const ::com::sun::star::geometry::RealRectangle2D& rA, const ::com::sun::star::geometry::RealRectangle2D& rB ) + { + return (rA.X1 == rB.X1 && rA.Y1 == rB.Y1 && rA.X2 == rB.X2 && rA.Y2 == rB.Y2); + } + + bool RealRectangle3DAreEqual( const ::com::sun::star::geometry::RealRectangle3D& rA, const ::com::sun::star::geometry::RealRectangle3D& rB ) + { + return (rA.X1 == rB.X1 && rA.Y1 == rB.Y1 && rA.Z1 == rB.Z1 && rA.X2 == rB.X2 && rA.Y2 == rB.Y2 && rA.Z2 == rB.Z2); + } + + bool AffineMatrix2DAreEqual( const ::com::sun::star::geometry::AffineMatrix2D& rA, const ::com::sun::star::geometry::AffineMatrix2D& rB ) + { + return (rA.m00 == rB.m00 && rA.m01 == rB.m01 && rA.m02 == rB.m02 && rA.m10 == rB.m10 && rA.m11 == rB.m11 && rA.m12 == rB.m12); + } + + bool IntegerSize2DAreEqual( const ::com::sun::star::geometry::IntegerSize2D& rA, const ::com::sun::star::geometry::IntegerSize2D& rB ) + { + return (rA.Width == rB.Width && rA.Height == rB.Height); + } + + bool IntegerPoint2DAreEqual( const ::com::sun::star::geometry::IntegerPoint2D& rA, const ::com::sun::star::geometry::IntegerPoint2D& rB ) + { + return (rA.X == rB.X && rA.Y == rB.Y); + } + + bool IntegerRectangle2DAreEqual( const ::com::sun::star::geometry::IntegerRectangle2D& rA, const ::com::sun::star::geometry::IntegerRectangle2D& rB ) + { + return (rA.X1 == rB.X1 && rA.Y1 == rB.Y1 && rA.X2 == rB.X2 && rA.Y2 == rB.Y2); + } + + bool awtSizeAreEqual( const ::com::sun::star::awt::Size& rA, const ::com::sun::star::awt::Size& rB ) + { + return (rA.Width == rB.Width && rA.Height == rB.Height); + } + + bool awtPointAreEqual( const ::com::sun::star::awt::Point& rA, const ::com::sun::star::awt::Point& rB ) + { + return (rA.X == rB.X && rA.Y == rB.Y); + } + + bool awtRectangleAreEqual( const ::com::sun::star::awt::Rectangle& rA, const ::com::sun::star::awt::Rectangle& rB ) + { + return (rA.X == rB.X && rA.Y == rB.Y && rA.Width == rB.Width && rA.Height == rB.Height); + } + } // namespace bgfxtools + +} // namespace canvas diff --git a/basegfx/source/tools/debugplotter.cxx b/basegfx/source/tools/debugplotter.cxx new file mode 100755 index 000000000000..2c68eb44a846 --- /dev/null +++ b/basegfx/source/tools/debugplotter.cxx @@ -0,0 +1,413 @@ +/************************************************************************* + * + * 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/curve/b2dcubicbezier.hxx> + +#include <basegfx/tools/debugplotter.hxx> +#include <boost/bind.hpp> + + +namespace basegfx +{ + namespace + { + void outputHeader( const ::rtl::OString& rTitle, + ::std::ostream* pStm ) + { + // output gnuplot setup + if( pStm ) + { + *pStm << "#!/usr/bin/gnuplot -persist" << ::std::endl << + "#" << ::std::endl << + "# automatically generated by basegfx, don't change!" << ::std::endl << + "#" << ::std::endl << + "# --- " << (const sal_Char*)rTitle << " ---" << ::std::endl << + "#" << ::std::endl << + "set parametric" << ::std::endl << + "# set terminal postscript eps enhanced color " << ::std::endl << + "# set output \"plot.eps\"" << ::std::endl << + // This function plots a cubic bezier curve. P,q,r,s + // are the control point elements of the corresponding + // output coordinate component (i.e. x components for + // the x plot, and y components for the y plot) + "cubicBezier(p,q,r,s,t) = p*(1-t)**3+q*3*(1-t)**2*t+r*3*(1-t)*t**2+s*t**3" << ::std::endl << + // This function plots the derivative of a cubic + // bezier curve. P,q,r,s are the control point + // components of the _original_ curve + "cubicBezDerivative(p,q,r,s,t) = 3*(q-p)*(1-t)**2+6*(r-q)*(1-t)*t+3*(s-r)*t**2" << ::std::endl << + // Plot a line's component of a line between a and b + // (where a and b should be the corresponding + // components of the line's start and end point, + // respectively) + "line(p,q,r) = p*(1-t)+q*t" << ::std::endl << + // Plot a line's x component of a line in implicit + // form ax + by + c = 0 + "implicitLineX(a,b,c,t) = a*-c + t*-b" << ::std::endl << + // Plot a line's y component of a line in implicit + // form ax + by + c = 0 + "implicitLineY(a,b,c,t) = b*-c + t*a" << ::std::endl << + "pointmarkx(c,t) = c-0.03*t" << ::std::endl << // hack for displaying single points in parametric form + "pointmarky(c,t) = c+0.03*t" << ::std::endl << // hack for displaying single points in parametric form + "# end of setup" << ::std::endl; + } + else + { + OSL_TRACE( "#!/usr/bin/gnuplot -persist\n", + "#\n", + "# automatically generated by basegfx, don't change!\n", + "#\n", + "# --- %s ---\n", + "#\n", + "set parametric\n", + // This function plots a cubic bezier curve. P,q,r,s + // are the control point elements of the corresponding + // output coordinate component (i.e. x components for + // the x plot, and y components for the y plot) + "cubicBezier(p,q,r,s,t) = p*(1-t)**3+q*3*(1-t)**2*t+r*3*(1-t)*t**2+s*t**3\n", + // This function plots the derivative of a cubic + // bezier curve. P,q,r,s are the control point + // components of the _original_ curve + "cubicBezDerivative(p,q,r,s,t) = 3*(q-p)*(1-t)**2+6*(r-q)*(1-t)*t+3*(s-r)*t**2\n", + // Plot a line's component of a line between a and b + // (where a and b should be the corresponding + // components of the line's start and end point, + // respectively) + "line(p,q,r) = p*(1-t)+q*t\n", + // Plot a line's x component of a line in implicit + // form ax + by + c = 0 + "implicitLineX(a,b,c,t) = a*-c + t*-b\n", + // Plot a line's y component of a line in implicit + // form ax + by + c = 0 + "implicitLineY(a,b,c,t) = b*-c + t*a\n", + "pointmarkx(c,t) = c-0.03*t\n", // hack for displaying single points in parametric form + "pointmarky(c,t) = c+0.03*t\n", // hack for displaying single points in parametric form + "# end of setup\n", + (const sal_Char*)rTitle ); + } + } + + class Writer + { + public: + Writer( ::std::ostream* pStm ) : + mpStream( pStm ) + { + } + + void outputPoint( const ::std::pair< B2DPoint, ::rtl::OString >& rElem ) + { + if( mpStream ) + *mpStream << " " << rElem.first.getX() << "\t" << rElem.first.getY() << ::std::endl; + else + OSL_TRACE( " %f\t%f\n", rElem.first.getX(), rElem.first.getY() ); + } + + void outputVector( const ::std::pair< B2DVector, ::rtl::OString >& rElem ) + { + if( mpStream ) + *mpStream << " " << rElem.first.getX() << "\t" << rElem.first.getY() << ::std::endl << ::std::endl; + else + OSL_TRACE( " %f\t%f\n\n", rElem.first.getX(), rElem.first.getY() ); + } + + void outputRect( const ::std::pair< B2DRange, ::rtl::OString >& rElem ) + { + const double nX0( rElem.first.getMinX() ); + const double nY0( rElem.first.getMinY() ); + const double nX1( rElem.first.getMaxX() ); + const double nY1( rElem.first.getMaxY() ); + + if( mpStream ) + *mpStream << " " + << nX0 << "\t" << nY0 << "\t" + << nX1 << "\t" << nY0 << "\t" + << nX1 << "\t" << nY1 << "\t" + << nX0 << "\t" << nY1 << "\t" + << nX0 << "\t" << nY0 << ::std::endl << ::std::endl; + + else + OSL_TRACE( " %f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\n\n", + nX0, nY0, + nX1, nY0, + nX1, nY1, + nX0, nY1, + nX0, nY0 ); + } + + private: + ::std::ostream* mpStream; + }; + } + + DebugPlotter::DebugPlotter( const sal_Char* pTitle ) : + maTitle( pTitle ), + maPoints(), + maVectors(), + maRanges(), + maPolygons(), + mpOutputStream(NULL) + { + } + + DebugPlotter::DebugPlotter( const sal_Char* pTitle, + ::std::ostream& rOutputStream ) : + maTitle( pTitle ), + maPoints(), + maVectors(), + maRanges(), + maPolygons(), + mpOutputStream(&rOutputStream) + { + } + + DebugPlotter::~DebugPlotter() + { + const bool bHavePoints( !maPoints.empty() ); + const bool bHaveVectors( !maVectors.empty() ); + const bool bHaveRanges( !maRanges.empty() ); + const bool bHavePolygons( !maPolygons.empty() ); + + if( bHavePoints || + bHaveVectors || + bHaveRanges || + bHavePolygons ) + { + outputHeader( maTitle, mpOutputStream ); + + print( "\n\n# parametric primitive output\n" + "plot [t=0:1] \\\n" ); + + // output plot declarations for used entities + bool bNeedColon( false ); + if( bHavePoints ) + { + print( " '-' using ($1):($2) title \"Points\" with points" ); + bNeedColon = true; + } + if( bHaveVectors ) + { + if( bNeedColon ) + print( ", \\\n" ); + + print( " '-' using ($1):($2) title \"Vectors\" with lp" ); + bNeedColon = true; + } + if( bHaveRanges ) + { + if( bNeedColon ) + print( ", \\\n" ); + + print( " '-' using ($1):($2) title \"Ranges\" with lines" ); + bNeedColon = true; + } + if( bHavePolygons ) + { + const ::std::size_t nSize( maPolygons.size() ); + for( ::std::size_t i=0; i<nSize; ++i ) + { + if( maPolygons.at(i).first.areControlPointsUsed() ) + { + const B2DPolygon& rCurrPoly( maPolygons.at(i).first ); + + const sal_uInt32 nCount( rCurrPoly.count() ); + for( sal_uInt32 k=0; k<nCount; ++k ) + { + if( bNeedColon ) + print( ", \\\n" ); + + const B2DPoint& rP0( rCurrPoly.getB2DPoint(k) ); + const B2DPoint& rP1( rCurrPoly.getNextControlPoint(k) ); + const B2DPoint& rP2( rCurrPoly.getPrevControlPoint((k + 1) % nCount) ); + const B2DPoint& rP3( k+1<nCount ? rCurrPoly.getB2DPoint(k+1) : rCurrPoly.getB2DPoint(k) ); + + if( mpOutputStream ) + *mpOutputStream << " cubicBezier(" + << rP0.getX() << "," + << rP1.getX() << "," + << rP2.getX() << "," + << rP3.getX() << ",t), \\\n cubicBezier(" + << rP0.getY() << "," + << rP1.getY() << "," + << rP2.getY() << "," + << rP3.getY() << ",t)"; + else + OSL_TRACE( " cubicBezier(%f,%f,%f,%f,t), \\\n" + " cubicBezier(%f,%f,%f,%f,t)", + rP0.getX(), + rP1.getX(), + rP2.getX(), + rP3.getX(), + rP0.getY(), + rP1.getY(), + rP2.getY(), + rP3.getY() ); + + bNeedColon = true; + } + } + else + { + if( bNeedColon ) + print( ", \\\n" ); + + if( mpOutputStream ) + *mpOutputStream << " '-' using ($1):($2) title \"Polygon " + << (const sal_Char*)maPolygons.at(i).second << "\" with lp"; + else + OSL_TRACE( " '-' using ($1):($2) title \"Polygon %s\" with lp", + (const sal_Char*)maPolygons.at(i).second ); + + bNeedColon = true; + } + } + } + + if( bHavePoints ) + { + Writer aWriter( mpOutputStream ); + + ::std::for_each( maPoints.begin(), + maPoints.end(), + ::boost::bind( &Writer::outputPoint, + ::boost::ref( aWriter ), + _1 ) ); + print( "e\n" ); + } + + if( bHaveVectors ) + { + Writer aWriter( mpOutputStream ); + + ::std::for_each( maVectors.begin(), + maVectors.end(), + ::boost::bind( &Writer::outputVector, + ::boost::ref( aWriter ), + _1 ) ); + print( "e\n" ); + } + + if( bHaveRanges ) + { + Writer aWriter( mpOutputStream ); + + ::std::for_each( maRanges.begin(), + maRanges.end(), + ::boost::bind( &Writer::outputRect, + ::boost::ref( aWriter ), + _1 ) ); + print( "e\n" ); + } + + if( bHavePolygons ) + { + const ::std::size_t nSize( maPolygons.size() ); + for( ::std::size_t i=0; i<nSize; ++i ) + { + if( !maPolygons.at(i).first.areControlPointsUsed() ) + { + const B2DPolygon& rCurrPoly( maPolygons.at(i).first ); + + const sal_uInt32 nCount( rCurrPoly.count() ); + for( sal_uInt32 k=0; k<nCount; ++k ) + { + const B2DPoint& rP( rCurrPoly.getB2DPoint(k) ); + + if( mpOutputStream ) + *mpOutputStream << " " << rP.getX() << "," << rP.getY(); + else + OSL_TRACE( " %f,%f", + rP.getX(), + rP.getX() ); + } + + print( "\ne\n" ); + } + } + } + } + } + + void DebugPlotter::plot( const B2DPoint& rPoint, + const sal_Char* pTitle ) + { + maPoints.push_back( ::std::make_pair( rPoint, + ::rtl::OString( pTitle ) ) ); + } + + void DebugPlotter::plot( const B2DVector& rVec, + const sal_Char* pTitle ) + { + maVectors.push_back( ::std::make_pair( rVec, + ::rtl::OString( pTitle ) ) ); + } + + void DebugPlotter::plot( const B2DCubicBezier& rBezier, + const sal_Char* pTitle ) + { + B2DPolygon aPoly; + aPoly.append(rBezier.getStartPoint()); + aPoly.appendBezierSegment(rBezier.getControlPointA(), rBezier.getControlPointB(), rBezier.getEndPoint()); + maPolygons.push_back( ::std::make_pair( aPoly, + ::rtl::OString( pTitle ) ) ); + } + + void DebugPlotter::plot( const B2DRange& rRange, + const sal_Char* pTitle ) + { + maRanges.push_back( ::std::make_pair( rRange, + ::rtl::OString( pTitle ) ) ); + } + + void DebugPlotter::plot( const B2DPolygon& rPoly, + const sal_Char* pTitle ) + { + maPolygons.push_back( ::std::make_pair( rPoly, + ::rtl::OString( pTitle ) ) ); + } + + void DebugPlotter::plot( const B2DPolyPolygon& rPoly, + const sal_Char* pTitle ) + { + const ::rtl::OString aTitle( pTitle ); + const sal_uInt32 nCount( rPoly.count() ); + for( sal_uInt32 i=0; i<nCount; ++i ) + maPolygons.push_back( ::std::make_pair( rPoly.getB2DPolygon( i ), + aTitle ) ); + } + + void DebugPlotter::print( const sal_Char* pStr ) + { + if( mpOutputStream ) + *mpOutputStream << pStr; + else + OSL_TRACE( pStr ); + } +} diff --git a/basegfx/source/tools/gradienttools.cxx b/basegfx/source/tools/gradienttools.cxx new file mode 100644 index 000000000000..857b668da68e --- /dev/null +++ b/basegfx/source/tools/gradienttools.cxx @@ -0,0 +1,349 @@ +/************************************************************************* + * + * 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/tools/gradienttools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +namespace basegfx +{ + /** Most of the setup for linear & axial gradient is the same, except + for the border treatment. Factored out here. + */ + static void init1DGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetRange, + sal_uInt32 nSteps, + double fBorder, + double fAngle, + bool bAxial) + { + o_rGradientInfo.maTextureTransform.identity(); + o_rGradientInfo.maBackTextureTransform.identity(); + o_rGradientInfo.mnSteps = nSteps; + + fAngle = -fAngle; + + double fTargetSizeX(rTargetRange.getWidth()); + double fTargetSizeY(rTargetRange.getHeight()); + double fTargetOffsetX(rTargetRange.getMinX()); + double fTargetOffsetY(rTargetRange.getMinY()); + + // add object expansion + if(0.0 != fAngle) + { + const double fAbsCos(fabs(cos(fAngle))); + const double fAbsSin(fabs(sin(fAngle))); + const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin); + const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin); + fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0; + fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0; + fTargetSizeX = fNewX; + fTargetSizeY = fNewY; + } + + const double fSizeWithoutBorder=1.0 - fBorder; + if( bAxial ) + { + o_rGradientInfo.maTextureTransform.scale(1.0, fSizeWithoutBorder * .5); + o_rGradientInfo.maTextureTransform.translate(0.0, 0.5); + } + else + { + if(!fTools::equal(fSizeWithoutBorder, 1.0)) + { + o_rGradientInfo.maTextureTransform.scale(1.0, fSizeWithoutBorder); + o_rGradientInfo.maTextureTransform.translate(0.0, fBorder); + } + } + + o_rGradientInfo.maTextureTransform.scale(fTargetSizeX, fTargetSizeY); + + // add texture rotate after scale to keep perpendicular angles + if(0.0 != fAngle) + { + const B2DPoint aCenter(0.5*fTargetSizeX, + 0.5*fTargetSizeY); + o_rGradientInfo.maTextureTransform *= + basegfx::tools::createRotateAroundPoint(aCenter, fAngle); + } + + // add object translate + o_rGradientInfo.maTextureTransform.translate(fTargetOffsetX, fTargetOffsetY); + + // prepare aspect for texture + o_rGradientInfo.mfAspectRatio = (0.0 != fTargetSizeY) ? fTargetSizeX / fTargetSizeY : 1.0; + + // build transform from u,v to [0.0 .. 1.0]. + o_rGradientInfo.maBackTextureTransform = o_rGradientInfo.maTextureTransform; + o_rGradientInfo.maBackTextureTransform.invert(); + } + + /** Most of the setup for radial & ellipsoidal gradient is the same, + except for the border treatment. Factored out here. + */ + static void initEllipticalGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetRange, + const B2DVector& rOffset, + sal_uInt32 nSteps, + double fBorder, + double fAngle, + bool bCircular) + { + o_rGradientInfo.maTextureTransform.identity(); + o_rGradientInfo.maBackTextureTransform.identity(); + o_rGradientInfo.mnSteps = nSteps; + + fAngle = -fAngle; + + double fTargetSizeX(rTargetRange.getWidth()); + double fTargetSizeY(rTargetRange.getHeight()); + double fTargetOffsetX(rTargetRange.getMinX()); + double fTargetOffsetY(rTargetRange.getMinY()); + + // add object expansion + if( bCircular ) + { + const double fOriginalDiag(sqrt((fTargetSizeX * fTargetSizeX) + (fTargetSizeY * fTargetSizeY))); + fTargetOffsetX -= (fOriginalDiag - fTargetSizeX) / 2.0; + fTargetOffsetY -= (fOriginalDiag - fTargetSizeY) / 2.0; + fTargetSizeX = fOriginalDiag; + fTargetSizeY = fOriginalDiag; + } + else + { + fTargetOffsetX -= (0.4142 / 2.0 ) * fTargetSizeX; + fTargetOffsetY -= (0.4142 / 2.0 ) * fTargetSizeY; + fTargetSizeX = 1.4142 * fTargetSizeX; + fTargetSizeY = 1.4142 * fTargetSizeY; + } + + const double fHalfBorder((1.0 - fBorder) * 0.5); + o_rGradientInfo.maTextureTransform.scale(fHalfBorder, fHalfBorder); + + o_rGradientInfo.maTextureTransform.translate(0.5, 0.5); + o_rGradientInfo.maTextureTransform.scale(fTargetSizeX, fTargetSizeY); + + // add texture rotate after scale to keep perpendicular angles + if( !bCircular && 0.0 != fAngle) + { + const B2DPoint aCenter(0.5*fTargetSizeX, + 0.5*fTargetSizeY); + o_rGradientInfo.maTextureTransform *= + basegfx::tools::createRotateAroundPoint(aCenter, fAngle); + } + + // add defined offsets after rotation + if(0.5 != rOffset.getX() || 0.5 != rOffset.getY()) + { + // use original target size + fTargetOffsetX += (rOffset.getX() - 0.5) * rTargetRange.getWidth(); + fTargetOffsetY += (rOffset.getY() - 0.5) * rTargetRange.getHeight(); + } + + // add object translate + o_rGradientInfo.maTextureTransform.translate(fTargetOffsetX, fTargetOffsetY); + + // prepare aspect for texture + o_rGradientInfo.mfAspectRatio = (0.0 != fTargetSizeY) ? fTargetSizeX / fTargetSizeY : 1.0; + + // build transform from u,v to [0.0 .. 1.0]. + o_rGradientInfo.maBackTextureTransform = o_rGradientInfo.maTextureTransform; + o_rGradientInfo.maBackTextureTransform.invert(); + } + + /** Setup for rect & square gradient is exactly the same. Factored out + here. + */ + static void initRectGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetRange, + const B2DVector& rOffset, + sal_uInt32 nSteps, + double fBorder, + double fAngle) + { + o_rGradientInfo.maTextureTransform.identity(); + o_rGradientInfo.maBackTextureTransform.identity(); + o_rGradientInfo.mnSteps = nSteps; + + fAngle = -fAngle; + + double fTargetSizeX(rTargetRange.getWidth()); + double fTargetSizeY(rTargetRange.getHeight()); + double fTargetOffsetX(rTargetRange.getMinX()); + double fTargetOffsetY(rTargetRange.getMinY()); + + // add object expansion + if(0.0 != fAngle) + { + const double fAbsCos(fabs(cos(fAngle))); + const double fAbsSin(fabs(sin(fAngle))); + const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin); + const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin); + fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0; + fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0; + fTargetSizeX = fNewX; + fTargetSizeY = fNewY; + } + + const double fHalfBorder((1.0 - fBorder) * 0.5); + o_rGradientInfo.maTextureTransform.scale(fHalfBorder, fHalfBorder); + + o_rGradientInfo.maTextureTransform.translate(0.5, 0.5); + o_rGradientInfo.maTextureTransform.scale(fTargetSizeX, fTargetSizeY); + + // add texture rotate after scale to keep perpendicular angles + if(0.0 != fAngle) + { + const B2DPoint aCenter(0.5*fTargetSizeX, + 0.5*fTargetSizeY); + o_rGradientInfo.maTextureTransform *= + basegfx::tools::createRotateAroundPoint(aCenter, fAngle); + } + + // add defined offsets after rotation + if(0.5 != rOffset.getX() || 0.5 != rOffset.getY()) + { + // use scaled target size + fTargetOffsetX += (rOffset.getX() - 0.5) * fTargetSizeX; + fTargetOffsetY += (rOffset.getY() - 0.5) * fTargetSizeY; + } + + // add object translate + o_rGradientInfo.maTextureTransform.translate(fTargetOffsetX, fTargetOffsetY); + + // prepare aspect for texture + o_rGradientInfo.mfAspectRatio = (0.0 != fTargetSizeY) ? fTargetSizeX / fTargetSizeY : 1.0; + + // build transform from u,v to [0.0 .. 1.0]. As base, use inverse texture transform + o_rGradientInfo.maBackTextureTransform = o_rGradientInfo.maTextureTransform; + o_rGradientInfo.maBackTextureTransform.invert(); + } + + namespace tools + { + ODFGradientInfo& createLinearODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + sal_uInt32 nSteps, + double fBorder, + double fAngle) + { + init1DGradientInfo(o_rGradientInfo, + rTargetArea, + nSteps, + fBorder, + fAngle, + false); + return o_rGradientInfo; + } + + ODFGradientInfo& createAxialODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + sal_uInt32 nSteps, + double fBorder, + double fAngle) + { + init1DGradientInfo(o_rGradientInfo, + rTargetArea, + nSteps, + fBorder, + fAngle, + true); + return o_rGradientInfo; + } + + ODFGradientInfo& createRadialODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + const B2DVector& rOffset, + sal_uInt32 nSteps, + double fBorder) + { + initEllipticalGradientInfo(o_rGradientInfo, + rTargetArea, + rOffset, + nSteps, + fBorder, + 0.0, + true); + return o_rGradientInfo; + } + + ODFGradientInfo& createEllipticalODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + const B2DVector& rOffset, + sal_uInt32 nSteps, + double fBorder, + double fAngle) + { + initEllipticalGradientInfo(o_rGradientInfo, + rTargetArea, + rOffset, + nSteps, + fBorder, + fAngle, + false); + return o_rGradientInfo; + } + + ODFGradientInfo& createSquareODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + const B2DVector& rOffset, + sal_uInt32 nSteps, + double fBorder, + double fAngle) + { + initRectGradientInfo(o_rGradientInfo, + rTargetArea, + rOffset, + nSteps, + fBorder, + fAngle); + return o_rGradientInfo; + } + + ODFGradientInfo& createRectangularODFGradientInfo(ODFGradientInfo& o_rGradientInfo, + const B2DRange& rTargetArea, + const B2DVector& rOffset, + sal_uInt32 nSteps, + double fBorder, + double fAngle) + { + initRectGradientInfo(o_rGradientInfo, + rTargetArea, + rOffset, + nSteps, + fBorder, + fAngle); + return o_rGradientInfo; + } + + } // namespace tools + +} // namespace basegfx diff --git a/basegfx/source/tools/keystoplerp.cxx b/basegfx/source/tools/keystoplerp.cxx new file mode 100644 index 000000000000..883bfec6bc96 --- /dev/null +++ b/basegfx/source/tools/keystoplerp.cxx @@ -0,0 +1,107 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: canvastools.hxx,v $ + * $Revision: 1.10 $ + * + * 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/tools/keystoplerp.hxx" +#include <com/sun/star/uno/Sequence.hxx> + +#include <algorithm> + +static void validateInput(const std::vector<double>& rKeyStops) +{ + (void)rKeyStops; +#ifdef DBG_UTIL + OSL_ENSURE( rKeyStops.size() > 1, + "KeyStopLerp::KeyStopLerp(): key stop vector must have two entries or more" ); + + // rKeyStops must be sorted in ascending order + for( ::std::size_t i=1, len=rKeyStops.size(); i<len; ++i ) + { + if( rKeyStops[i-1] > rKeyStops[i] ) + OSL_ENSURE( false, + "KeyStopLerp::KeyStopLerp(): time vector is not sorted in ascending order!" ); + } +#endif +} + +namespace basegfx +{ + namespace tools + { + KeyStopLerp::KeyStopLerp( const std::vector<double>& rKeyStops ) : + maKeyStops(rKeyStops), + mnLastIndex(0) + { + validateInput(maKeyStops); + } + + KeyStopLerp::KeyStopLerp( const ::com::sun::star::uno::Sequence<double>& rKeyStops ) : + maKeyStops(rKeyStops.getLength()), + mnLastIndex(0) + { + std::copy( rKeyStops.getConstArray(), + rKeyStops.getConstArray()+rKeyStops.getLength(), + maKeyStops.begin() ); + validateInput(maKeyStops); + } + + KeyStopLerp::ResultType KeyStopLerp::lerp(double fAlpha) const + { + // cached value still okay? + if( maKeyStops.at(mnLastIndex) < fAlpha || + maKeyStops.at(mnLastIndex+1) >= fAlpha ) + { + // nope, find new index + mnLastIndex = std::min<std::ptrdiff_t>( + maKeyStops.size()-2, + // range is ensured by max below + std::max<std::ptrdiff_t>( + 0, + std::distance( maKeyStops.begin(), + std::lower_bound( maKeyStops.begin(), + maKeyStops.end(), + fAlpha )) - 1 )); + } + + // lerp between stop and stop+1 + const double fRawLerp= + (fAlpha-maKeyStops.at(mnLastIndex)) / + (maKeyStops.at(mnLastIndex+1) - maKeyStops.at(mnLastIndex)); + + // clamp to permissible range (input fAlpha might be + // everything) + return ResultType( + mnLastIndex, + clamp(fRawLerp,0.0,1.0)); + } + } +} diff --git a/basegfx/source/tools/liangbarsky.cxx b/basegfx/source/tools/liangbarsky.cxx new file mode 100644 index 000000000000..f07cea4a34d9 --- /dev/null +++ b/basegfx/source/tools/liangbarsky.cxx @@ -0,0 +1,132 @@ +/************************************************************************* + * + * 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/tools/tools.hxx" +#include "basegfx/numeric/ftools.hxx" +#include "basegfx/range/b2drange.hxx" + + +namespace basegfx +{ + namespace tools + { + namespace + { + // see Foley/vanDam, pp. 122 for the Liang-Barsky line + // clipping algorithm + inline bool liangBarskyClipT( double nDenom, + double nNumerator, + double& io_rTE, + double& io_rTL ) + { + double t; + if( nDenom > 0 ) + { + t = nNumerator / nDenom; + if( t > io_rTL ) + return false; + else if( t > io_rTE ) + io_rTE = t; + } + else if( nDenom < 0 ) + { + t = nNumerator / nDenom; + if( t < io_rTE ) + return false; + else + io_rTL = t; + } + else if( nNumerator > 0 ) + { + return false; + } + + return true; + } + } + + // see Foley/vanDam, pp. 122 for the Liang-Barsky line + // clipping algorithm + bool liangBarskyClip2D( ::basegfx::B2DPoint& io_rStart, + ::basegfx::B2DPoint& io_rEnd, + const ::basegfx::B2DRange& rClipRect ) + { + const double nDX( io_rEnd.getX() - io_rStart.getX() ); + const double nDY( io_rEnd.getY() - io_rStart.getY() ); + + if( ::basegfx::fTools::equalZero( nDX ) && + ::basegfx::fTools::equalZero( nDY ) ) + { + return rClipRect.isInside( io_rStart ); + } + else + { + double nTE( 0.0 ); + double nTL( 1.0 ); + if( liangBarskyClipT(nDX, rClipRect.getMinX() - io_rStart.getX(), + nTE, nTL ) ) // inside wrt. left edge + { + if( liangBarskyClipT(-nDX, io_rStart.getX() - rClipRect.getMaxX(), + nTE, nTL ) ) // inside wrt. right edge + { + if( liangBarskyClipT(nDY, rClipRect.getMinY() - io_rStart.getY(), + nTE, nTL ) ) // inside wrt. bottom edge + { + if( liangBarskyClipT(-nDY, io_rStart.getY() - rClipRect.getMaxY(), + nTE, nTL ) ) // inside wrt. top edge + { + // compute actual intersection points, + // if nTL has changed + if( nTL < 1.0 ) + { + io_rEnd.setX( io_rStart.getX() + nTL*nDX ); + io_rEnd.setY( io_rStart.getY() + nTL*nDY ); + } + + // compute actual intersection points, + // if nTE has changed + if( nTE > 0.0 ) + { + io_rStart.setX( io_rStart.getX() + nTE*nDX ); + io_rStart.setY( io_rStart.getY() + nTE*nDY ); + } + + // line is (at least partially) visible + return true; + } + } + } + } + } + + return false; + } + } +} diff --git a/basegfx/source/tools/makefile.mk b/basegfx/source/tools/makefile.mk new file mode 100755 index 000000000000..0a0977f7305d --- /dev/null +++ b/basegfx/source/tools/makefile.mk @@ -0,0 +1,51 @@ +#************************************************************************* +# +# 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=tools + +ENABLE_EXCEPTIONS=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= $(SLO)$/b2dclipstate.obj \ + $(SLO)$/canvastools.obj \ + $(SLO)$/gradienttools.obj \ + $(SLO)$/debugplotter.obj \ + $(SLO)$/keystoplerp.obj \ + $(SLO)$/liangbarsky.obj \ + $(SLO)$/tools.obj \ + $(SLO)$/unopolypolygon.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/tools/tools.cxx b/basegfx/source/tools/tools.cxx new file mode 100644 index 000000000000..d375f144169d --- /dev/null +++ b/basegfx/source/tools/tools.cxx @@ -0,0 +1,124 @@ +/************************************************************************* + * + * 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/tools/tools.hxx" +#include "basegfx/range/b2drange.hxx" + +#include <algorithm> + + +namespace basegfx +{ + namespace tools + { + namespace + { + inline double distance( const double& nX, + const double& nY, + const ::basegfx::B2DVector& rNormal, + const double& nC ) + { + return nX*rNormal.getX() + nY*rNormal.getY() - nC; + } + + void moveLineOutsideRect( ::basegfx::B2DPoint& io_rStart, + ::basegfx::B2DPoint& io_rEnd, + const ::basegfx::B2DVector& rMoveDirection, + const ::basegfx::B2DRange& rFitTarget ) + { + // calc c for normal line form equation n x - c = 0 + const double nC( rMoveDirection.scalar( io_rStart ) ); + + // calc maximum orthogonal distance for all four bound + // rect corners to the line + const double nMaxDistance( ::std::max( + 0.0, + ::std::max( + distance(rFitTarget.getMinX(), + rFitTarget.getMinY(), + rMoveDirection, + nC), + ::std::max( + distance(rFitTarget.getMinX(), + rFitTarget.getMaxY(), + rMoveDirection, + nC), + ::std::max( + distance(rFitTarget.getMaxX(), + rFitTarget.getMinY(), + rMoveDirection, + nC), + distance(rFitTarget.getMaxX(), + rFitTarget.getMaxY(), + rMoveDirection, + nC) ) ) ) ) ); + + // now move line points, such that the bound rect + // points are all either 'on' or on the negative side + // of the half-plane + io_rStart += nMaxDistance*rMoveDirection; + io_rEnd += nMaxDistance*rMoveDirection; + } + } + + void infiniteLineFromParallelogram( ::basegfx::B2DPoint& io_rLeftTop, + ::basegfx::B2DPoint& io_rLeftBottom, + ::basegfx::B2DPoint& io_rRightTop, + ::basegfx::B2DPoint& io_rRightBottom, + const ::basegfx::B2DRange& rFitTarget ) + { + // For the top and bottom border line of the + // parallelogram, we determine the distance to all four + // corner points of the bound rect (tl, tr, bl, br). When + // using the unit normal form for lines (n x - c = 0), and + // choosing n to point 'outwards' the parallelogram, then + // all bound rect corner points having positive distance + // to the line lie outside the extended gradient rect, and + // thus, the corresponding border line must be moved the + // maximum distance outwards. + + // don't use the top and bottom border line direction, and + // calculate the normal from them. Instead, use the + // vertical lines (lt - lb or rt - rb), as they more + // faithfully represent the direction of the + // to-be-generated infinite line + ::basegfx::B2DVector aDirectionVertical( io_rLeftTop - io_rLeftBottom ); + aDirectionVertical.normalize(); + + const ::basegfx::B2DVector aNormalTop( aDirectionVertical ); + const ::basegfx::B2DVector aNormalBottom( -aDirectionVertical ); + + // now extend parallelogram, such that the bound rect + // point are included + moveLineOutsideRect( io_rLeftTop, io_rRightTop, aNormalTop, rFitTarget ); + moveLineOutsideRect( io_rLeftBottom, io_rRightBottom, aNormalBottom, rFitTarget ); + } + } +} diff --git a/basegfx/source/tools/unopolypolygon.cxx b/basegfx/source/tools/unopolypolygon.cxx new file mode 100755 index 000000000000..871fed7c7d18 --- /dev/null +++ b/basegfx/source/tools/unopolypolygon.cxx @@ -0,0 +1,486 @@ +/************************************************************************* + * + * 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 <com/sun/star/geometry/AffineMatrix2D.hpp> +#include <com/sun/star/rendering/RenderState.hpp> +#include <com/sun/star/rendering/ViewState.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/tools/canvastools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/tools/unopolypolygon.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +using namespace ::com::sun::star; + +namespace basegfx +{ +namespace unotools +{ + UnoPolyPolygon::UnoPolyPolygon( const B2DPolyPolygon& rPolyPoly ) : + UnoPolyPolygonBase( m_aMutex ), + maPolyPoly( rPolyPoly ), + meFillRule( rendering::FillRule_EVEN_ODD ) + { + // or else races will haunt us. + maPolyPoly.makeUnique(); + } + + void SAL_CALL UnoPolyPolygon::addPolyPolygon( + const geometry::RealPoint2D& position, + const uno::Reference< rendering::XPolyPolygon2D >& polyPolygon ) throw (lang::IllegalArgumentException,uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + modifying(); + + // TODO(F1): Correctly fulfill the UNO API + // specification. This will probably result in a vector of + // poly-polygons to be stored in this object. + + const sal_Int32 nPolys( polyPolygon->getNumberOfPolygons() ); + + if( !polyPolygon.is() || !nPolys ) + { + // invalid or empty polygon - nothing to do. + return; + } + + B2DPolyPolygon aSrcPoly; + const UnoPolyPolygon* pSrc( dynamic_cast< UnoPolyPolygon* >(polyPolygon.get()) ); + + // try to extract polygon data from interface. First, + // check whether it's the same implementation object, + // which we can tunnel then. + if( pSrc ) + { + aSrcPoly = pSrc->getPolyPolygon(); + } + else + { + // not a known implementation object - try data source + // interfaces + uno::Reference< rendering::XBezierPolyPolygon2D > xBezierPoly( + polyPolygon, + uno::UNO_QUERY ); + + if( xBezierPoly.is() ) + { + aSrcPoly = unotools::polyPolygonFromBezier2DSequenceSequence( + xBezierPoly->getBezierSegments( 0, + nPolys, + 0, + -1 ) ); + } + else + { + uno::Reference< rendering::XLinePolyPolygon2D > xLinePoly( + polyPolygon, + uno::UNO_QUERY ); + + // no implementation class and no data provider + // found - contract violation. + if( !xLinePoly.is() ) + throw lang::IllegalArgumentException( + ::rtl::OUString( + RTL_CONSTASCII_USTRINGPARAM( + "UnoPolyPolygon::addPolyPolygon(): Invalid input " + "poly-polygon, cannot retrieve vertex data")), + static_cast<cppu::OWeakObject*>(this), 1); + + aSrcPoly = unotools::polyPolygonFromPoint2DSequenceSequence( + xLinePoly->getPoints( 0, + nPolys, + 0, + -1 ) ); + } + } + + const B2DRange aBounds( tools::getRange( aSrcPoly ) ); + const B2DVector aOffset( unotools::b2DPointFromRealPoint2D( position ) - + aBounds.getMinimum() ); + + if( !aOffset.equalZero() ) + { + const B2DHomMatrix aTranslate(tools::createTranslateB2DHomMatrix(aOffset)); + aSrcPoly.transform( aTranslate ); + } + + maPolyPoly.append( aSrcPoly ); + } + + sal_Int32 SAL_CALL UnoPolyPolygon::getNumberOfPolygons() throw (uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + return maPolyPoly.count(); + } + + sal_Int32 SAL_CALL UnoPolyPolygon::getNumberOfPolygonPoints( + sal_Int32 polygon ) throw (lang::IndexOutOfBoundsException,uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + checkIndex( polygon ); + + return maPolyPoly.getB2DPolygon(polygon).count(); + } + + rendering::FillRule SAL_CALL UnoPolyPolygon::getFillRule() throw (uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + return meFillRule; + } + + void SAL_CALL UnoPolyPolygon::setFillRule( + rendering::FillRule fillRule ) throw (uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + modifying(); + + meFillRule = fillRule; + } + + sal_Bool SAL_CALL UnoPolyPolygon::isClosed( + sal_Int32 index ) throw (lang::IndexOutOfBoundsException,uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + checkIndex( index ); + + return maPolyPoly.getB2DPolygon(index).isClosed(); + } + + void SAL_CALL UnoPolyPolygon::setClosed( + sal_Int32 index, + sal_Bool closedState ) throw (lang::IndexOutOfBoundsException,uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + modifying(); + + if( index == -1L ) + { + // set all + maPolyPoly.setClosed( closedState ); + } + else + { + checkIndex( index ); + + // fetch referenced polygon, change state + B2DPolygon aTmp( maPolyPoly.getB2DPolygon(index) ); + aTmp.setClosed( closedState ); + + // set back to container + maPolyPoly.setB2DPolygon( index, aTmp ); + } + } + + uno::Sequence< uno::Sequence< geometry::RealPoint2D > > SAL_CALL UnoPolyPolygon::getPoints( + sal_Int32 nPolygonIndex, + sal_Int32 nNumberOfPolygons, + sal_Int32 nPointIndex, + sal_Int32 nNumberOfPoints ) throw (lang::IndexOutOfBoundsException,uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + + return unotools::pointSequenceSequenceFromB2DPolyPolygon( + getSubsetPolyPolygon( nPolygonIndex, + nNumberOfPolygons, + nPointIndex, + nNumberOfPoints ) ); + } + + void SAL_CALL UnoPolyPolygon::setPoints( + const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points, + sal_Int32 nPolygonIndex ) throw (lang::IndexOutOfBoundsException,uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + modifying(); + + const B2DPolyPolygon& rNewPolyPoly( + unotools::polyPolygonFromPoint2DSequenceSequence( points ) ); + + if( nPolygonIndex == -1 ) + { + maPolyPoly = rNewPolyPoly; + } + else + { + checkIndex( nPolygonIndex ); + + maPolyPoly.insert( nPolygonIndex, rNewPolyPoly ); + } + } + + geometry::RealPoint2D SAL_CALL UnoPolyPolygon::getPoint( + sal_Int32 nPolygonIndex, + sal_Int32 nPointIndex ) throw (lang::IndexOutOfBoundsException,uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + checkIndex( nPolygonIndex ); + + const B2DPolygon& rPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) ); + + if( nPointIndex < 0 || nPointIndex >= static_cast<sal_Int32>(rPoly.count()) ) + throw lang::IndexOutOfBoundsException(); + + return unotools::point2DFromB2DPoint( rPoly.getB2DPoint( nPointIndex ) ); + } + + void SAL_CALL UnoPolyPolygon::setPoint( + const geometry::RealPoint2D& point, + sal_Int32 nPolygonIndex, + sal_Int32 nPointIndex ) throw (lang::IndexOutOfBoundsException,uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + checkIndex( nPolygonIndex ); + modifying(); + + B2DPolygon aPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) ); + + if( nPointIndex < 0 || nPointIndex >= static_cast<sal_Int32>(aPoly.count()) ) + throw lang::IndexOutOfBoundsException(); + + aPoly.setB2DPoint( nPointIndex, + unotools::b2DPointFromRealPoint2D( point ) ); + maPolyPoly.setB2DPolygon( nPolygonIndex, aPoly ); + } + + uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > SAL_CALL UnoPolyPolygon::getBezierSegments( + sal_Int32 nPolygonIndex, + sal_Int32 nNumberOfPolygons, + sal_Int32 nPointIndex, + sal_Int32 nNumberOfPoints ) throw (lang::IndexOutOfBoundsException, uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + return unotools::bezierSequenceSequenceFromB2DPolyPolygon( + getSubsetPolyPolygon( nPolygonIndex, + nNumberOfPolygons, + nPointIndex, + nNumberOfPoints ) ); + } + + void SAL_CALL UnoPolyPolygon::setBezierSegments( + const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& points, + sal_Int32 nPolygonIndex ) throw (lang::IndexOutOfBoundsException, + uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + modifying(); + const B2DPolyPolygon& rNewPolyPoly( + unotools::polyPolygonFromBezier2DSequenceSequence( points ) ); + + if( nPolygonIndex == -1 ) + { + maPolyPoly = rNewPolyPoly; + } + else + { + checkIndex( nPolygonIndex ); + + maPolyPoly.insert( nPolygonIndex, rNewPolyPoly ); + } + } + + geometry::RealBezierSegment2D SAL_CALL UnoPolyPolygon::getBezierSegment( sal_Int32 nPolygonIndex, + sal_Int32 nPointIndex ) throw (lang::IndexOutOfBoundsException, + uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + checkIndex( nPolygonIndex ); + + const B2DPolygon& rPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) ); + const sal_uInt32 nPointCount(rPoly.count()); + + if( nPointIndex < 0 || nPointIndex >= static_cast<sal_Int32>(nPointCount) ) + throw lang::IndexOutOfBoundsException(); + + const B2DPoint& rPt( rPoly.getB2DPoint( nPointIndex ) ); + const B2DPoint& rCtrl0( rPoly.getNextControlPoint(nPointIndex) ); + const B2DPoint& rCtrl1( rPoly.getPrevControlPoint((nPointIndex + 1) % nPointCount) ); + + return geometry::RealBezierSegment2D( rPt.getX(), + rPt.getY(), + rCtrl0.getX(), + rCtrl0.getY(), + rCtrl1.getX(), + rCtrl1.getY() ); + } + + void SAL_CALL UnoPolyPolygon::setBezierSegment( const geometry::RealBezierSegment2D& segment, + sal_Int32 nPolygonIndex, + sal_Int32 nPointIndex ) throw (lang::IndexOutOfBoundsException, + uno::RuntimeException) + { + osl::MutexGuard const guard( m_aMutex ); + checkIndex( nPolygonIndex ); + modifying(); + + B2DPolygon aPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) ); + const sal_uInt32 nPointCount(aPoly.count()); + + if( nPointIndex < 0 || nPointIndex >= static_cast<sal_Int32>(nPointCount) ) + throw lang::IndexOutOfBoundsException(); + + aPoly.setB2DPoint( nPointIndex, + B2DPoint( segment.Px, + segment.Py ) ); + aPoly.setNextControlPoint(nPointIndex, + B2DPoint(segment.C1x, segment.C1y)); + aPoly.setPrevControlPoint((nPointIndex + 1) % nPointCount, + B2DPoint(segment.C2x, segment.C2y)); + + maPolyPoly.setB2DPolygon( nPolygonIndex, aPoly ); + } + + B2DPolyPolygon UnoPolyPolygon::getSubsetPolyPolygon( + sal_Int32 nPolygonIndex, + sal_Int32 nNumberOfPolygons, + sal_Int32 nPointIndex, + sal_Int32 nNumberOfPoints ) const + { + osl::MutexGuard const guard( m_aMutex ); + checkIndex( nPolygonIndex ); + + const sal_Int32 nPolyCount( maPolyPoly.count() ); + + // check for "full polygon" case + if( !nPolygonIndex && + !nPointIndex && + nNumberOfPolygons == nPolyCount && + nNumberOfPoints == -1 ) + { + return maPolyPoly; + } + + B2DPolyPolygon aSubsetPoly; + + // create temporary polygon (as an extract from maPoly, + // which contains the requested subset) + for( sal_Int32 i=nPolygonIndex; i<nNumberOfPolygons; ++i ) + { + checkIndex(i); + + const B2DPolygon& rCurrPoly( maPolyPoly.getB2DPolygon(i) ); + + sal_Int32 nFirstPoint(0); + sal_Int32 nLastPoint(nPolyCount-1); + + if( nPointIndex && i==nPolygonIndex ) + { + // very first polygon - respect nPointIndex, if + // not zero + + // empty polygon - impossible to specify _any_ + // legal value except 0 here! + if( !nPolyCount && nPointIndex ) + throw lang::IndexOutOfBoundsException(); + + nFirstPoint = nPointIndex; + } + + if( i==nNumberOfPolygons-1 && nNumberOfPoints != -1 ) + { + // very last polygon - respect nNumberOfPoints + + // empty polygon - impossible to specify _any_ + // legal value except -1 here! + if( !nPolyCount ) + throw lang::IndexOutOfBoundsException(); + + nLastPoint = nFirstPoint+nNumberOfPoints; + } + + if( !nPolyCount ) + { + // empty polygon - index checks already performed + // above, now simply append empty polygon + aSubsetPoly.append( rCurrPoly ); + } + else + { + if( nFirstPoint < 0 || nFirstPoint >= nPolyCount ) + throw lang::IndexOutOfBoundsException(); + + if( nLastPoint < 0 || nLastPoint >= nPolyCount ) + throw lang::IndexOutOfBoundsException(); + + B2DPolygon aTmp; + for( sal_Int32 j=nFirstPoint; j<nLastPoint; ++j ) + aTmp.append( rCurrPoly.getB2DPoint(j) ); + + aSubsetPoly.append( aTmp ); + } + } + + return aSubsetPoly; + } + + B2DPolyPolygon UnoPolyPolygon::getPolyPolygonUnsafe() const + { + return maPolyPoly; + } + +#define IMPLEMENTATION_NAME "gfx::internal::UnoPolyPolygon" +#define SERVICE_NAME "com.sun.star.rendering.PolyPolygon2D" + ::rtl::OUString SAL_CALL UnoPolyPolygon::getImplementationName() throw( uno::RuntimeException ) + { + return ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( IMPLEMENTATION_NAME ) ); + } + + sal_Bool SAL_CALL UnoPolyPolygon::supportsService( const ::rtl::OUString& ServiceName ) throw( uno::RuntimeException ) + { + return ServiceName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM ( SERVICE_NAME ) ); + } + + uno::Sequence< ::rtl::OUString > SAL_CALL UnoPolyPolygon::getSupportedServiceNames() throw( uno::RuntimeException ) + { + uno::Sequence< ::rtl::OUString > aRet(1); + aRet[0] = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM ( SERVICE_NAME ) ); + + return aRet; + } + + B2DPolyPolygon UnoPolyPolygon::getPolyPolygon() const + { + osl::MutexGuard const guard( m_aMutex ); + + // detach result from us + B2DPolyPolygon aRet( maPolyPoly ); + aRet.makeUnique(); + return aRet; + } + +} +} diff --git a/basegfx/source/tuple/b2dtuple.cxx b/basegfx/source/tuple/b2dtuple.cxx new file mode 100644 index 000000000000..01a3bf145d7e --- /dev/null +++ b/basegfx/source/tuple/b2dtuple.cxx @@ -0,0 +1,84 @@ +/************************************************************************* + * + * 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/tuple/b2dtuple.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <rtl/instance.hxx> + +namespace { struct EmptyTuple : public rtl::Static<basegfx::B2DTuple, EmptyTuple> {}; } +#include <basegfx/tuple/b2ituple.hxx> + +namespace basegfx +{ + const B2DTuple& B2DTuple::getEmptyTuple() + { + return EmptyTuple::get(); + } + + B2DTuple::B2DTuple(const B2ITuple& rTup) + : mfX( rTup.getX() ), + mfY( rTup.getY() ) + {} + + void B2DTuple::correctValues(const double fCompareValue) + { + if(0.0 == fCompareValue) + { + if(::basegfx::fTools::equalZero(mfX)) + { + mfX = 0.0; + } + + if(::basegfx::fTools::equalZero(mfY)) + { + mfY = 0.0; + } + } + else + { + if(::basegfx::fTools::equal(mfX, fCompareValue)) + { + mfX = fCompareValue; + } + + if(::basegfx::fTools::equal(mfY, fCompareValue)) + { + mfY = fCompareValue; + } + } + } + + B2ITuple fround(const B2DTuple& rTup) + { + return B2ITuple(fround(rTup.getX()), fround(rTup.getY())); + } + +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/tuple/b2i64tuple.cxx b/basegfx/source/tuple/b2i64tuple.cxx new file mode 100644 index 000000000000..3457fc138dd1 --- /dev/null +++ b/basegfx/source/tuple/b2i64tuple.cxx @@ -0,0 +1,44 @@ +/************************************************************************* + * + * 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/tuple/b2i64tuple.hxx> +#include <rtl/instance.hxx> + +namespace { struct EmptyTuple : public rtl::Static<basegfx::B2I64Tuple, EmptyTuple> {}; } + +namespace basegfx +{ + const B2I64Tuple& B2I64Tuple::getEmptyTuple() + { + return EmptyTuple::get(); + } + +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/tuple/b2ituple.cxx b/basegfx/source/tuple/b2ituple.cxx new file mode 100644 index 000000000000..eb9888f11ecb --- /dev/null +++ b/basegfx/source/tuple/b2ituple.cxx @@ -0,0 +1,153 @@ +/************************************************************************* + * + * 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/tuple/b2ituple.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <rtl/instance.hxx> + +namespace { struct EmptyTuple : public rtl::Static<basegfx::B2ITuple, EmptyTuple> {}; } + +namespace basegfx +{ + const B2ITuple& B2ITuple::getEmptyTuple() + { + return EmptyTuple::get(); + } + + // external operators + ////////////////////////////////////////////////////////////////////////// + + B2ITuple minimum(const B2ITuple& rTupA, const B2ITuple& rTupB) + { + B2ITuple aMin( + (rTupB.getX() < rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() < rTupA.getY()) ? rTupB.getY() : rTupA.getY()); + return aMin; + } + + B2ITuple maximum(const B2ITuple& rTupA, const B2ITuple& rTupB) + { + B2ITuple aMax( + (rTupB.getX() > rTupA.getX()) ? rTupB.getX() : rTupA.getX(), + (rTupB.getY() > rTupA.getY()) ? rTupB.getY() : rTupA.getY()); + return aMax; + } + + B2ITuple absolute(const B2ITuple& rTup) + { + B2ITuple aAbs( + (0 > rTup.getX()) ? -rTup.getX() : rTup.getX(), + (0 > rTup.getY()) ? -rTup.getY() : rTup.getY()); + return aAbs; + } + + B2DTuple interpolate(const B2ITuple& rOld1, const B2ITuple& rOld2, double t) + { + B2DTuple aInt( + ((rOld2.getX() - rOld1.getX()) * t) + rOld1.getX(), + ((rOld2.getY() - rOld1.getY()) * t) + rOld1.getY()); + return aInt; + } + + B2DTuple average(const B2ITuple& rOld1, const B2ITuple& rOld2) + { + B2DTuple aAvg( + (rOld1.getX() + rOld2.getX()) * 0.5, + (rOld1.getY() + rOld2.getY()) * 0.5); + return aAvg; + } + + B2DTuple average(const B2ITuple& rOld1, const B2ITuple& rOld2, const B2ITuple& rOld3) + { + B2DTuple aAvg( + (rOld1.getX() + rOld2.getX() + rOld3.getX()) * (1.0 / 3.0), + (rOld1.getY() + rOld2.getY() + rOld3.getY()) * (1.0 / 3.0)); + return aAvg; + } + + B2ITuple operator+(const B2ITuple& rTupA, const B2ITuple& rTupB) + { + B2ITuple aSum(rTupA); + aSum += rTupB; + return aSum; + } + + B2ITuple operator-(const B2ITuple& rTupA, const B2ITuple& rTupB) + { + B2ITuple aSub(rTupA); + aSub -= rTupB; + return aSub; + } + + B2ITuple operator/(const B2ITuple& rTupA, const B2ITuple& rTupB) + { + B2ITuple aDiv(rTupA); + aDiv /= rTupB; + return aDiv; + } + + B2ITuple operator*(const B2ITuple& rTupA, const B2ITuple& rTupB) + { + B2ITuple aMul(rTupA); + aMul *= rTupB; + return aMul; + } + + B2ITuple operator*(const B2ITuple& rTup, sal_Int32 t) + { + B2ITuple aNew(rTup); + aNew *= t; + return aNew; + } + + B2ITuple operator*(sal_Int32 t, const B2ITuple& rTup) + { + B2ITuple aNew(rTup); + aNew *= t; + return aNew; + } + + B2ITuple operator/(const B2ITuple& rTup, sal_Int32 t) + { + B2ITuple aNew(rTup); + aNew /= t; + return aNew; + } + + B2ITuple operator/(sal_Int32 t, const B2ITuple& rTup) + { + B2ITuple aNew(t, t); + B2ITuple aTmp(rTup); + aNew /= aTmp; + return aNew; + } + +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/tuple/b3dtuple.cxx b/basegfx/source/tuple/b3dtuple.cxx new file mode 100644 index 000000000000..d9204154b192 --- /dev/null +++ b/basegfx/source/tuple/b3dtuple.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. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <basegfx/tuple/b3dtuple.hxx> +#include <rtl/instance.hxx> + +namespace { struct EmptyTuple : public rtl::Static<basegfx::B3DTuple, EmptyTuple> {}; } +#include <basegfx/tuple/b3ituple.hxx> + +namespace basegfx +{ + const B3DTuple& B3DTuple::getEmptyTuple() + { + return EmptyTuple::get(); + } + + B3DTuple::B3DTuple(const B3ITuple& rTup) + : mfX( rTup.getX() ), + mfY( rTup.getY() ), + mfZ( rTup.getZ() ) + {} + + B3ITuple fround(const B3DTuple& rTup) + { + return B3ITuple(fround(rTup.getX()), fround(rTup.getY()), fround(rTup.getZ())); + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/tuple/b3i64tuple.cxx b/basegfx/source/tuple/b3i64tuple.cxx new file mode 100644 index 000000000000..ce2094568bb8 --- /dev/null +++ b/basegfx/source/tuple/b3i64tuple.cxx @@ -0,0 +1,43 @@ +/************************************************************************* + * + * 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/tuple/b3i64tuple.hxx> +#include <rtl/instance.hxx> + +namespace { struct EmptyTuple : public rtl::Static<basegfx::B3I64Tuple, EmptyTuple> {}; } + +namespace basegfx +{ + const B3I64Tuple& B3I64Tuple::getEmptyTuple() + { + return EmptyTuple::get(); + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/tuple/b3ituple.cxx b/basegfx/source/tuple/b3ituple.cxx new file mode 100644 index 000000000000..014e8876588c --- /dev/null +++ b/basegfx/source/tuple/b3ituple.cxx @@ -0,0 +1,43 @@ +/************************************************************************* + * + * 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/tuple/b3ituple.hxx> +#include <rtl/instance.hxx> + +namespace { struct EmptyTuple : public rtl::Static<basegfx::B3ITuple, EmptyTuple> {}; } + +namespace basegfx +{ + const B3ITuple& B3ITuple::getEmptyTuple() + { + return EmptyTuple::get(); + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/tuple/makefile.mk b/basegfx/source/tuple/makefile.mk new file mode 100644 index 000000000000..01a4c6de2d3c --- /dev/null +++ b/basegfx/source/tuple/makefile.mk @@ -0,0 +1,52 @@ +#************************************************************************* +# +# 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=tuple + +#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb +#ENABLE_EXCEPTIONS=FALSE +#USE_DEFFILE=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= \ + $(SLO)$/b2dtuple.obj \ + $(SLO)$/b3dtuple.obj \ + $(SLO)$/b2ituple.obj \ + $(SLO)$/b3ituple.obj \ + $(SLO)$/b2i64tuple.obj \ + $(SLO)$/b3i64tuple.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/vector/b2dvector.cxx b/basegfx/source/vector/b2dvector.cxx new file mode 100644 index 000000000000..bb08b104eff8 --- /dev/null +++ b/basegfx/source/vector/b2dvector.cxx @@ -0,0 +1,219 @@ +/************************************************************************* + * + * 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/vector/b2dvector.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> + +namespace basegfx +{ + B2DVector& B2DVector::normalize() + { + double fLen(scalar(*this)); + + if(fTools::equalZero(fLen)) + { + mfX = 0.0; + mfY = 0.0; + } + else + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fLen)) + { + fLen = sqrt(fLen); + + if(!fTools::equalZero(fLen)) + { + mfX /= fLen; + mfY /= fLen; + } + } + } + + return *this; + } + + B2DVector& B2DVector::operator=( const B2DTuple& rVec ) + { + mfX = rVec.getX(); + mfY = rVec.getY(); + return *this; + } + + + double B2DVector::getLength() const + { + if(fTools::equalZero(mfX)) + { + return fabs(mfY); + } + else if(fTools::equalZero(mfY)) + { + return fabs(mfX); + } + + return hypot( mfX, mfY ); + } + + double B2DVector::scalar( const B2DVector& rVec ) const + { + return((mfX * rVec.mfX) + (mfY * rVec.mfY)); + } + + double B2DVector::cross( const B2DVector& rVec ) const + { + return(mfX * rVec.getY() - mfY * rVec.getX()); + } + + double B2DVector::angle( const B2DVector& rVec ) const + { + return atan2(mfX * rVec.getY() - mfY * rVec.getX(), + mfX * rVec.getX() + mfY * rVec.getY()); + } + + const B2DVector& B2DVector::getEmptyVector() + { + return (const B2DVector&) B2DTuple::getEmptyTuple(); + } + + B2DVector& B2DVector::operator*=( const B2DHomMatrix& rMat ) + { + const double fTempX( rMat.get(0,0)*mfX + + rMat.get(0,1)*mfY ); + const double fTempY( rMat.get(1,0)*mfX + + rMat.get(1,1)*mfY ); + mfX = fTempX; + mfY = fTempY; + + return *this; + } + + B2DVector& B2DVector::setLength(double fLen) + { + double fLenNow(scalar(*this)); + + if(!fTools::equalZero(fLenNow)) + { + const double fOne(10.0); + + if(!fTools::equal(fOne, fLenNow)) + { + fLen /= sqrt(fLenNow); + } + + mfX *= fLen; + mfY *= fLen; + } + + return *this; + } + + bool B2DVector::isNormalized() const + { + const double fOne(1.0); + const double fScalar(scalar(*this)); + + return fTools::equal(fOne, fScalar); + } + + bool areParallel( const B2DVector& rVecA, const B2DVector& rVecB ) + { + const double fValA(rVecA.getX() * rVecB.getY()); + const double fValB(rVecA.getY() * rVecB.getX()); + + return fTools::equal(fValA, fValB); + } + + B2VectorOrientation getOrientation( const B2DVector& rVecA, const B2DVector& rVecB ) + { + double fVal(rVecA.getX() * rVecB.getY() - rVecA.getY() * rVecB.getX()); + + if(fTools::equalZero(fVal)) + { + return ORIENTATION_NEUTRAL; + } + + if(fVal > 0.0) + { + return ORIENTATION_POSITIVE; + } + else + { + return ORIENTATION_NEGATIVE; + } + } + + B2DVector getPerpendicular( const B2DVector& rNormalizedVec ) + { + B2DVector aPerpendicular(-rNormalizedVec.getY(), rNormalizedVec.getX()); + return aPerpendicular; + } + + B2DVector getNormalizedPerpendicular( const B2DVector& rVec ) + { + B2DVector aPerpendicular(rVec); + aPerpendicular.normalize(); + const double aTemp(-aPerpendicular.getY()); + aPerpendicular.setY(aPerpendicular.getX()); + aPerpendicular.setX(aTemp); + return aPerpendicular; + } + + B2DVector operator*( const B2DHomMatrix& rMat, const B2DVector& rVec ) + { + B2DVector aRes( rVec ); + return aRes*=rMat; + } + + B2VectorContinuity getContinuity(const B2DVector& rBackVector, const B2DVector& rForwardVector ) + { + if(rBackVector.equalZero() || rForwardVector.equalZero()) + { + return CONTINUITY_NONE; + } + + if(fTools::equal(rBackVector.getX(), -rForwardVector.getX()) && fTools::equal(rBackVector.getY(), -rForwardVector.getY())) + { + // same direction and same length -> C2 + return CONTINUITY_C2; + } + + if(areParallel(rBackVector, rForwardVector) && rBackVector.scalar(rForwardVector) < 0.0) + { + // parallel and opposite direction -> C1 + return CONTINUITY_C1; + } + + return CONTINUITY_NONE; + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/vector/b2ivector.cxx b/basegfx/source/vector/b2ivector.cxx new file mode 100644 index 000000000000..026a600c0c94 --- /dev/null +++ b/basegfx/source/vector/b2ivector.cxx @@ -0,0 +1,159 @@ +/************************************************************************* + * + * 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/vector/b2ivector.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> + +namespace basegfx +{ + B2IVector& B2IVector::operator=( const ::basegfx::B2ITuple& rVec ) + { + mnX = rVec.getX(); + mnY = rVec.getY(); + return *this; + } + + + double B2IVector::getLength() const + { + return hypot( mnX, mnY ); + } + + double B2IVector::scalar( const B2IVector& rVec ) const + { + return((mnX * rVec.mnX) + (mnY * rVec.mnY)); + } + + double B2IVector::cross( const B2IVector& rVec ) const + { + return(mnX * rVec.getY() - mnY * rVec.getX()); + } + + double B2IVector::angle( const B2IVector& rVec ) const + { + return atan2(double( mnX * rVec.getY() - mnY * rVec.getX()), + double( mnX * rVec.getX() + mnY * rVec.getY())); + } + + const B2IVector& B2IVector::getEmptyVector() + { + return (const B2IVector&) ::basegfx::B2ITuple::getEmptyTuple(); + } + + B2IVector& B2IVector::operator*=( const B2DHomMatrix& rMat ) + { + mnX = fround( rMat.get(0,0)*mnX + + rMat.get(0,1)*mnY ); + mnY = fround( rMat.get(1,0)*mnX + + rMat.get(1,1)*mnY ); + + return *this; + } + + B2IVector& B2IVector::setLength(double fLen) + { + double fLenNow(scalar(*this)); + + if(!::basegfx::fTools::equalZero(fLenNow)) + { + const double fOne(10.0); + + if(!::basegfx::fTools::equal(fOne, fLenNow)) + { + fLen /= sqrt(fLenNow); + } + + mnX = fround( mnX*fLen ); + mnY = fround( mnY*fLen ); + } + + return *this; + } + + bool areParallel( const B2IVector& rVecA, const B2IVector& rVecB ) + { + double fVal(rVecA.getX() * rVecB.getY() - rVecA.getY() * rVecB.getX()); + return ::basegfx::fTools::equalZero(fVal); + } + + B2VectorOrientation getOrientation( const B2IVector& rVecA, const B2IVector& rVecB ) + { + double fVal(rVecA.getX() * rVecB.getY() - rVecA.getY() * rVecB.getX()); + + if(fVal > 0.0) + { + return ORIENTATION_POSITIVE; + } + + if(fVal < 0.0) + { + return ORIENTATION_NEGATIVE; + } + + return ORIENTATION_NEUTRAL; + } + + B2IVector getPerpendicular( const B2IVector& rNormalizedVec ) + { + B2IVector aPerpendicular(-rNormalizedVec.getY(), rNormalizedVec.getX()); + return aPerpendicular; + } + + B2IVector operator*( const B2DHomMatrix& rMat, const B2IVector& rVec ) + { + B2IVector aRes( rVec ); + return aRes*=rMat; + } + + B2VectorContinuity getContinuity(const B2IVector& rBackVector, const B2IVector& rForwardVector ) + { + B2VectorContinuity eRetval(CONTINUITY_NONE); + + if(!rBackVector.equalZero() && !rForwardVector.equalZero()) + { + const B2IVector aInverseForwardVector(-rForwardVector.getX(), -rForwardVector.getY()); + + if(rBackVector == aInverseForwardVector) + { + // same direction and same length -> C2 + eRetval = CONTINUITY_C2; + } + else if(areParallel(rBackVector, aInverseForwardVector)) + { + // same direction -> C1 + eRetval = CONTINUITY_C1; + } + } + + return eRetval; + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/vector/b3dvector.cxx b/basegfx/source/vector/b3dvector.cxx new file mode 100644 index 000000000000..74e4eb07eb33 --- /dev/null +++ b/basegfx/source/vector/b3dvector.cxx @@ -0,0 +1,115 @@ +/************************************************************************* + * + * 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/vector/b3dvector.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + B3DVector& B3DVector::normalize() + { + double fLen(scalar(*this)); + + if(!::basegfx::fTools::equalZero(fLen)) + { + const double fOne(1.0); + + if(!::basegfx::fTools::equal(fOne, fLen)) + { + fLen = sqrt(fLen); + + if(!::basegfx::fTools::equalZero(fLen)) + { + mfX /= fLen; + mfY /= fLen; + mfZ /= fLen; + } + } + } + + return *this; + } + + B3DVector B3DVector::getPerpendicular(const B3DVector& rNormalizedVec) const + { + B3DVector aNew(*this); + aNew = cross(aNew, rNormalizedVec); + aNew.normalize(); + return aNew; + } + + B3DVector B3DVector::getProjectionOnPlane(const B3DVector& rNormalizedPlane) const + { + B3DVector aNew(*this); + aNew = cross(aNew, rNormalizedPlane); + aNew = cross(aNew, rNormalizedPlane); + + aNew.mfX = mfX - aNew.mfX; + aNew.mfY = mfY - aNew.mfY; + aNew.mfZ = mfZ - aNew.mfZ; + + return aNew; + } + + B3DVector& B3DVector::operator*=( const ::basegfx::B3DHomMatrix& rMat ) + { + const double fTempX( rMat.get(0,0)*mfX + rMat.get(0,1)*mfY + rMat.get(0,2)*mfZ ); + const double fTempY( rMat.get(1,0)*mfX + rMat.get(1,1)*mfY + rMat.get(1,2)*mfZ ); + const double fTempZ( rMat.get(2,0)*mfX + rMat.get(2,1)*mfY + rMat.get(2,2)*mfZ ); + mfX = fTempX; + mfY = fTempY; + mfZ = fTempZ; + + return *this; + } + + B3DVector operator*( const ::basegfx::B3DHomMatrix& rMat, const B3DVector& rVec ) + { + B3DVector aRes( rVec ); + return aRes*=rMat; + } + + bool areParallel( const B3DVector& rVecA, const B3DVector& rVecB ) + { + // i think fastest is to compare relations, need no square or division + if(!fTools::equal(rVecA.getX() * rVecB.getY(), rVecA.getY() * rVecB.getX())) + return false; + + if(!fTools::equal(rVecA.getX() * rVecB.getZ(), rVecA.getZ() * rVecB.getX())) + return false; + + return (fTools::equal(rVecA.getY() * rVecB.getZ(), rVecA.getZ() * rVecB.getY())); + } + +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/vector/b3ivector.cxx b/basegfx/source/vector/b3ivector.cxx new file mode 100644 index 000000000000..a11871ef1b19 --- /dev/null +++ b/basegfx/source/vector/b3ivector.cxx @@ -0,0 +1,51 @@ +/************************************************************************* + * + * 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/vector/b3ivector.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> + +namespace basegfx +{ + B3IVector& B3IVector::operator*=( const B3DHomMatrix& rMat ) + { + mnX = fround( rMat.get(0,0)*mnX + rMat.get(0,1)*mnY + rMat.get(0,2)*mnZ ); + mnY = fround( rMat.get(1,0)*mnX + rMat.get(1,1)*mnY + rMat.get(1,2)*mnZ ); + mnZ = fround( rMat.get(2,0)*mnX + rMat.get(2,1)*mnY + rMat.get(2,2)*mnZ ); + + return *this; + } + + B3IVector operator*( const B3DHomMatrix& rMat, const B3IVector& rVec ) + { + B3IVector aRes( rVec ); + return aRes*=rMat; + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/vector/makefile.mk b/basegfx/source/vector/makefile.mk new file mode 100644 index 000000000000..defc7a31dd51 --- /dev/null +++ b/basegfx/source/vector/makefile.mk @@ -0,0 +1,50 @@ +#************************************************************************* +# +# 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=vector + +#UNOUCRRDB=$(SOLARBINDIR)$/applicat.rdb +#ENABLE_EXCEPTIONS=FALSE +#USE_DEFFILE=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= \ + $(SLO)$/b2dvector.obj \ + $(SLO)$/b3dvector.obj \ + $(SLO)$/b2ivector.obj \ + $(SLO)$/b3ivector.obj + +# --- Targets ---------------------------------- + +.INCLUDE : target.mk diff --git a/basegfx/source/workbench/Makefile b/basegfx/source/workbench/Makefile new file mode 100644 index 000000000000..fe23a4d06708 --- /dev/null +++ b/basegfx/source/workbench/Makefile @@ -0,0 +1,16 @@ +# Testbuild + +#test : bezierclip.cxx convexhull.cxx +# g++ -Wall -g \ +# -I. -I. -I../inc -I./inc -I./unx/inc -I./unxlngi4/inc -I. -I/develop4/update/SRX644/unxlngi4/inc.m4/stl -I/develop4/update/SRX644/unxlngi4/inc.m4/external -I/develop4/update/SRX644/unxlngi4/inc.m4 -I/develop4/update/SRX644/src.m4/solenv/unxlngi4/inc -I/net/grande/develop6/update/dev/gcc_3.0.1_linux_libc2.11_turbolinux/include -I/develop4/update/SRX644/src.m4/solenv/inc -I/develop4/update/SRX644/unxlngi4/inc.m4/stl -I/net/grande.germany/develop6/update/dev/gcc_3.0.1_linux_libc2.11_turbolinux/redhat60/usr/include -I/net/grande.germany/develop6/update/dev/gcc_3.0.1_linux_libc2.11_turbolinux/redhat60/usr/include/X11 -I/develop4/update/SRX644/src.m4/res -I/net/grande/develop6/update/dev/Linux_JDK_1.4.0/include -I/net/grande/develop6/update/dev/Linux_JDK_1.4.0/include/linux -I. -I./res -I. \ +# -include preinclude.h -D_USE_NAMESPACE -DGLIBC=2 -D_USE_NAMESPACE=1 -DSTLPORT_VERSION=400 -D_STLP_DEBUG \ +# bezierclip.cxx convexhull.cxx -o bezierclip + +prog : bezierclip.cxx convexhull.cxx + g++ -Wall -g bezierclip.cxx convexhull.cxx -o bezierclip + +test : testconvexhull.cxx + g++ -Wall -g testconvexhull.cxx -o testhull + +.cxx.o: + g++ -c $(LOCALDEFINES) $(CCFLAGS) $< diff --git a/basegfx/source/workbench/bezierclip.cxx b/basegfx/source/workbench/bezierclip.cxx new file mode 100644 index 000000000000..12c0d6f7d6df --- /dev/null +++ b/basegfx/source/workbench/bezierclip.cxx @@ -0,0 +1,2057 @@ +/************************************************************************* + * + * 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 <algorithm> +#include <iterator> +#include <vector> +#include <utility> + +#include <math.h> + +#include "bezierclip.hxx" +#include "gauss.hxx" + + + +// what to test +#define WITH_ASSERTIONS +//#define WITH_CONVEXHULL_TEST +//#define WITH_MULTISUBDIVIDE_TEST +//#define WITH_FATLINE_TEST +//#define WITH_CALCFOCUS_TEST +//#define WITH_SAFEPARAMBASE_TEST +//#define WITH_SAFEPARAMS_TEST +//#define WITH_SAFEPARAM_DETAILED_TEST +//#define WITH_SAFEFOCUSPARAM_CALCFOCUS +//#define WITH_SAFEFOCUSPARAM_TEST +//#define WITH_SAFEFOCUSPARAM_DETAILED_TEST +#define WITH_BEZIERCLIP_TEST + + + +// ----------------------------------------------------------------------------- + +/* Implementation of the so-called 'Fat-Line Bezier Clipping Algorithm' by Sederberg et al. + * + * Actual reference is: T. W. Sederberg and T Nishita: Curve + * intersection using Bezier clipping. In Computer Aided Design, 22 + * (9), 1990, pp. 538--549 + */ + +// ----------------------------------------------------------------------------- + +/* Misc helper + * =========== + */ +int fallFac( int n, int k ) +{ +#ifdef WITH_ASSERTIONS + assert(n>=k); // "For factorials, n must be greater or equal k" + assert(n>=0); // "For factorials, n must be positive" + assert(k>=0); // "For factorials, k must be positive" +#endif + + int res( 1 ); + + while( k-- && n ) res *= n--; + + return res; +} + +// ----------------------------------------------------------------------------- + +int fac( int n ) +{ + return fallFac(n, n); +} + +// ----------------------------------------------------------------------------- + +/* Bezier fat line clipping part + * ============================= + */ + +// ----------------------------------------------------------------------------- + +void Impl_calcFatLine( FatLine& line, const Bezier& c ) +{ + // Prepare normalized implicit line + // ================================ + + // calculate vector orthogonal to p1-p4: + line.a = -(c.p0.y - c.p3.y); + line.b = (c.p0.x - c.p3.x); + + // normalize + const double len( sqrt( line.a*line.a + line.b*line.b ) ); + if( !tolZero(len) ) + { + line.a /= len; + line.b /= len; + } + + line.c = -(line.a*c.p0.x + line.b*c.p0.y); + + + // Determine bounding fat line from it + // =================================== + + // calc control point distances + const double dP2( calcLineDistance(line.a, line.b, line.c, c.p1.x, c.p1.y ) ); + const double dP3( calcLineDistance(line.a, line.b, line.c, c.p2.x, c.p2.y ) ); + + // calc approximate bounding lines to curve (tight bounds are + // possible here, but more expensive to calculate and thus not + // worth the overhead) + if( dP2 * dP3 > 0.0 ) + { + line.dMin = 3.0/4.0 * ::std::min(0.0, ::std::min(dP2, dP3)); + line.dMax = 3.0/4.0 * ::std::max(0.0, ::std::max(dP2, dP3)); + } + else + { + line.dMin = 4.0/9.0 * ::std::min(0.0, ::std::min(dP2, dP3)); + line.dMax = 4.0/9.0 * ::std::max(0.0, ::std::max(dP2, dP3)); + } +} + +void Impl_calcBounds( Point2D& leftTop, + Point2D& rightBottom, + const Bezier& c1 ) +{ + leftTop.x = ::std::min( c1.p0.x, ::std::min( c1.p1.x, ::std::min( c1.p2.x, c1.p3.x ) ) ); + leftTop.y = ::std::min( c1.p0.y, ::std::min( c1.p1.y, ::std::min( c1.p2.y, c1.p3.y ) ) ); + rightBottom.x = ::std::max( c1.p0.x, ::std::max( c1.p1.x, ::std::max( c1.p2.x, c1.p3.x ) ) ); + rightBottom.y = ::std::max( c1.p0.y, ::std::max( c1.p1.y, ::std::max( c1.p2.y, c1.p3.y ) ) ); +} + +bool Impl_doBBoxIntersect( const Bezier& c1, + const Bezier& c2 ) +{ + // calc rectangular boxes from c1 and c2 + Point2D lt1; + Point2D rb1; + Point2D lt2; + Point2D rb2; + + Impl_calcBounds( lt1, rb1, c1 ); + Impl_calcBounds( lt2, rb2, c2 ); + + if( ::std::min(rb1.x, rb2.x) < ::std::max(lt1.x, lt2.x) || + ::std::min(rb1.y, rb2.y) < ::std::max(lt1.y, lt2.y) ) + { + return false; + } + else + { + return true; + } +} + +/* calculates two t's for the given bernstein control polygon: the first is + * the intersection of the min value line with the convex hull from + * the left, the second is the intersection of the max value line with + * the convex hull from the right. + */ +bool Impl_calcSafeParams( double& t1, + double& t2, + const Polygon2D& rPoly, + double lowerYBound, + double upperYBound ) +{ + // need the convex hull of the control polygon, as this is + // guaranteed to completely bound the curve + Polygon2D convHull( convexHull(rPoly) ); + + // init min and max buffers + t1 = 0.0 ; + double currLowerT( 1.0 ); + + t2 = 1.0; + double currHigherT( 0.0 ); + + if( convHull.size() <= 1 ) + return false; // only one point? Then we're done with clipping + + /* now, clip against lower and higher bounds */ + Point2D p0; + Point2D p1; + + bool bIntersection( false ); + + for( Polygon2D::size_type i=0; i<convHull.size(); ++i ) + { + // have to check against convHull.size() segments, as the + // convex hull is, by definition, closed. Thus, for the + // last point, we take the first point as partner. + if( i+1 == convHull.size() ) + { + // close the polygon + p0 = convHull[i]; + p1 = convHull[0]; + } + else + { + p0 = convHull[i]; + p1 = convHull[i+1]; + } + + // is the segment in question within or crossing the + // horizontal band spanned by lowerYBound and upperYBound? If + // not, we've got no intersection. If yes, we maybe don't have + // an intersection, but we've got to update the permissible + // range, nevertheless. This is because inside lying segments + // leads to full range forbidden. + if( (tolLessEqual(p0.y, upperYBound) || tolLessEqual(p1.y, upperYBound)) && + (tolGreaterEqual(p0.y, lowerYBound) || tolGreaterEqual(p1.y, lowerYBound)) ) + { + // calc intersection of convex hull segment with + // one of the horizontal bounds lines + const double r_x( p1.x - p0.x ); + const double r_y( p1.y - p0.y ); + + if( tolZero(r_y) ) + { + // r_y is virtually zero, thus we've got a horizontal + // line. Now check whether we maybe coincide with lower or + // upper horizonal bound line. + if( tolEqual(p0.y, lowerYBound) || + tolEqual(p0.y, upperYBound) ) + { + // yes, simulate intersection then + currLowerT = ::std::min(currLowerT, ::std::min(p0.x, p1.x)); + currHigherT = ::std::max(currHigherT, ::std::max(p0.x, p1.x)); + } + } + else + { + // check against lower and higher bounds + // ===================================== + + // calc intersection with horizontal dMin line + const double currTLow( (lowerYBound - p0.y) * r_x / r_y + p0.x ); + + // calc intersection with horizontal dMax line + const double currTHigh( (upperYBound - p0.y) * r_x / r_y + p0.x ); + + currLowerT = ::std::min(currLowerT, ::std::min(currTLow, currTHigh)); + currHigherT = ::std::max(currHigherT, ::std::max(currTLow, currTHigh)); + } + + // set flag that at least one segment is contained or + // intersects given horizontal band. + bIntersection = true; + } + } + +#ifndef WITH_SAFEPARAMBASE_TEST + // limit intersections found to permissible t parameter range + t1 = ::std::max(0.0, currLowerT); + t2 = ::std::min(1.0, currHigherT); +#endif + + return bIntersection; +} + + +/* calculates two t's for the given bernstein polynomial: the first is + * the intersection of the min value line with the convex hull from + * the left, the second is the intersection of the max value line with + * the convex hull from the right. + * + * The polynomial coefficients c0 to c3 given to this method + * must correspond to t values of 0, 1/3, 2/3 and 1, respectively. + */ +bool Impl_calcSafeParams_clip( double& t1, + double& t2, + const FatLine& bounds, + double c0, + double c1, + double c2, + double c3 ) +{ + /* first of all, determine convex hull of c0-c3 */ + Polygon2D poly(4); + poly[0] = Point2D(0, c0); + poly[1] = Point2D(1.0/3.0, c1); + poly[2] = Point2D(2.0/3.0, c2); + poly[3] = Point2D(1, c3); + +#ifndef WITH_SAFEPARAM_DETAILED_TEST + + return Impl_calcSafeParams( t1, t2, poly, bounds.dMin, bounds.dMax ); + +#else + bool bRet( Impl_calcSafeParams( t1, t2, poly, bounds.dMin, bounds.dMax ) ); + + Polygon2D convHull( convexHull( poly ) ); + + cout << "# convex hull testing" << endl + << "plot [t=0:1] "; + cout << " bez(" + << poly[0].x << "," + << poly[1].x << "," + << poly[2].x << "," + << poly[3].x << ",t),bez(" + << poly[0].y << "," + << poly[1].y << "," + << poly[2].y << "," + << poly[3].y << ",t), " + << "t, " << bounds.dMin << ", " + << "t, " << bounds.dMax << ", " + << t1 << ", t, " + << t2 << ", t, " + << "'-' using ($1):($2) title \"control polygon\" with lp, " + << "'-' using ($1):($2) title \"convex hull\" with lp" << endl; + + unsigned int k; + for( k=0; k<poly.size(); ++k ) + { + cout << poly[k].x << " " << poly[k].y << endl; + } + cout << poly[0].x << " " << poly[0].y << endl; + cout << "e" << endl; + + for( k=0; k<convHull.size(); ++k ) + { + cout << convHull[k].x << " " << convHull[k].y << endl; + } + cout << convHull[0].x << " " << convHull[0].y << endl; + cout << "e" << endl; + + return bRet; +#endif +} + +// ----------------------------------------------------------------------------- + +void Impl_deCasteljauAt( Bezier& part1, + Bezier& part2, + const Bezier& input, + double t ) +{ + // deCasteljau bezier arc, scheme is: + // + // First row is C_0^n,C_1^n,...,C_n^n + // Second row is P_1^n,...,P_n^n + // etc. + // with P_k^r = (1 - x_s)P_{k-1}^{r-1} + x_s P_k{r-1} + // + // this results in: + // + // P1 P2 P3 P4 + // L1 P2 P3 R4 + // L2 H R3 + // L3 R2 + // L4/R1 + if( tolZero(t) ) + { + // t is zero -> part2 is input curve, part1 is empty (input.p0, that is) + part1.p0.x = part1.p1.x = part1.p2.x = part1.p3.x = input.p0.x; + part1.p0.y = part1.p1.y = part1.p2.y = part1.p3.y = input.p0.y; + part2 = input; + } + else if( tolEqual(t, 1.0) ) + { + // t is one -> part1 is input curve, part2 is empty (input.p3, that is) + part1 = input; + part2.p0.x = part2.p1.x = part2.p2.x = part2.p3.x = input.p3.x; + part2.p0.y = part2.p1.y = part2.p2.y = part2.p3.y = input.p3.y; + } + else + { + part1.p0.x = input.p0.x; part1.p0.y = input.p0.y; + part1.p1.x = (1.0 - t)*part1.p0.x + t*input.p1.x; part1.p1.y = (1.0 - t)*part1.p0.y + t*input.p1.y; + const double Hx ( (1.0 - t)*input.p1.x + t*input.p2.x ), Hy ( (1.0 - t)*input.p1.y + t*input.p2.y ); + part1.p2.x = (1.0 - t)*part1.p1.x + t*Hx; part1.p2.y = (1.0 - t)*part1.p1.y + t*Hy; + part2.p3.x = input.p3.x; part2.p3.y = input.p3.y; + part2.p2.x = (1.0 - t)*input.p2.x + t*input.p3.x; part2.p2.y = (1.0 - t)*input.p2.y + t*input.p3.y; + part2.p1.x = (1.0 - t)*Hx + t*part2.p2.x; part2.p1.y = (1.0 - t)*Hy + t*part2.p2.y; + part2.p0.x = (1.0 - t)*part1.p2.x + t*part2.p1.x; part2.p0.y = (1.0 - t)*part1.p2.y + t*part2.p1.y; + part1.p3.x = part2.p0.x; part1.p3.y = part2.p0.y; + } +} + +// ----------------------------------------------------------------------------- + +void printCurvesWithSafeRange( const Bezier& c1, const Bezier& c2, double t1_c1, double t2_c1, + const Bezier& c2_part, const FatLine& bounds_c2 ) +{ + static int offset = 0; + + cout << "# safe param range testing" << endl + << "plot [t=0.0:1.0] "; + + // clip safe ranges off c1 + Bezier c1_part1; + Bezier c1_part2; + Bezier c1_part3; + + // subdivide at t1_c1 + Impl_deCasteljauAt( c1_part1, c1_part2, c1, t1_c1 ); + // subdivide at t2_c1 + Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, t2_c1 ); + + // output remaining segment (c1_part1) + + cout << "bez(" + << c1.p0.x+offset << "," + << c1.p1.x+offset << "," + << c1.p2.x+offset << "," + << c1.p3.x+offset << ",t),bez(" + << c1.p0.y << "," + << c1.p1.y << "," + << c1.p2.y << "," + << c1.p3.y << ",t), bez(" + << c2.p0.x+offset << "," + << c2.p1.x+offset << "," + << c2.p2.x+offset << "," + << c2.p3.x+offset << ",t),bez(" + << c2.p0.y << "," + << c2.p1.y << "," + << c2.p2.y << "," + << c2.p3.y << ",t), " +#if 1 + << "bez(" + << c1_part1.p0.x+offset << "," + << c1_part1.p1.x+offset << "," + << c1_part1.p2.x+offset << "," + << c1_part1.p3.x+offset << ",t),bez(" + << c1_part1.p0.y << "," + << c1_part1.p1.y << "," + << c1_part1.p2.y << "," + << c1_part1.p3.y << ",t), " +#endif +#if 1 + << "bez(" + << c2_part.p0.x+offset << "," + << c2_part.p1.x+offset << "," + << c2_part.p2.x+offset << "," + << c2_part.p3.x+offset << ",t),bez(" + << c2_part.p0.y << "," + << c2_part.p1.y << "," + << c2_part.p2.y << "," + << c2_part.p3.y << ",t), " +#endif + << "linex(" + << bounds_c2.a << "," + << bounds_c2.b << "," + << bounds_c2.c << ",t)+" << offset << ", liney(" + << bounds_c2.a << "," + << bounds_c2.b << "," + << bounds_c2.c << ",t) title \"fat line (center)\", linex(" + << bounds_c2.a << "," + << bounds_c2.b << "," + << bounds_c2.c-bounds_c2.dMin << ",t)+" << offset << ", liney(" + << bounds_c2.a << "," + << bounds_c2.b << "," + << bounds_c2.c-bounds_c2.dMin << ",t) title \"fat line (min) \", linex(" + << bounds_c2.a << "," + << bounds_c2.b << "," + << bounds_c2.c-bounds_c2.dMax << ",t)+" << offset << ", liney(" + << bounds_c2.a << "," + << bounds_c2.b << "," + << bounds_c2.c-bounds_c2.dMax << ",t) title \"fat line (max) \"" << endl; + + offset += 1; +} + +// ----------------------------------------------------------------------------- + +void printResultWithFinalCurves( const Bezier& c1, const Bezier& c1_part, + const Bezier& c2, const Bezier& c2_part, + double t1_c1, double t2_c1 ) +{ + static int offset = 0; + + cout << "# final result" << endl + << "plot [t=0.0:1.0] "; + + cout << "bez(" + << c1.p0.x+offset << "," + << c1.p1.x+offset << "," + << c1.p2.x+offset << "," + << c1.p3.x+offset << ",t),bez(" + << c1.p0.y << "," + << c1.p1.y << "," + << c1.p2.y << "," + << c1.p3.y << ",t), bez(" + << c1_part.p0.x+offset << "," + << c1_part.p1.x+offset << "," + << c1_part.p2.x+offset << "," + << c1_part.p3.x+offset << ",t),bez(" + << c1_part.p0.y << "," + << c1_part.p1.y << "," + << c1_part.p2.y << "," + << c1_part.p3.y << ",t), " + << " pointmarkx(bez(" + << c1.p0.x+offset << "," + << c1.p1.x+offset << "," + << c1.p2.x+offset << "," + << c1.p3.x+offset << "," + << t1_c1 << "),t), " + << " pointmarky(bez(" + << c1.p0.y << "," + << c1.p1.y << "," + << c1.p2.y << "," + << c1.p3.y << "," + << t1_c1 << "),t), " + << " pointmarkx(bez(" + << c1.p0.x+offset << "," + << c1.p1.x+offset << "," + << c1.p2.x+offset << "," + << c1.p3.x+offset << "," + << t2_c1 << "),t), " + << " pointmarky(bez(" + << c1.p0.y << "," + << c1.p1.y << "," + << c1.p2.y << "," + << c1.p3.y << "," + << t2_c1 << "),t), " + + << "bez(" + << c2.p0.x+offset << "," + << c2.p1.x+offset << "," + << c2.p2.x+offset << "," + << c2.p3.x+offset << ",t),bez(" + << c2.p0.y << "," + << c2.p1.y << "," + << c2.p2.y << "," + << c2.p3.y << ",t), " + << "bez(" + << c2_part.p0.x+offset << "," + << c2_part.p1.x+offset << "," + << c2_part.p2.x+offset << "," + << c2_part.p3.x+offset << ",t),bez(" + << c2_part.p0.y << "," + << c2_part.p1.y << "," + << c2_part.p2.y << "," + << c2_part.p3.y << ",t)" << endl; + + offset += 1; +} + +// ----------------------------------------------------------------------------- + +/** determine parameter ranges [0,t1) and (t2,1] on c1, where c1 is guaranteed to lie outside c2. + Returns false, if the two curves don't even intersect. + + @param t1 + Range [0,t1) on c1 is guaranteed to lie outside c2 + + @param t2 + Range (t2,1] on c1 is guaranteed to lie outside c2 + + @param c1_orig + Original curve c1 + + @param c1_part + Subdivided current part of c1 + + @param c2_orig + Original curve c2 + + @param c2_part + Subdivided current part of c2 + */ +bool Impl_calcClipRange( double& t1, + double& t2, + const Bezier& c1_orig, + const Bezier& c1_part, + const Bezier& c2_orig, + const Bezier& c2_part ) +{ + // TODO: Maybe also check fat line orthogonal to P0P3, having P0 + // and P3 as the extremal points + + if( Impl_doBBoxIntersect(c1_part, c2_part) ) + { + // Calculate fat lines around c1 + FatLine bounds_c2; + + // must use the subdivided version of c2, since the fat line + // algorithm works implicitely with the convex hull bounding + // box. + Impl_calcFatLine(bounds_c2, c2_part); + + // determine clip positions on c2. Can use original c1 (which + // is necessary anyway, to get the t's on the original curve), + // since the distance calculations work directly in the + // Bernstein polynom parameter domain. + if( Impl_calcSafeParams_clip( t1, t2, bounds_c2, + calcLineDistance( bounds_c2.a, + bounds_c2.b, + bounds_c2.c, + c1_orig.p0.x, + c1_orig.p0.y ), + calcLineDistance( bounds_c2.a, + bounds_c2.b, + bounds_c2.c, + c1_orig.p1.x, + c1_orig.p1.y ), + calcLineDistance( bounds_c2.a, + bounds_c2.b, + bounds_c2.c, + c1_orig.p2.x, + c1_orig.p2.y ), + calcLineDistance( bounds_c2.a, + bounds_c2.b, + bounds_c2.c, + c1_orig.p3.x, + c1_orig.p3.y ) ) ) + { + //printCurvesWithSafeRange(c1_orig, c2_orig, t1, t2, c2_part, bounds_c2); + + // they do intersect + return true; + } + } + + // they don't intersect: nothing to do + return false; +} + +// ----------------------------------------------------------------------------- + +/* Tangent intersection part + * ========================= + */ + +// ----------------------------------------------------------------------------- + +void Impl_calcFocus( Bezier& res, const Bezier& c ) +{ + // arbitrary small value, for now + // TODO: find meaningful value + const double minPivotValue( 1.0e-20 ); + + Point2D::value_type fMatrix[6]; + Point2D::value_type fRes[2]; + + // calc new curve from hodograph, c and linear blend + + // Coefficients for derivative of c are (C_i=n(C_{i+1} - C_i)): + // + // 3(P1 - P0), 3(P2 - P1), 3(P3 - P2) (bezier curve of degree 2) + // + // The hodograph is then (bezier curve of 2nd degree is P0(1-t)^2 + 2P1(1-t)t + P2t^2): + // + // 3(P1 - P0)(1-t)^2 + 6(P2 - P1)(1-t)t + 3(P3 - P2)t^2 + // + // rotate by 90 degrees: x=-y, y=x and you get the normal vector function N(t): + // + // x(t) = -(3(P1.y - P0.y)(1-t)^2 + 6(P2.y - P1.y)(1-t)t + 3(P3.y - P2.y)t^2) + // y(t) = 3(P1.x - P0.x)(1-t)^2 + 6(P2.x - P1.x)(1-t)t + 3(P3.x - P2.x)t^2 + // + // Now, the focus curve is defined to be F(t)=P(t) + c(t)N(t), + // where P(t) is the original curve, and c(t)=c0(1-t) + c1 t + // + // This results in the following expression for F(t): + // + // x(t) = P0.x (1-t)^3 + 3 P1.x (1-t)^2t + 3 P2.x (1.t)t^2 + P3.x t^3 - + // (c0(1-t) + c1 t)(3(P1.y - P0.y)(1-t)^2 + 6(P2.y - P1.y)(1-t)t + 3(P3.y - P2.y)t^2) + // + // y(t) = P0.y (1-t)^3 + 3 P1.y (1-t)^2t + 3 P2.y (1.t)t^2 + P3.y t^3 + + // (c0(1-t) + c1 t)(3(P1.x - P0.x)(1-t)^2 + 6(P2.x - P1.x)(1-t)t + 3(P3.x - P2.x)t^2) + // + // As a heuristic, we set F(0)=F(1) (thus, the curve is closed and _tends_ to be small): + // + // For F(0), the following results: + // + // x(0) = P0.x - c0 3(P1.y - P0.y) + // y(0) = P0.y + c0 3(P1.x - P0.x) + // + // For F(1), the following results: + // + // x(1) = P3.x - c1 3(P3.y - P2.y) + // y(1) = P3.y + c1 3(P3.x - P2.x) + // + // Reorder, collect and substitute into F(0)=F(1): + // + // P0.x - c0 3(P1.y - P0.y) = P3.x - c1 3(P3.y - P2.y) + // P0.y + c0 3(P1.x - P0.x) = P3.y + c1 3(P3.x - P2.x) + // + // which yields + // + // (P0.y - P1.y)c0 + (P3.y - P2.y)c1 = (P3.x - P0.x)/3 + // (P1.x - P0.x)c0 + (P2.x - P3.x)c1 = (P3.y - P0.y)/3 + // + + // so, this is what we calculate here (determine c0 and c1): + fMatrix[0] = c.p1.x - c.p0.x; + fMatrix[1] = c.p2.x - c.p3.x; + fMatrix[2] = (c.p3.y - c.p0.y)/3.0; + fMatrix[3] = c.p0.y - c.p1.y; + fMatrix[4] = c.p3.y - c.p2.y; + fMatrix[5] = (c.p3.x - c.p0.x)/3.0; + + // TODO: determine meaningful value for + if( !solve(fMatrix, 2, 3, fRes, minPivotValue) ) + { + // TODO: generate meaningful values here + // singular or nearly singular system -- use arbitrary + // values for res + fRes[0] = 0.0; + fRes[1] = 1.0; + + cerr << "Matrix singular!" << endl; + } + + // now, the reordered and per-coefficient collected focus curve is + // the following third degree bezier curve F(t): + // + // x(t) = P0.x (1-t)^3 + 3 P1.x (1-t)^2t + 3 P2.x (1.t)t^2 + P3.x t^3 - + // (c0(1-t) + c1 t)(3(P1.y - P0.y)(1-t)^2 + 6(P2.y - P1.y)(1-t)t + 3(P3.y - P2.y)t^2) + // = P0.x (1-t)^3 + 3 P1.x (1-t)^2t + 3 P2.x (1.t)t^2 + P3.x t^3 - + // (3c0P1.y(1-t)^3 - 3c0P0.y(1-t)^3 + 6c0P2.y(1-t)^2t - 6c0P1.y(1-t)^2t + + // 3c0P3.y(1-t)t^2 - 3c0P2.y(1-t)t^2 + + // 3c1P1.y(1-t)^2t - 3c1P0.y(1-t)^2t + 6c1P2.y(1-t)t^2 - 6c1P1.y(1-t)t^2 + + // 3c1P3.yt^3 - 3c1P2.yt^3) + // = (P0.x - 3 c0 P1.y + 3 c0 P0.y)(1-t)^3 + + // 3(P1.x - c1 P1.y + c1 P0.y - 2 c0 P2.y + 2 c0 P1.y)(1-t)^2t + + // 3(P2.x - 2 c1 P2.y + 2 c1 P1.y - c0 P3.y + c0 P2.y)(1-t)t^2 + + // (P3.x - 3 c1 P3.y + 3 c1 P2.y)t^3 + // = (P0.x - 3 c0(P1.y - P0.y))(1-t)^3 + + // 3(P1.x - c1(P1.y - P0.y) - 2c0(P2.y - P1.y))(1-t)^2t + + // 3(P2.x - 2 c1(P2.y - P1.y) - c0(P3.y - P2.y))(1-t)t^2 + + // (P3.x - 3 c1(P3.y - P2.y))t^3 + // + // y(t) = P0.y (1-t)^3 + 3 P1.y (1-t)^2t + 3 P2.y (1-t)t^2 + P3.y t^3 + + // (c0(1-t) + c1 t)(3(P1.x - P0.x)(1-t)^2 + 6(P2.x - P1.x)(1-t)t + 3(P3.x - P2.x)t^2) + // = P0.y (1-t)^3 + 3 P1.y (1-t)^2t + 3 P2.y (1-t)t^2 + P3.y t^3 + + // 3c0(P1.x - P0.x)(1-t)^3 + 6c0(P2.x - P1.x)(1-t)^2t + 3c0(P3.x - P2.x)(1-t)t^2 + + // 3c1(P1.x - P0.x)(1-t)^2t + 6c1(P2.x - P1.x)(1-t)t^2 + 3c1(P3.x - P2.x)t^3 + // = (P0.y + 3 c0 (P1.x - P0.x))(1-t)^3 + + // 3(P1.y + 2 c0 (P2.x - P1.x) + c1 (P1.x - P0.x))(1-t)^2t + + // 3(P2.y + c0 (P3.x - P2.x) + 2 c1 (P2.x - P1.x))(1-t)t^2 + + // (P3.y + 3 c1 (P3.x - P2.x))t^3 + // + // Therefore, the coefficients F0 to F3 of the focus curve are: + // + // F0.x = (P0.x - 3 c0(P1.y - P0.y)) F0.y = (P0.y + 3 c0 (P1.x - P0.x)) + // F1.x = (P1.x - c1(P1.y - P0.y) - 2c0(P2.y - P1.y)) F1.y = (P1.y + 2 c0 (P2.x - P1.x) + c1 (P1.x - P0.x)) + // F2.x = (P2.x - 2 c1(P2.y - P1.y) - c0(P3.y - P2.y)) F2.y = (P2.y + c0 (P3.x - P2.x) + 2 c1 (P2.x - P1.x)) + // F3.x = (P3.x - 3 c1(P3.y - P2.y)) F3.y = (P3.y + 3 c1 (P3.x - P2.x)) + // + res.p0.x = c.p0.x - 3*fRes[0]*(c.p1.y - c.p0.y); + res.p1.x = c.p1.x - fRes[1]*(c.p1.y - c.p0.y) - 2*fRes[0]*(c.p2.y - c.p1.y); + res.p2.x = c.p2.x - 2*fRes[1]*(c.p2.y - c.p1.y) - fRes[0]*(c.p3.y - c.p2.y); + res.p3.x = c.p3.x - 3*fRes[1]*(c.p3.y - c.p2.y); + + res.p0.y = c.p0.y + 3*fRes[0]*(c.p1.x - c.p0.x); + res.p1.y = c.p1.y + 2*fRes[0]*(c.p2.x - c.p1.x) + fRes[1]*(c.p1.x - c.p0.x); + res.p2.y = c.p2.y + fRes[0]*(c.p3.x - c.p2.x) + 2*fRes[1]*(c.p2.x - c.p1.x); + res.p3.y = c.p3.y + 3*fRes[1]*(c.p3.x - c.p2.x); +} + +// ----------------------------------------------------------------------------- + +bool Impl_calcSafeParams_focus( double& t1, + double& t2, + const Bezier& curve, + const Bezier& focus ) +{ + // now, we want to determine which normals of the original curve + // P(t) intersect with the focus curve F(t). The condition for + // this statement is P'(t)(P(t) - F) = 0, i.e. hodograph P'(t) and + // line through P(t) and F are perpendicular. + // If you expand this equation, you end up with something like + // + // (\sum_{i=0}^n (P_i - F)B_i^n(t))^T (\sum_{j=0}^{n-1} n(P_{j+1} - P_j)B_j^{n-1}(t)) + // + // Multiplying that out (as the scalar product is linear, we can + // extract some terms) yields: + // + // (P_i - F)^T n(P_{j+1} - P_j) B_i^n(t)B_j^{n-1}(t) + ... + // + // If we combine the B_i^n(t)B_j^{n-1}(t) product, we arrive at a + // Bernstein polynomial of degree 2n-1, as + // + // \binom{n}{i}(1-t)^{n-i}t^i) \binom{n-1}{j}(1-t)^{n-1-j}t^j) = + // \binom{n}{i}\binom{n-1}{j}(1-t)^{2n-1-i-j}t^{i+j} + // + // Thus, with the defining equation for a 2n-1 degree Bernstein + // polynomial + // + // \sum_{i=0}^{2n-1} d_i B_i^{2n-1}(t) + // + // the d_i are calculated as follows: + // + // d_i = \sum_{j+k=i, j\in\{0,...,n\}, k\in\{0,...,n-1\}} \frac{\binom{n}{j}\binom{n-1}{k}}{\binom{2n-1}{i}} n (P_{k+1} - P_k)^T(P_j - F) + // + // + // Okay, but F is now not a single point, but itself a curve + // F(u). Thus, for every value of u, we get a different 2n-1 + // bezier curve from the above equation. Therefore, we have a + // tensor product bezier patch, with the following defining + // equation: + // + // d(t,u) = \sum_{i=0}^{2n-1} \sum_{j=0}^m B_i^{2n-1}(t) B_j^{m}(u) d_{ij}, where + // d_{ij} = \sum_{k+l=i, l\in\{0,...,n\}, k\in\{0,...,n-1\}} \frac{\binom{n}{l}\binom{n-1}{k}}{\binom{2n-1}{i}} n (P_{k+1} - P_k)^T(P_l - F_j) + // + // as above, only that now F is one of the focus' control points. + // + // Note the difference in the binomial coefficients to the + // reference paper, these formulas most probably contained a typo. + // + // To determine, where D(t,u) is _not_ zero (these are the parts + // of the curve that don't share normals with the focus and can + // thus be safely clipped away), we project D(u,t) onto the + // (d(t,u), t) plane, determine the convex hull there and proceed + // as for the curve intersection part (projection is orthogonal to + // u axis, thus simply throw away u coordinate). + // + // \fallfac are so-called falling factorials (see Concrete + // Mathematics, p. 47 for a definition). + // + + // now, for tensor product bezier curves, the convex hull property + // holds, too. Thus, we simply project the control points (t_{ij}, + // u_{ij}, d_{ij}) onto the (t,d) plane and calculate the + // intersections of the convex hull with the t axis, as for the + // bezier clipping case. + + // + // calc polygon of control points (t_{ij}, d_{ij}): + // + const int n( 3 ); // cubic bezier curves, as a matter of fact + const int i_card( 2*n ); + const int j_card( n + 1 ); + const int k_max( n-1 ); + Polygon2D controlPolygon( i_card*j_card ); // vector of (t_{ij}, d_{ij}) in row-major order + + int i, j, k, l; // variable notation from formulas above and Sederberg article + Point2D::value_type d; + for( i=0; i<i_card; ++i ) + { + for( j=0; j<j_card; ++j ) + { + // calc single d_{ij} sum: + for( d=0.0, k=::std::max(0,i-n); k<=k_max && k<=i; ++k ) + { + l = i - k; // invariant: k + l = i + assert(k>=0 && k<=n-1); // k \in {0,...,n-1} + assert(l>=0 && l<=n); // l \in {0,...,n} + + // TODO: find, document and assert proper limits for n and int's max_val. + // This becomes important should anybody wants to use + // this code for higher-than-cubic beziers + d += static_cast<double>(fallFac(n,l)*fallFac(n-1,k)*fac(i)) / + static_cast<double>(fac(l)*fac(k) * fallFac(2*n-1,i)) * n * + ( (curve[k+1].x - curve[k].x)*(curve[l].x - focus[j].x) + // dot product here + (curve[k+1].y - curve[k].y)*(curve[l].y - focus[j].y) ); + } + + // Note that the t_{ij} values are evenly spaced on the + // [0,1] interval, thus t_{ij}=i/(2n-1) + controlPolygon[ i*j_card + j ] = Point2D( i/(2.0*n-1.0), d ); + } + } + +#ifndef WITH_SAFEFOCUSPARAM_DETAILED_TEST + + // calc safe parameter range, to determine [0,t1] and [t2,1] where + // no zero crossing is guaranteed. + return Impl_calcSafeParams( t1, t2, controlPolygon, 0.0, 0.0 ); + +#else + bool bRet( Impl_calcSafeParams( t1, t2, controlPolygon, 0.0, 0.0 ) ); + + Polygon2D convHull( convexHull( controlPolygon ) ); + + cout << "# convex hull testing (focus)" << endl + << "plot [t=0:1] "; + cout << "'-' using ($1):($2) title \"control polygon\" with lp, " + << "'-' using ($1):($2) title \"convex hull\" with lp" << endl; + + unsigned int count; + for( count=0; count<controlPolygon.size(); ++count ) + { + cout << controlPolygon[count].x << " " << controlPolygon[count].y << endl; + } + cout << controlPolygon[0].x << " " << controlPolygon[0].y << endl; + cout << "e" << endl; + + for( count=0; count<convHull.size(); ++count ) + { + cout << convHull[count].x << " " << convHull[count].y << endl; + } + cout << convHull[0].x << " " << convHull[0].y << endl; + cout << "e" << endl; + + return bRet; +#endif +} + +// ----------------------------------------------------------------------------- + +/** Calc all values t_i on c1, for which safeRanges functor does not + give a safe range on c1 and c2. + + This method is the workhorse of the bezier clipping. Because c1 + and c2 must be alternatingly tested against each other (first + determine safe parameter interval on c1 with regard to c2, then + the other way around), we call this method recursively with c1 and + c2 swapped. + + @param result + Output iterator where the final t values are added to. If curves + don't intersect, nothing is added. + + @param delta + Maximal allowed distance to true critical point (measured in the + original curve's coordinate system) + + @param safeRangeFunctor + Functor object, that must provide the following operator(): + bool safeRangeFunctor( double& t1, + double& t2, + const Bezier& c1_orig, + const Bezier& c1_part, + const Bezier& c2_orig, + const Bezier& c2_part ); + This functor must calculate the safe ranges [0,t1] and [t2,1] on + c1_orig, where c1_orig is 'safe' from c2_part. If the whole + c1_orig is safe, false must be returned, true otherwise. + */ +template <class Functor> void Impl_applySafeRanges_rec( ::std::back_insert_iterator< ::std::vector< ::std::pair<double, double> > >& result, + double delta, + const Functor& safeRangeFunctor, + int recursionLevel, + const Bezier& c1_orig, + const Bezier& c1_part, + double last_t1_c1, + double last_t2_c1, + const Bezier& c2_orig, + const Bezier& c2_part, + double last_t1_c2, + double last_t2_c2 ) +{ + // check end condition + // =================== + + // TODO: tidy up recursion handling. maybe put everything in a + // struct and swap that here at method entry + + // TODO: Implement limit on recursion depth. Should that limit be + // reached, chances are that we're on a higher-order tangency. For + // this case, AW proposed to take the middle of the current + // interval, and to correct both curve's tangents at that new + // endpoint to be equal. That virtually generates a first-order + // tangency, and justifies to return a single intersection + // point. Otherwise, inside/outside test might fail here. + + for( int i=0; i<recursionLevel; ++i ) cerr << " "; + if( recursionLevel % 2 ) + { + cerr << "level: " << recursionLevel + << " t: " + << last_t1_c2 + (last_t2_c2 - last_t1_c2)/2.0 + << ", c1: " << last_t1_c2 << " " << last_t2_c2 + << ", c2: " << last_t1_c1 << " " << last_t2_c1 + << endl; + } + else + { + cerr << "level: " << recursionLevel + << " t: " + << last_t1_c1 + (last_t2_c1 - last_t1_c1)/2.0 + << ", c1: " << last_t1_c1 << " " << last_t2_c1 + << ", c2: " << last_t1_c2 << " " << last_t2_c2 + << endl; + } + + // refine solution + // =============== + + double t1_c1, t2_c1; + + // Note: we first perform the clipping and only test for precision + // sufficiency afterwards, since we want to exploit the fact that + // Impl_calcClipRange returns false if the curves don't + // intersect. We would have to check that separately for the end + // condition, otherwise. + + // determine safe range on c1_orig + if( safeRangeFunctor( t1_c1, t2_c1, c1_orig, c1_part, c2_orig, c2_part ) ) + { + // now, t1 and t2 are calculated on the original curve + // (but against a fat line calculated from the subdivided + // c2, namely c2_part). If the [t1,t2] range is outside + // our current [last_t1,last_t2] range, we're done in this + // branch - the curves no longer intersect. + if( tolLessEqual(t1_c1, last_t2_c1) && tolGreaterEqual(t2_c1, last_t1_c1) ) + { + // As noted above, t1 and t2 are calculated on the + // original curve, but against a fat line + // calculated from the subdivided c2, namely + // c2_part. Our domain to work on is + // [last_t1,last_t2], on the other hand, so values + // of [t1,t2] outside that range are irrelevant + // here. Clip range appropriately. + t1_c1 = ::std::max(t1_c1, last_t1_c1); + t2_c1 = ::std::min(t2_c1, last_t2_c1); + + // TODO: respect delta + // for now, end condition is just a fixed threshold on the t's + + // check end condition + // =================== + +#if 1 + if( fabs(last_t2_c1 - last_t1_c1) < 0.0001 && + fabs(last_t2_c2 - last_t1_c2) < 0.0001 ) +#else + if( fabs(last_t2_c1 - last_t1_c1) < 0.01 && + fabs(last_t2_c2 - last_t1_c2) < 0.01 ) +#endif + { + // done. Add to result + if( recursionLevel % 2 ) + { + // uneven level: have to swap the t's, since curves are swapped, too + *result++ = ::std::make_pair( last_t1_c2 + (last_t2_c2 - last_t1_c2)/2.0, + last_t1_c1 + (last_t2_c1 - last_t1_c1)/2.0 ); + } + else + { + *result++ = ::std::make_pair( last_t1_c1 + (last_t2_c1 - last_t1_c1)/2.0, + last_t1_c2 + (last_t2_c2 - last_t1_c2)/2.0 ); + } + +#if 0 + //printResultWithFinalCurves( c1_orig, c1_part, c2_orig, c2_part, last_t1_c1, last_t2_c1 ); + printResultWithFinalCurves( c1_orig, c1_part, c2_orig, c2_part, t1_c1, t2_c1 ); +#else + // calc focus curve of c2 + Bezier focus; + Impl_calcFocus(focus, c2_part); // need to use subdivided c2 + + safeRangeFunctor( t1_c1, t2_c1, c1_orig, c1_part, c2_orig, c2_part ); + + //printResultWithFinalCurves( c1_orig, c1_part, c2_orig, focus, t1_c1, t2_c1 ); + printResultWithFinalCurves( c1_orig, c1_part, c2_orig, focus, last_t1_c1, last_t2_c1 ); +#endif + } + else + { + // heuristic: if parameter range is not reduced by at least + // 20%, subdivide longest curve, and clip shortest against + // both parts of longest +// if( (last_t2_c1 - last_t1_c1 - t2_c1 + t1_c1) / (last_t2_c1 - last_t1_c1) < 0.2 ) + if( false ) + { + // subdivide and descend + // ===================== + + Bezier part1; + Bezier part2; + + double intervalMiddle; + + if( last_t2_c1 - last_t1_c1 > last_t2_c2 - last_t1_c2 ) + { + // subdivide c1 + // ============ + + intervalMiddle = last_t1_c1 + (last_t2_c1 - last_t1_c1)/2.0; + + // subdivide at the middle of the interval (as + // we're not subdividing on the original + // curve, this simply amounts to subdivision + // at 0.5) + Impl_deCasteljauAt( part1, part2, c1_part, 0.5 ); + + // and descend recursively with swapped curves + Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1, + c2_orig, c2_part, last_t1_c2, last_t2_c2, + c1_orig, part1, last_t1_c1, intervalMiddle ); + + Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1, + c2_orig, c2_part, last_t1_c2, last_t2_c2, + c1_orig, part2, intervalMiddle, last_t2_c1 ); + } + else + { + // subdivide c2 + // ============ + + intervalMiddle = last_t1_c2 + (last_t2_c2 - last_t1_c2)/2.0; + + // subdivide at the middle of the interval (as + // we're not subdividing on the original + // curve, this simply amounts to subdivision + // at 0.5) + Impl_deCasteljauAt( part1, part2, c2_part, 0.5 ); + + // and descend recursively with swapped curves + Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1, + c2_orig, part1, last_t1_c2, intervalMiddle, + c1_orig, c1_part, last_t1_c1, last_t2_c1 ); + + Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1, + c2_orig, part2, intervalMiddle, last_t2_c2, + c1_orig, c1_part, last_t1_c1, last_t2_c1 ); + } + } + else + { + // apply calculated clip + // ===================== + + // clip safe ranges off c1_orig + Bezier c1_part1; + Bezier c1_part2; + Bezier c1_part3; + + // subdivide at t1_c1 + Impl_deCasteljauAt( c1_part1, c1_part2, c1_orig, t1_c1 ); + + // subdivide at t2_c1. As we're working on + // c1_part2 now, we have to adapt t2_c1 since + // we're no longer in the original parameter + // interval. This is based on the following + // assumption: t2_new = (t2-t1)/(1-t1), which + // relates the t2 value into the new parameter + // range [0,1] of c1_part2. + Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (t2_c1-t1_c1)/(1.0-t1_c1) ); + + // descend with swapped curves and c1_part1 as the + // remaining (middle) segment + Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1, + c2_orig, c2_part, last_t1_c2, last_t2_c2, + c1_orig, c1_part1, t1_c1, t2_c1 ); + } + } + } + } +} + +// ----------------------------------------------------------------------------- + +struct ClipBezierFunctor +{ + bool operator()( double& t1_c1, + double& t2_c1, + const Bezier& c1_orig, + const Bezier& c1_part, + const Bezier& c2_orig, + const Bezier& c2_part ) const + { + return Impl_calcClipRange( t1_c1, t2_c1, c1_orig, c1_part, c2_orig, c2_part ); + } +}; + +// ----------------------------------------------------------------------------- + +struct BezierTangencyFunctor +{ + bool operator()( double& t1_c1, + double& t2_c1, + const Bezier& c1_orig, + const Bezier& c1_part, + const Bezier& c2_orig, + const Bezier& c2_part ) const + { + // calc focus curve of c2 + Bezier focus; + Impl_calcFocus(focus, c2_part); // need to use subdivided c2 + // here, as the whole curve is + // used for focus calculation + + // determine safe range on c1_orig + bool bRet( Impl_calcSafeParams_focus( t1_c1, t2_c1, + c1_orig, // use orig curve here, need t's on original curve + focus ) ); + + cerr << "range: " << t2_c1 - t1_c1 << ", ret: " << bRet << endl; + + return bRet; + } +}; + +// ----------------------------------------------------------------------------- + +/** Perform a bezier clip (curve against curve) + + @param result + Output iterator where the final t values are added to. This + iterator will remain empty, if there are no intersections. + + @param delta + Maximal allowed distance to true intersection (measured in the + original curve's coordinate system) + */ +void clipBezier( ::std::back_insert_iterator< ::std::vector< ::std::pair<double, double> > >& result, + double delta, + const Bezier& c1, + const Bezier& c2 ) +{ +#if 0 + // first of all, determine list of collinear normals. Collinear + // normals typically separate two intersections, thus, subdivide + // at all collinear normal's t values beforehand. This will cater + // for tangent intersections, where two or more intersections are + // infinitesimally close together. + + // TODO: evaluate effects of higher-than-second-order + // tangencies. Sederberg et al. state that collinear normal + // algorithm then degrades quickly. + + ::std::vector< ::std::pair<double,double> > results; + ::std::back_insert_iterator< ::std::vector< ::std::pair<double, double> > > ii(results); + + Impl_calcCollinearNormals( ii, delta, 0, c1, c1, 0.0, 1.0, c2, c2, 0.0, 1.0 ); + + // As Sederberg's collinear normal theorem is only sufficient, not + // necessary for two intersections left and right, we've to test + // all segments generated by the collinear normal algorithm + // against each other. In other words, if the two curves are both + // divided in a left and a right part, the collinear normal + // theorem does _not_ state that the left part of curve 1 does not + // e.g. intersect with the right part of curve 2. + + // divide c1 and c2 at collinear normal intersection points + ::std::vector< Bezier > c1_segments( results.size()+1 ); + ::std::vector< Bezier > c2_segments( results.size()+1 ); + Bezier c1_remainder( c1 ); + Bezier c2_remainder( c2 ); + unsigned int i; + for( i=0; i<results.size(); ++i ) + { + Bezier c1_part2; + Impl_deCasteljauAt( c1_segments[i], c1_part2, c1_remainder, results[i].first ); + c1_remainder = c1_part2; + + Bezier c2_part2; + Impl_deCasteljauAt( c2_segments[i], c2_part2, c2_remainder, results[i].second ); + c2_remainder = c2_part2; + } + c1_segments[i] = c1_remainder; + c2_segments[i] = c2_remainder; + + // now, c1/c2_segments contain all segments, then + // clip every resulting segment against every other + unsigned int c1_curr, c2_curr; + for( c1_curr=0; c1_curr<c1_segments.size(); ++c1_curr ) + { + for( c2_curr=0; c2_curr<c2_segments.size(); ++c2_curr ) + { + if( c1_curr != c2_curr ) + { + Impl_clipBezier_rec(result, delta, 0, + c1_segments[c1_curr], c1_segments[c1_curr], + 0.0, 1.0, + c2_segments[c2_curr], c2_segments[c2_curr], + 0.0, 1.0); + } + } + } +#else + Impl_applySafeRanges_rec( result, delta, BezierTangencyFunctor(), 0, c1, c1, 0.0, 1.0, c2, c2, 0.0, 1.0 ); + //Impl_applySafeRanges_rec( result, delta, ClipBezierFunctor(), 0, c1, c1, 0.0, 1.0, c2, c2, 0.0, 1.0 ); +#endif + // that's it, boys'n'girls! +} + +int main(int argc, const char *argv[]) +{ + double curr_Offset( 0 ); + unsigned int i,j,k; + + Bezier someCurves[] = + { +// {Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0),Point2D(1.0,0.0)}, +// {Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0),Point2D(1.0,0.5)}, +// {Point2D(1.0,0.0),Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0)} +// {Point2D(0.25+1,0.5),Point2D(0.25+1,0.708333),Point2D(0.423611+1,0.916667),Point2D(0.770833+1,0.980324)}, +// {Point2D(0.0+1,0.0),Point2D(0.0+1,1.0),Point2D(1.0+1,1.0),Point2D(1.0+1,0.5)} + +// tangency1 +// {Point2D(0.627124+1,0.828427),Point2D(0.763048+1,0.828507),Point2D(0.885547+1,0.77312),Point2D(0.950692+1,0.67325)}, +// {Point2D(0.0,1.0),Point2D(0.1,1.0),Point2D(0.4,1.0),Point2D(0.5,1.0)} + +// {Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0),Point2D(1.0,0.5)}, +// {Point2D(0.60114,0.933091),Point2D(0.69461,0.969419),Point2D(0.80676,0.992976),Point2D(0.93756,0.998663)} +// {Point2D(1.0,0.0),Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0)}, +// {Point2D(0.62712,0.828427),Point2D(0.76305,0.828507),Point2D(0.88555,0.77312),Point2D(0.95069,0.67325)} + +// clipping1 +// {Point2D(0.0,0.0),Point2D(0.0,3.5),Point2D(1.0,-2.5),Point2D(1.0,1.0)}, +// {Point2D(0.0,1.0),Point2D(3.5,1.0),Point2D(-2.5,0.0),Point2D(1.0,0.0)} + +// tangency2 +// {Point2D(0.0,1.0),Point2D(3.5,1.0),Point2D(-2.5,0.0),Point2D(1.0,0.0)}, +// {Point2D(15.3621,0.00986464),Point2D(15.3683,0.0109389),Point2D(15.3682,0.0109315),Point2D(15.3621,0.00986464)} + +// tangency3 +// {Point2D(1.0,0.0),Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0)}, +// {Point2D(-0.5,0.0),Point2D(0.5,0.0),Point2D(0.5,1.0),Point2D(-0.5,1.0)} + +// tangency4 +// {Point2D(-0.5,0.0),Point2D(0.5,0.0),Point2D(0.5,1.0),Point2D(-0.5,1.0)}, +// {Point2D(0.26,0.4),Point2D(0.25,0.5),Point2D(0.25,0.5),Point2D(0.26,0.6)} + +// tangency5 +// {Point2D(0.0,0.0),Point2D(0.0,3.5),Point2D(1.0,-2.5),Point2D(1.0,1.0)}, +// {Point2D(15.3621,0.00986464),Point2D(15.3683,0.0109389),Point2D(15.3682,0.0109315),Point2D(15.3621,0.00986464)} + +// tangency6 +// {Point2D(0.0,0.0),Point2D(0.0,3.5),Point2D(1.0,-2.5),Point2D(1.0,1.0)}, +// {Point2D(15.3621,10.00986464),Point2D(15.3683,10.0109389),Point2D(15.3682,10.0109315),Point2D(15.3621,10.00986464)} + +// tangency7 +// {Point2D(2.505,0.0),Point2D(2.505+4.915,4.300),Point2D(2.505+3.213,10.019),Point2D(2.505-2.505,10.255)}, +// {Point2D(15.3621,10.00986464),Point2D(15.3683,10.0109389),Point2D(15.3682,10.0109315),Point2D(15.3621,10.00986464)} + +// tangency Sederberg example + {Point2D(2.505,0.0),Point2D(2.505+4.915,4.300),Point2D(2.505+3.213,10.019),Point2D(2.505-2.505,10.255)}, + {Point2D(5.33+9.311,0.0),Point2D(5.33+9.311-13.279,4.205),Point2D(5.33+9.311-10.681,9.119),Point2D(5.33+9.311-2.603,10.254)} + +// clipping2 +// {Point2D(-0.5,0.0),Point2D(0.5,0.0),Point2D(0.5,1.0),Point2D(-0.5,1.0)}, +// {Point2D(0.2575,0.4),Point2D(0.2475,0.5),Point2D(0.2475,0.5),Point2D(0.2575,0.6)} + +// {Point2D(0.0,0.1),Point2D(0.2,3.5),Point2D(1.0,-2.5),Point2D(1.1,1.2)}, +// {Point2D(0.0,1.0),Point2D(3.5,0.9),Point2D(-2.5,0.1),Point2D(1.1,0.2)} +// {Point2D(0.0,0.1),Point2D(0.2,3.0),Point2D(1.0,-2.0),Point2D(1.1,1.2)}, +// {Point2D(0.627124+1,0.828427),Point2D(0.763048+1,0.828507),Point2D(0.885547+1,0.77312),Point2D(0.950692+1,0.67325)} +// {Point2D(0.0,1.0),Point2D(3.0,0.9),Point2D(-2.0,0.1),Point2D(1.1,0.2)} +// {Point2D(0.0,4.0),Point2D(0.1,5.0),Point2D(0.9,5.0),Point2D(1.0,4.0)}, +// {Point2D(0.0,0.0),Point2D(0.1,0.5),Point2D(0.9,0.5),Point2D(1.0,0.0)}, +// {Point2D(0.0,0.1),Point2D(0.1,1.5),Point2D(0.9,1.5),Point2D(1.0,0.1)}, +// {Point2D(0.0,-4.0),Point2D(0.1,-5.0),Point2D(0.9,-5.0),Point2D(1.0,-4.0)} + }; + + // output gnuplot setup + cout << "#!/usr/bin/gnuplot -persist" << endl + << "#" << endl + << "# automatically generated by bezierclip, don't change!" << endl + << "#" << endl + << "set parametric" << endl + << "bez(p,q,r,s,t) = p*(1-t)**3+q*3*(1-t)**2*t+r*3*(1-t)*t**2+s*t**3" << endl + << "bezd(p,q,r,s,t) = 3*(q-p)*(1-t)**2+6*(r-q)*(1-t)*t+3*(s-r)*t**2" << endl + << "pointmarkx(c,t) = c-0.03*t" << endl + << "pointmarky(c,t) = c+0.03*t" << endl + << "linex(a,b,c,t) = a*-c + t*-b" << endl + << "liney(a,b,c,t) = b*-c + t*a" << endl << endl + << "# end of setup" << endl << endl; + +#ifdef WITH_CONVEXHULL_TEST + // test convex hull algorithm + const double convHull_xOffset( curr_Offset ); + curr_Offset += 20; + cout << "# convex hull testing" << endl + << "plot [t=0:1] "; + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + Polygon2D aTestPoly(4); + aTestPoly[0] = someCurves[i].p0; + aTestPoly[1] = someCurves[i].p1; + aTestPoly[2] = someCurves[i].p2; + aTestPoly[3] = someCurves[i].p3; + + aTestPoly[0].x += convHull_xOffset; + aTestPoly[1].x += convHull_xOffset; + aTestPoly[2].x += convHull_xOffset; + aTestPoly[3].x += convHull_xOffset; + + cout << " bez(" + << aTestPoly[0].x << "," + << aTestPoly[1].x << "," + << aTestPoly[2].x << "," + << aTestPoly[3].x << ",t),bez(" + << aTestPoly[0].y << "," + << aTestPoly[1].y << "," + << aTestPoly[2].y << "," + << aTestPoly[3].y << ",t), '-' using ($1):($2) title \"convex hull " << i << "\" with lp"; + + if( i+1<sizeof(someCurves)/sizeof(Bezier) ) + cout << ",\\" << endl; + else + cout << endl; + } + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + Polygon2D aTestPoly(4); + aTestPoly[0] = someCurves[i].p0; + aTestPoly[1] = someCurves[i].p1; + aTestPoly[2] = someCurves[i].p2; + aTestPoly[3] = someCurves[i].p3; + + aTestPoly[0].x += convHull_xOffset; + aTestPoly[1].x += convHull_xOffset; + aTestPoly[2].x += convHull_xOffset; + aTestPoly[3].x += convHull_xOffset; + + Polygon2D convHull( convexHull(aTestPoly) ); + + for( k=0; k<convHull.size(); ++k ) + { + cout << convHull[k].x << " " << convHull[k].y << endl; + } + cout << convHull[0].x << " " << convHull[0].y << endl; + cout << "e" << endl; + } +#endif + +#ifdef WITH_MULTISUBDIVIDE_TEST + // test convex hull algorithm + const double multiSubdivide_xOffset( curr_Offset ); + curr_Offset += 20; + cout << "# multi subdivide testing" << endl + << "plot [t=0:1] "; + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + Bezier c( someCurves[i] ); + Bezier c1_part1; + Bezier c1_part2; + Bezier c1_part3; + + c.p0.x += multiSubdivide_xOffset; + c.p1.x += multiSubdivide_xOffset; + c.p2.x += multiSubdivide_xOffset; + c.p3.x += multiSubdivide_xOffset; + + const double t1( 0.1+i/(3.0*sizeof(someCurves)/sizeof(Bezier)) ); + const double t2( 0.9-i/(3.0*sizeof(someCurves)/sizeof(Bezier)) ); + + // subdivide at t1 + Impl_deCasteljauAt( c1_part1, c1_part2, c, t1 ); + + // subdivide at t2_c1. As we're working on + // c1_part2 now, we have to adapt t2_c1 since + // we're no longer in the original parameter + // interval. This is based on the following + // assumption: t2_new = (t2-t1)/(1-t1), which + // relates the t2 value into the new parameter + // range [0,1] of c1_part2. + Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (t2-t1)/(1.0-t1) ); + + // subdivide at t2 + Impl_deCasteljauAt( c1_part3, c1_part2, c, t2 ); + + cout << " bez(" + << c1_part1.p0.x << "," + << c1_part1.p1.x << "," + << c1_part1.p2.x << "," + << c1_part1.p3.x << ",t), bez(" + << c1_part1.p0.y+0.01 << "," + << c1_part1.p1.y+0.01 << "," + << c1_part1.p2.y+0.01 << "," + << c1_part1.p3.y+0.01 << ",t) title \"middle " << i << "\", " + << " bez(" + << c1_part2.p0.x << "," + << c1_part2.p1.x << "," + << c1_part2.p2.x << "," + << c1_part2.p3.x << ",t), bez(" + << c1_part2.p0.y << "," + << c1_part2.p1.y << "," + << c1_part2.p2.y << "," + << c1_part2.p3.y << ",t) title \"right " << i << "\", " + << " bez(" + << c1_part3.p0.x << "," + << c1_part3.p1.x << "," + << c1_part3.p2.x << "," + << c1_part3.p3.x << ",t), bez(" + << c1_part3.p0.y << "," + << c1_part3.p1.y << "," + << c1_part3.p2.y << "," + << c1_part3.p3.y << ",t) title \"left " << i << "\""; + + + if( i+1<sizeof(someCurves)/sizeof(Bezier) ) + cout << ",\\" << endl; + else + cout << endl; + } +#endif + +#ifdef WITH_FATLINE_TEST + // test fatline algorithm + const double fatLine_xOffset( curr_Offset ); + curr_Offset += 20; + cout << "# fat line testing" << endl + << "plot [t=0:1] "; + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + Bezier c( someCurves[i] ); + + c.p0.x += fatLine_xOffset; + c.p1.x += fatLine_xOffset; + c.p2.x += fatLine_xOffset; + c.p3.x += fatLine_xOffset; + + FatLine line; + + Impl_calcFatLine(line, c); + + cout << " bez(" + << c.p0.x << "," + << c.p1.x << "," + << c.p2.x << "," + << c.p3.x << ",t), bez(" + << c.p0.y << "," + << c.p1.y << "," + << c.p2.y << "," + << c.p3.y << ",t) title \"bezier " << i << "\", linex(" + << line.a << "," + << line.b << "," + << line.c << ",t), liney(" + << line.a << "," + << line.b << "," + << line.c << ",t) title \"fat line (center) on " << i << "\", linex(" + << line.a << "," + << line.b << "," + << line.c-line.dMin << ",t), liney(" + << line.a << "," + << line.b << "," + << line.c-line.dMin << ",t) title \"fat line (min) on " << i << "\", linex(" + << line.a << "," + << line.b << "," + << line.c-line.dMax << ",t), liney(" + << line.a << "," + << line.b << "," + << line.c-line.dMax << ",t) title \"fat line (max) on " << i << "\""; + + if( i+1<sizeof(someCurves)/sizeof(Bezier) ) + cout << ",\\" << endl; + else + cout << endl; + } +#endif + +#ifdef WITH_CALCFOCUS_TEST + // test focus curve algorithm + const double focus_xOffset( curr_Offset ); + curr_Offset += 20; + cout << "# focus line testing" << endl + << "plot [t=0:1] "; + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + Bezier c( someCurves[i] ); + + c.p0.x += focus_xOffset; + c.p1.x += focus_xOffset; + c.p2.x += focus_xOffset; + c.p3.x += focus_xOffset; + + // calc focus curve + Bezier focus; + Impl_calcFocus(focus, c); + + cout << " bez(" + << c.p0.x << "," + << c.p1.x << "," + << c.p2.x << "," + << c.p3.x << ",t), bez(" + << c.p0.y << "," + << c.p1.y << "," + << c.p2.y << "," + << c.p3.y << ",t) title \"bezier " << i << "\", bez(" + << focus.p0.x << "," + << focus.p1.x << "," + << focus.p2.x << "," + << focus.p3.x << ",t), bez(" + << focus.p0.y << "," + << focus.p1.y << "," + << focus.p2.y << "," + << focus.p3.y << ",t) title \"focus " << i << "\""; + + + if( i+1<sizeof(someCurves)/sizeof(Bezier) ) + cout << ",\\" << endl; + else + cout << endl; + } +#endif + +#ifdef WITH_SAFEPARAMBASE_TEST + // test safe params base method + double safeParamsBase_xOffset( curr_Offset ); + cout << "# safe param base method testing" << endl + << "plot [t=0:1] "; + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + Bezier c( someCurves[i] ); + + c.p0.x += safeParamsBase_xOffset; + c.p1.x += safeParamsBase_xOffset; + c.p2.x += safeParamsBase_xOffset; + c.p3.x += safeParamsBase_xOffset; + + Polygon2D poly(4); + poly[0] = c.p0; + poly[1] = c.p1; + poly[2] = c.p2; + poly[3] = c.p3; + + double t1, t2; + + bool bRet( Impl_calcSafeParams( t1, t2, poly, 0, 1 ) ); + + Polygon2D convHull( convexHull( poly ) ); + + cout << " bez(" + << poly[0].x << "," + << poly[1].x << "," + << poly[2].x << "," + << poly[3].x << ",t),bez(" + << poly[0].y << "," + << poly[1].y << "," + << poly[2].y << "," + << poly[3].y << ",t), " + << "t+" << safeParamsBase_xOffset << ", 0, " + << "t+" << safeParamsBase_xOffset << ", 1, "; + if( bRet ) + { + cout << t1+safeParamsBase_xOffset << ", t, " + << t2+safeParamsBase_xOffset << ", t, "; + } + cout << "'-' using ($1):($2) title \"control polygon\" with lp, " + << "'-' using ($1):($2) title \"convex hull\" with lp"; + + if( i+1<sizeof(someCurves)/sizeof(Bezier) ) + cout << ",\\" << endl; + else + cout << endl; + + safeParamsBase_xOffset += 2; + } + + safeParamsBase_xOffset = curr_Offset; + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + Bezier c( someCurves[i] ); + + c.p0.x += safeParamsBase_xOffset; + c.p1.x += safeParamsBase_xOffset; + c.p2.x += safeParamsBase_xOffset; + c.p3.x += safeParamsBase_xOffset; + + Polygon2D poly(4); + poly[0] = c.p0; + poly[1] = c.p1; + poly[2] = c.p2; + poly[3] = c.p3; + + double t1, t2; + + Impl_calcSafeParams( t1, t2, poly, 0, 1 ); + + Polygon2D convHull( convexHull( poly ) ); + + unsigned int k; + for( k=0; k<poly.size(); ++k ) + { + cout << poly[k].x << " " << poly[k].y << endl; + } + cout << poly[0].x << " " << poly[0].y << endl; + cout << "e" << endl; + + for( k=0; k<convHull.size(); ++k ) + { + cout << convHull[k].x << " " << convHull[k].y << endl; + } + cout << convHull[0].x << " " << convHull[0].y << endl; + cout << "e" << endl; + + safeParamsBase_xOffset += 2; + } + curr_Offset += 20; +#endif + +#ifdef WITH_SAFEPARAMS_TEST + // test safe parameter range algorithm + const double safeParams_xOffset( curr_Offset ); + curr_Offset += 20; + cout << "# safe param range testing" << endl + << "plot [t=0.0:1.0] "; + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + for( j=i+1; j<sizeof(someCurves)/sizeof(Bezier); ++j ) + { + Bezier c1( someCurves[i] ); + Bezier c2( someCurves[j] ); + + c1.p0.x += safeParams_xOffset; + c1.p1.x += safeParams_xOffset; + c1.p2.x += safeParams_xOffset; + c1.p3.x += safeParams_xOffset; + c2.p0.x += safeParams_xOffset; + c2.p1.x += safeParams_xOffset; + c2.p2.x += safeParams_xOffset; + c2.p3.x += safeParams_xOffset; + + double t1, t2; + + if( Impl_calcClipRange(t1, t2, c1, c1, c2, c2) ) + { + // clip safe ranges off c1 + Bezier c1_part1; + Bezier c1_part2; + Bezier c1_part3; + + // subdivide at t1_c1 + Impl_deCasteljauAt( c1_part1, c1_part2, c1, t1 ); + // subdivide at t2_c1 + Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (t2-t1)/(1.0-t1) ); + + // output remaining segment (c1_part1) + + cout << " bez(" + << c1.p0.x << "," + << c1.p1.x << "," + << c1.p2.x << "," + << c1.p3.x << ",t),bez(" + << c1.p0.y << "," + << c1.p1.y << "," + << c1.p2.y << "," + << c1.p3.y << ",t), bez(" + << c2.p0.x << "," + << c2.p1.x << "," + << c2.p2.x << "," + << c2.p3.x << ",t),bez(" + << c2.p0.y << "," + << c2.p1.y << "," + << c2.p2.y << "," + << c2.p3.y << ",t), bez(" + << c1_part1.p0.x << "," + << c1_part1.p1.x << "," + << c1_part1.p2.x << "," + << c1_part1.p3.x << ",t),bez(" + << c1_part1.p0.y << "," + << c1_part1.p1.y << "," + << c1_part1.p2.y << "," + << c1_part1.p3.y << ",t)"; + + if( i+2<sizeof(someCurves)/sizeof(Bezier) ) + cout << ",\\" << endl; + else + cout << endl; + } + } + } +#endif + +#ifdef WITH_SAFEPARAM_DETAILED_TEST + // test safe parameter range algorithm + const double safeParams2_xOffset( curr_Offset ); + curr_Offset += 20; + if( sizeof(someCurves)/sizeof(Bezier) > 1 ) + { + Bezier c1( someCurves[0] ); + Bezier c2( someCurves[1] ); + + c1.p0.x += safeParams2_xOffset; + c1.p1.x += safeParams2_xOffset; + c1.p2.x += safeParams2_xOffset; + c1.p3.x += safeParams2_xOffset; + c2.p0.x += safeParams2_xOffset; + c2.p1.x += safeParams2_xOffset; + c2.p2.x += safeParams2_xOffset; + c2.p3.x += safeParams2_xOffset; + + double t1, t2; + + // output happens here + Impl_calcClipRange(t1, t2, c1, c1, c2, c2); + } +#endif + +#ifdef WITH_SAFEFOCUSPARAM_TEST + // test safe parameter range from focus algorithm + const double safeParamsFocus_xOffset( curr_Offset ); + curr_Offset += 20; + cout << "# safe param range from focus testing" << endl + << "plot [t=0.0:1.0] "; + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + for( j=i+1; j<sizeof(someCurves)/sizeof(Bezier); ++j ) + { + Bezier c1( someCurves[i] ); + Bezier c2( someCurves[j] ); + + c1.p0.x += safeParamsFocus_xOffset; + c1.p1.x += safeParamsFocus_xOffset; + c1.p2.x += safeParamsFocus_xOffset; + c1.p3.x += safeParamsFocus_xOffset; + c2.p0.x += safeParamsFocus_xOffset; + c2.p1.x += safeParamsFocus_xOffset; + c2.p2.x += safeParamsFocus_xOffset; + c2.p3.x += safeParamsFocus_xOffset; + + double t1, t2; + + Bezier focus; +#ifdef WITH_SAFEFOCUSPARAM_CALCFOCUS +#if 0 + { + // clip safe ranges off c1_orig + Bezier c1_part1; + Bezier c1_part2; + Bezier c1_part3; + + // subdivide at t1_c1 + Impl_deCasteljauAt( c1_part1, c1_part2, c2, 0.30204 ); + + // subdivide at t2_c1. As we're working on + // c1_part2 now, we have to adapt t2_c1 since + // we're no longer in the original parameter + // interval. This is based on the following + // assumption: t2_new = (t2-t1)/(1-t1), which + // relates the t2 value into the new parameter + // range [0,1] of c1_part2. + Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (0.57151-0.30204)/(1.0-0.30204) ); + + c2 = c1_part1; + Impl_calcFocus( focus, c2 ); + } +#else + Impl_calcFocus( focus, c2 ); +#endif +#else + focus = c2; +#endif + // determine safe range on c1 + bool bRet( Impl_calcSafeParams_focus( t1, t2, + c1, focus ) ); + + cerr << "t1: " << t1 << ", t2: " << t2 << endl; + + // clip safe ranges off c1 + Bezier c1_part1; + Bezier c1_part2; + Bezier c1_part3; + + // subdivide at t1_c1 + Impl_deCasteljauAt( c1_part1, c1_part2, c1, t1 ); + // subdivide at t2_c1 + Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (t2-t1)/(1.0-t1) ); + + // output remaining segment (c1_part1) + + cout << " bez(" + << c1.p0.x << "," + << c1.p1.x << "," + << c1.p2.x << "," + << c1.p3.x << ",t),bez(" + << c1.p0.y << "," + << c1.p1.y << "," + << c1.p2.y << "," + << c1.p3.y << ",t) title \"c1\", " +#ifdef WITH_SAFEFOCUSPARAM_CALCFOCUS + << "bez(" + << c2.p0.x << "," + << c2.p1.x << "," + << c2.p2.x << "," + << c2.p3.x << ",t),bez(" + << c2.p0.y << "," + << c2.p1.y << "," + << c2.p2.y << "," + << c2.p3.y << ",t) title \"c2\", " + << "bez(" + << focus.p0.x << "," + << focus.p1.x << "," + << focus.p2.x << "," + << focus.p3.x << ",t),bez(" + << focus.p0.y << "," + << focus.p1.y << "," + << focus.p2.y << "," + << focus.p3.y << ",t) title \"focus\""; +#else + << "bez(" + << c2.p0.x << "," + << c2.p1.x << "," + << c2.p2.x << "," + << c2.p3.x << ",t),bez(" + << c2.p0.y << "," + << c2.p1.y << "," + << c2.p2.y << "," + << c2.p3.y << ",t) title \"focus\""; +#endif + if( bRet ) + { + cout << ", bez(" + << c1_part1.p0.x << "," + << c1_part1.p1.x << "," + << c1_part1.p2.x << "," + << c1_part1.p3.x << ",t),bez(" + << c1_part1.p0.y+0.01 << "," + << c1_part1.p1.y+0.01 << "," + << c1_part1.p2.y+0.01 << "," + << c1_part1.p3.y+0.01 << ",t) title \"part\""; + } + + if( i+2<sizeof(someCurves)/sizeof(Bezier) ) + cout << ",\\" << endl; + else + cout << endl; + } + } +#endif + +#ifdef WITH_SAFEFOCUSPARAM_DETAILED_TEST + // test safe parameter range algorithm + const double safeParams3_xOffset( curr_Offset ); + curr_Offset += 20; + if( sizeof(someCurves)/sizeof(Bezier) > 1 ) + { + Bezier c1( someCurves[0] ); + Bezier c2( someCurves[1] ); + + c1.p0.x += safeParams3_xOffset; + c1.p1.x += safeParams3_xOffset; + c1.p2.x += safeParams3_xOffset; + c1.p3.x += safeParams3_xOffset; + c2.p0.x += safeParams3_xOffset; + c2.p1.x += safeParams3_xOffset; + c2.p2.x += safeParams3_xOffset; + c2.p3.x += safeParams3_xOffset; + + double t1, t2; + + Bezier focus; +#ifdef WITH_SAFEFOCUSPARAM_CALCFOCUS + Impl_calcFocus( focus, c2 ); +#else + focus = c2; +#endif + + // determine safe range on c1, output happens here + Impl_calcSafeParams_focus( t1, t2, + c1, focus ); + } +#endif + +#ifdef WITH_BEZIERCLIP_TEST + ::std::vector< ::std::pair<double, double> > result; + ::std::back_insert_iterator< ::std::vector< ::std::pair<double, double> > > ii(result); + + // test full bezier clipping + const double bezierClip_xOffset( curr_Offset ); + curr_Offset += 20; + cout << endl << endl << "# bezier clip testing" << endl + << "plot [t=0:1] "; + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + for( j=i+1; j<sizeof(someCurves)/sizeof(Bezier); ++j ) + { + Bezier c1( someCurves[i] ); + Bezier c2( someCurves[j] ); + + c1.p0.x += bezierClip_xOffset; + c1.p1.x += bezierClip_xOffset; + c1.p2.x += bezierClip_xOffset; + c1.p3.x += bezierClip_xOffset; + c2.p0.x += bezierClip_xOffset; + c2.p1.x += bezierClip_xOffset; + c2.p2.x += bezierClip_xOffset; + c2.p3.x += bezierClip_xOffset; + + cout << " bez(" + << c1.p0.x << "," + << c1.p1.x << "," + << c1.p2.x << "," + << c1.p3.x << ",t),bez(" + << c1.p0.y << "," + << c1.p1.y << "," + << c1.p2.y << "," + << c1.p3.y << ",t), bez(" + << c2.p0.x << "," + << c2.p1.x << "," + << c2.p2.x << "," + << c2.p3.x << ",t),bez(" + << c2.p0.y << "," + << c2.p1.y << "," + << c2.p2.y << "," + << c2.p3.y << ",t), '-' using (bez(" + << c1.p0.x << "," + << c1.p1.x << "," + << c1.p2.x << "," + << c1.p3.x + << ",$1)):(bez(" + << c1.p0.y << "," + << c1.p1.y << "," + << c1.p2.y << "," + << c1.p3.y << ",$1)) title \"bezier " << i << " clipped against " << j << " (t on " << i << ")\", " + << " '-' using (bez(" + << c2.p0.x << "," + << c2.p1.x << "," + << c2.p2.x << "," + << c2.p3.x + << ",$1)):(bez(" + << c2.p0.y << "," + << c2.p1.y << "," + << c2.p2.y << "," + << c2.p3.y << ",$1)) title \"bezier " << i << " clipped against " << j << " (t on " << j << ")\""; + + if( i+2<sizeof(someCurves)/sizeof(Bezier) ) + cout << ",\\" << endl; + else + cout << endl; + } + } + for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i ) + { + for( j=i+1; j<sizeof(someCurves)/sizeof(Bezier); ++j ) + { + result.clear(); + Bezier c1( someCurves[i] ); + Bezier c2( someCurves[j] ); + + c1.p0.x += bezierClip_xOffset; + c1.p1.x += bezierClip_xOffset; + c1.p2.x += bezierClip_xOffset; + c1.p3.x += bezierClip_xOffset; + c2.p0.x += bezierClip_xOffset; + c2.p1.x += bezierClip_xOffset; + c2.p2.x += bezierClip_xOffset; + c2.p3.x += bezierClip_xOffset; + + clipBezier( ii, 0.00001, c1, c2 ); + + for( k=0; k<result.size(); ++k ) + { + cout << result[k].first << endl; + } + cout << "e" << endl; + + for( k=0; k<result.size(); ++k ) + { + cout << result[k].second << endl; + } + cout << "e" << endl; + } + } +#endif + + return 0; +} diff --git a/basegfx/source/workbench/bezierclip.hxx b/basegfx/source/workbench/bezierclip.hxx new file mode 100644 index 000000000000..ca16ad0fdd44 --- /dev/null +++ b/basegfx/source/workbench/bezierclip.hxx @@ -0,0 +1,93 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef BASEGFX_BEZIERCLIP_HXX +#define BASEGFX_BEZIERCLIP_HXX + +#include <vector> + +struct Point2D +{ + typedef double value_type; + Point2D( double _x, double _y ) : x(_x), y(_y) {} + Point2D() : x(), y() {} + double x; + double y; +}; + +struct Bezier +{ + Point2D p0; + Point2D p1; + Point2D p2; + Point2D p3; + + Point2D& operator[]( int i ) { return reinterpret_cast<Point2D*>(this)[i]; } + const Point2D& operator[]( int i ) const { return reinterpret_cast<const Point2D*>(this)[i]; } +}; + +struct FatLine +{ + // line L through p1 and p4 in normalized implicit form + double a; + double b; + double c; + + // the upper and lower distance from this line + double dMin; + double dMax; +}; + +template <typename DataType> DataType calcLineDistance( const DataType& a, + const DataType& b, + const DataType& c, + const DataType& x, + const DataType& y ) +{ + return a*x + b*y + c; +} + +typedef ::std::vector< Point2D > Polygon2D; + +/* little abs template */ +template <typename NumType> NumType absval( NumType x ) +{ + return x<0 ? -x : x; +} + +Polygon2D convexHull( const Polygon2D& rPoly ); + +// TODO: find proper epsilon here (try ::std::numeric_limits<NumType>::epsilon()?)! +#define DBL_EPSILON 1.0e-100 + +/* little approximate comparions */ +template <typename NumType> bool tolZero( NumType n ) { return fabs(n) < DBL_EPSILON; } +template <typename NumType> bool tolEqual( NumType n1, NumType n2 ) { return tolZero(n1-n2); } +template <typename NumType> bool tolLessEqual( NumType n1, NumType n2 ) { return tolEqual(n1,n2) || n1<n2; } +template <typename NumType> bool tolGreaterEqual( NumType n1, NumType n2 ) { return tolEqual(n1,n2) || n1>n2; } + +#endif /* BASEGFX_BEZIERCLIP_HXX */ diff --git a/basegfx/source/workbench/convexhull.cxx b/basegfx/source/workbench/convexhull.cxx new file mode 100644 index 000000000000..99d9fb86c1d9 --- /dev/null +++ b/basegfx/source/workbench/convexhull.cxx @@ -0,0 +1,213 @@ +/************************************************************************* + * + * 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 <algorithm> +#include <functional> +#include <vector> + +#include "bezierclip.hxx" + +// ----------------------------------------------------------------------------- + +/* Implements the theta function from Sedgewick: Algorithms in XXX, chapter 24 */ +template <class PointType> double theta( const PointType& p1, const PointType& p2 ) +{ + typename PointType::value_type dx, dy, ax, ay; + double t; + + dx = p2.x - p1.x; ax = absval( dx ); + dy = p2.y - p1.y; ay = absval( dy ); + t = (ax+ay == 0) ? 0 : (double) dy/(ax+ay); + if( dx < 0 ) + t = 2-t; + else if( dy < 0 ) + t = 4+t; + + return t*90.0; +} + +/* Model of LessThanComparable for theta sort. + * Uses the theta function from Sedgewick: Algorithms in XXX, chapter 24 + */ +template <class PointType> class ThetaCompare : public ::std::binary_function< const PointType&, const PointType&, bool > +{ +public: + ThetaCompare( const PointType& rRefPoint ) : maRefPoint( rRefPoint ) {} + + bool operator() ( const PointType& p1, const PointType& p2 ) + { + // return true, if p1 precedes p2 in the angle relative to maRefPoint + return theta(maRefPoint, p1) < theta(maRefPoint, p2); + } + + double operator() ( const PointType& p ) const + { + return theta(maRefPoint, p); + } + +private: + PointType maRefPoint; +}; + +/* Implementation of the predicate 'counter-clockwise' for three points, from Sedgewick: Algorithms in XXX, chapter 24 */ +template <class PointType, class CmpFunctor> typename PointType::value_type ccw( const PointType& p0, const PointType& p1, const PointType& p2, CmpFunctor& thetaCmp ) +{ + typename PointType::value_type dx1, dx2, dy1, dy2; + typename PointType::value_type theta0( thetaCmp(p0) ); + typename PointType::value_type theta1( thetaCmp(p1) ); + typename PointType::value_type theta2( thetaCmp(p2) ); + +#if 0 + if( theta0 == theta1 || + theta0 == theta2 || + theta1 == theta2 ) + { + // cannot reliably compare, as at least two points are + // theta-equal. See bug description below + return 0; + } +#endif + + dx1 = p1.x - p0.x; dy1 = p1.y - p0.y; + dx2 = p2.x - p0.x; dy2 = p2.y - p0.y; + + if( dx1*dy2 > dy1*dx2 ) + return +1; + + if( dx1*dy2 < dy1*dx2 ) + return -1; + + if( (dx1*dx2 < 0) || (dy1*dy2 < 0) ) + return -1; + + if( (dx1*dx1 + dy1*dy1) < (dx2*dx2 + dy2*dy2) ) + return +1; + + return 0; +} + +/* + Bug + === + + Sometimes, the resulting polygon is not the convex hull (see below + for an edge configuration to reproduce that problem) + + Problem analysis: + ================= + + The root cause of this bug is the fact that the second part of + the algorithm (the 'wrapping' of the point set) relies on the + previous theta sorting. Namely, it is required that the + generated point ordering, when interpreted as a polygon, is not + self-intersecting. This property, although, cannot be + guaranteed due to limited floating point accuracy. For example, + for two points very close together, and at the same time very + far away from the theta reference point p1, can appear on the + same theta value (because floating point accuracy is limited), + although on different rays to p1 when inspected locally. + + Example: + + / + P3 / + |\ / + | / + |/ \ + P2 \ + \ + ...____________\ + P1 + + Here, P2 and P3 are theta-equal relative to P1, but the local + ccw measure always says 'left turn'. Thus, the convex hull is + wrong at this place. + + Solution: + ========= + + If two points are theta-equal and checked via ccw, ccw must + also classify them as 'equal'. Thus, the second stage of the + convex hull algorithm sorts the first one out, effectively + reducing a cluster of theta-equal points to only one. This + single point can then be treated correctly. +*/ + + +/* Implementation of Graham's convex hull algorithm, see Sedgewick: Algorithms in XXX, chapter 25 */ +Polygon2D convexHull( const Polygon2D& rPoly ) +{ + const Polygon2D::size_type N( rPoly.size() ); + Polygon2D result( N + 1 ); + ::std::copy(rPoly.begin(), rPoly.end(), result.begin()+1 ); + Polygon2D::size_type min, i; + + // determine safe point on hull (smallest y value) + for( min=1, i=2; i<=N; ++i ) + { + if( result[i].y < result[min].y ) + min = i; + } + + // determine safe point on hull (largest x value) + for( i=1; i<=N; ++i ) + { + if( result[i].y == result[min].y && + result[i].x > result[min].x ) + { + min = i; + } + } + + // TODO: add inner elimination optimization from Sedgewick: Algorithms in XXX, chapter 25 + // TODO: use radix sort instead of ::std::sort(), calc theta only once and store + + // setup first point and sort + ::std::swap( result[1], result[min] ); + ThetaCompare<Point2D> cmpFunc(result[1]); + ::std::sort( result.begin()+1, result.end(), cmpFunc ); + + // setup sentinel + result[0] = result[N]; + + // generate convex hull + Polygon2D::size_type M; + for( M=3, i=4; i<=N; ++i ) + { + while( ccw(result[M], result[M-1], result[i], cmpFunc) >= 0 ) + --M; + + ++M; + ::std::swap( result[M], result[i] ); + } + + // copy range [1,M] to output + return Polygon2D( result.begin()+1, result.begin()+M+1 ); +} diff --git a/basegfx/source/workbench/gauss.hxx b/basegfx/source/workbench/gauss.hxx new file mode 100644 index 000000000000..63910c6ded2d --- /dev/null +++ b/basegfx/source/workbench/gauss.hxx @@ -0,0 +1,172 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +/** This method eliminates elements below main diagonal in the given + matrix by gaussian elimination. + + @param matrix + The matrix to operate on. Last column is the result vector (right + hand side of the linear equation). After successful termination, + the matrix is upper triangular. The matrix is expected to be in + row major order. + + @param rows + Number of rows in matrix + + @param cols + Number of columns in matrix + + @param minPivot + If the pivot element gets lesser than minPivot, this method fails, + otherwise, elimination succeeds and true is returned. + + @return true, if elimination succeeded. + */ +template <class Matrix, typename BaseType> +bool eliminate( Matrix& matrix, + int rows, + int cols, + const BaseType& minPivot ) +{ + BaseType temp; + int max, i, j, k; /* *must* be signed, when looping like: j>=0 ! */ + + /* eliminate below main diagonal */ + for(i=0; i<cols-1; ++i) + { + /* find best pivot */ + max = i; + for(j=i+1; j<rows; ++j) + if( fabs(matrix[ j*cols + i ]) > fabs(matrix[ max*cols + i ]) ) + max = j; + + /* check pivot value */ + if( fabs(matrix[ max*cols + i ]) < minPivot ) + return false; /* pivot too small! */ + + /* interchange rows 'max' and 'i' */ + for(k=0; k<cols; ++k) + { + temp = matrix[ i*cols + k ]; + matrix[ i*cols + k ] = matrix[ max*cols + k ]; + matrix[ max*cols + k ] = temp; + } + + /* eliminate column */ + for(j=i+1; j<rows; ++j) + for(k=cols-1; k>=i; --k) + matrix[ j*cols + k ] -= matrix[ i*cols + k ] * + matrix[ j*cols + i ] / matrix[ i*cols + i ]; + } + + /* everything went well */ + return true; +} + + +/** Retrieve solution vector of linear system by substituting backwards. + + This operation _relies_ on the previous successful + application of eliminate()! + + @param matrix + Matrix in upper diagonal form, as e.g. generated by eliminate() + + @param rows + Number of rows in matrix + + @param cols + Number of columns in matrix + + @param result + Result vector. Given matrix must have space for one column (rows entries). + + @return true, if back substitution was possible (i.e. no division + by zero occured). + */ +template <class Matrix, class Vector, typename BaseType> +bool substitute( const Matrix& matrix, + int rows, + int cols, + Vector& result ) +{ + BaseType temp; + int j,k; /* *must* be signed, when looping like: j>=0 ! */ + + /* substitute backwards */ + for(j=rows-1; j>=0; --j) + { + temp = 0.0; + for(k=j+1; k<cols-1; ++k) + temp += matrix[ j*cols + k ] * result[k]; + + if( matrix[ j*cols + j ] == 0.0 ) + return false; /* imminent division by zero! */ + + result[j] = (matrix[ j*cols + cols-1 ] - temp) / matrix[ j*cols + j ]; + } + + /* everything went well */ + return true; +} + + +/** This method determines solution of given linear system, if any + + This is a wrapper for eliminate and substitute, given matrix must + contain right side of equation as the last column. + + @param matrix + The matrix to operate on. Last column is the result vector (right + hand side of the linear equation). After successful termination, + the matrix is upper triangular. The matrix is expected to be in + row major order. + + @param rows + Number of rows in matrix + + @param cols + Number of columns in matrix + + @param minPivot + If the pivot element gets lesser than minPivot, this method fails, + otherwise, elimination succeeds and true is returned. + + @return true, if elimination succeeded. + */ +template <class Matrix, class Vector, typename BaseType> +bool solve( Matrix& matrix, + int rows, + int cols, + Vector& result, + BaseType minPivot ) +{ + if( eliminate<Matrix,BaseType>(matrix, rows, cols, minPivot) ) + return substitute<Matrix,Vector,BaseType>(matrix, rows, cols, result); + + return false; +} diff --git a/basegfx/test/basegfx1d.cxx b/basegfx/test/basegfx1d.cxx new file mode 100644 index 000000000000..9b189bd8b236 --- /dev/null +++ b/basegfx/test/basegfx1d.cxx @@ -0,0 +1,75 @@ +/************************************************************************* + * + * 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" +// autogenerated file with codegen.pl + +#include "preextstl.h" +#include "cppunit/TestAssert.h" +#include "cppunit/TestFixture.h" +#include "cppunit/extensions/HelperMacros.h" +#include "cppunit/plugin/TestPlugIn.h" +#include "postextstl.h" + +namespace basegfx1d +{ + +class b1drange : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + // this is only demonstration code + void EmptyMethod() + { + // CPPUNIT_ASSERT_MESSAGE("a message", 1 == 1); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b1drange); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b1drange + +// ----------------------------------------------------------------------------- +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx1d::b1drange); +} // namespace basegfx1d + +CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/basegfx/test/basegfx2d.cxx b/basegfx/test/basegfx2d.cxx new file mode 100644 index 000000000000..53501d190c03 --- /dev/null +++ b/basegfx/test/basegfx2d.cxx @@ -0,0 +1,1463 @@ +/************************************************************************* + * + * 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" +// autogenerated file with codegen.pl + +#include "preextstl.h" +#include "cppunit/TestAssert.h" +#include "cppunit/TestFixture.h" +#include "cppunit/extensions/HelperMacros.h" +#include "postextstl.h" + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <basegfx/curve/b2dbeziertools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/range/b2dpolyrange.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/color/bcolortools.hxx> + +#include <basegfx/tools/debugplotter.hxx> + +#include <iostream> +#include <fstream> + +using namespace ::basegfx; + + +namespace basegfx2d +{ + +class b2dsvgdimpex : public CppUnit::TestFixture +{ +private: + ::rtl::OUString aPath0; + ::rtl::OUString aPath1; + ::rtl::OUString aPath2; + ::rtl::OUString aPath3; + +public: + // initialise your test code values here. + void setUp() + { + // simple rectangle + aPath0 = ::rtl::OUString::createFromAscii( + "M 10 10-10 10-10-10 10-10Z" ); + + // simple bezier polygon + aPath1 = ::rtl::OUString::createFromAscii( + "m11430 0c-8890 3810 5715 6985 5715 6985 " + "0 0-17145-1905-17145-1905 0 0 22860-10160 " + "16510 6350-6350 16510-3810-11430-3810-11430z" ); + + // '@' as a bezier polygon + aPath2 = ::rtl::OUString::createFromAscii( + "m1917 1114c-89-189-233-284-430-284-167 " + "0-306 91-419 273-113 182-170 370-170 564 " + "0 145 33 259 98 342 65 84 150 126 257 126 " + "77 0 154-19 231-57 77-38 147-97 210-176 63" + "-79 99-143 109-190 38-199 76-398 114-598z" + "m840 1646c-133 73-312 139-537 197-225 57" + "-440 86-644 87-483-1-866-132-1150-392-284" + "-261-426-619-426-1076 0-292 67-560 200-803 " + "133-243 321-433 562-569 241-136 514-204 821" + "-204 405 0 739 125 1003 374 264 250 396 550 " + "396 899 0 313-88 576-265 787-177 212-386 318" + "-627 318-191 0-308-94-352-281-133 187-315 281" + "-546 281-172 0-315-67-428-200-113-133-170-301" + "-170-505 0-277 90-527 271-751 181-223 394" + "-335 640-335 196 0 353 83 470 250 13-68 26" + "-136 41-204 96 0 192 0 288 0-74 376-148 752" + "-224 1128-21 101-31 183-31 245 0 39 9 70 26 " + "93 17 24 39 36 67 36 145 0 279-80 400-240 121" + "-160 182-365 182-615 0-288-107-533-322-734" + "-215-201-487-301-816-301-395 0-715 124-960 " + "373-245 249-368 569-368 958 0 385 119 685 " + "357 900 237 216 557 324 958 325 189-1 389-27 " + "600-77 211-52 378-110 503-174 27 70 54 140 81 210z" ); + + // first part of 'Hello World' as a line polygon + aPath3 = ::rtl::OUString::createFromAscii( + "m1598 125h306v2334h-306v-1105h-1293v1105h-305v" + "-2334h305v973h1293zm2159 1015 78-44 85 235-91 " + "47-91 40-90 34-90 29-89 21-88 16-88 10-88 3-102" + "-4-97-12-91-19-85-26-40-16-39-18-38-20-36-22-34" + "-24-33-26-32-27-30-30-29-31-27-33-25-33-23-36-21" + "-36-19-38-18-40-16-40-26-86-18-91-11-97-4-103 3" + "-98 11-94 17-89 24-84 31-79 37-75 22-35 23-34 24" + "-33 27-32 28-30 29-28 31-27 31-24 33-22 34-21 35" + "-18 37-17 38-14 38-13 41-11 41-8 86-12 91-4 82 4 " + "78 10 37 9 37 9 36 12 35 14 33 15 33 17 32 19 31 " + "21 30 22 30 25 55 54 26 29 24 31 22 32 21 33 19 " + "34 18 36 30 74 23 80 17 84 10 89 3 94v78h-1277l6 " + "75 10 70 14 66 19 62 23 57 13 26 14 26 15 25 17 " + "23 17 22 19 21 19 20 21 18 21 18 23 16 23 14 24 " + "14 26 12 26 11 27 10 28 8 59 13 63 7 67 3 80-3 81" + "-9 79-14 80-21 78-26 79-32zm-1049-808-12 53h963l" + "-7-51-11-49-14-46-17-43-21-40-24-38-27-36-31-32" + "-33-29-35-25-37-22-38-17-40-14-41-9-42-6-44-2-48 " + "2-46 6-44 9-42 13-40 17-38 21-36 24-34 28-32 32" + "-29 34-26 38-23 41-20 44-17 47zm1648-1293h288v" + "2459h-288zm752-2459h288v2459h-288zm1286-1750 86-11 " + "91-4 91 4 85 12 42 8 39 11 39 13 38 14 36 17 35 18 " + "34 20 33 23 31 24 30 26 29 28 28 30 26 32 25 33 23 " + "34 21 35 37 75 31 80 24 84 16 90 11 94 3 100-3 100" + "-11 95-16 89-24 85-31 80-37 74-21 35-23 35-25 32-26 " + "32-28 30-29 28-30 26-31 24-33 22-34 21-35 18-36 17" + "-38 14-39 13-39 10-42 9-85 12-91 4-91-4-86-12-41-9" + "-40-10-39-13-37-14-36-17-35-18-34-21-33-22-31-24-30" + "-26-29-28-28-30-26-32-25-32-23-35-21-35-38-74-30-80" + "-24-85-17-89-11-95-3-100 3-101 11-95 17-90 24-85 30" + "-79 38-75 21-35 23-35 25-32 26-32 28-30 29-28 30-26 " + "31-24 33-22 34-20 35-18 36-16 37-15 39-12 40-11z" ); + } + + void tearDown() + { + } + + void impex() + { + B2DPolyPolygon aPoly; + ::rtl::OUString aExport; + + CPPUNIT_ASSERT_MESSAGE("importing simple rectangle from SVG-D", + tools::importFromSvgD( aPoly, + aPath0 )); + aExport = tools::exportToSvgD( aPoly ); + const char* sExportString = "m10 10h-20v-20h20z"; + CPPUNIT_ASSERT_MESSAGE("exporting rectangle to SVG-D", + !aExport.compareToAscii(sExportString) ); + CPPUNIT_ASSERT_MESSAGE("importing simple rectangle from SVG-D (round-trip", + tools::importFromSvgD( aPoly, + aExport )); + aExport = tools::exportToSvgD( aPoly ); + CPPUNIT_ASSERT_MESSAGE("exporting rectangle to SVG-D (round-trip)", + !aExport.compareToAscii(sExportString)); + + CPPUNIT_ASSERT_MESSAGE("importing simple bezier polygon from SVG-D", + tools::importFromSvgD( aPoly, + aPath1 )); + aExport = tools::exportToSvgD( aPoly ); + + // Adaptions for B2DPolygon bezier change (see #i77162#): + // + // The import/export of aPath1 does not reproduce aExport again. This is + // correct since aPath1 contains a segment with non-used control points + // which gets exported now correctly as 'l' and also a point (#4, index 3) + // with C2 continuity which produces a 's' staement now. + // + // The old SVGexport identified nun-used ControlVectors erraneously as bezier segments + // because the 2nd vector at the start point was used, even when added + // with start point was identical to end point. Exactly for that reason + // i reworked the B2DPolygon to use prev, next control points. + // + // so for correct unit test i add the new exported string here as sExportStringSimpleBezier + // and compare to it. + const char* sExportStringSimpleBezier = + "m11430 0c-8890 3810 5715 6985 5715 6985" + "l-17145-1905c0 0 22860-10160 16510 6350" + "s-3810-11430-3810-11430z"; + CPPUNIT_ASSERT_MESSAGE("exporting bezier polygon to SVG-D", !aExport.compareToAscii(sExportStringSimpleBezier)); + + // Adaptions for B2DPolygon bezier change (see #i77162#): + // + // a 2nd good test is that re-importing of aExport has to create the same + // B2DPolPolygon again: + B2DPolyPolygon aReImport; + CPPUNIT_ASSERT_MESSAGE("importing simple bezier polygon from SVG-D", tools::importFromSvgD( aReImport, aExport)); + CPPUNIT_ASSERT_MESSAGE("re-imported polygon needs to be identical", aReImport == aPoly); + + CPPUNIT_ASSERT_MESSAGE("importing '@' from SVG-D", tools::importFromSvgD( aPoly, aPath2 )); + aExport = tools::exportToSvgD( aPoly ); + + // Adaptions for B2DPolygon bezier change (see #i77162#): + // + // same here, the corrected export with the corrected B2DPolygon is simply more efficient, + // so i needed to change the compare string. Also adding the re-import comparison below. + const char* sExportString1 = + "m1917 1114c-89-189-233-284-430-284-167 0-306 91-419 273s-170 370-17" + "0 564c0 145 33 259 98 342 65 84 150 126 257 126q115.5 0 231-57s147-97 210-176 99-143 109-190c38-199 76-398 114" + "-598zm840 1646c-133 73-312 139-537 197-225 57-440 86-644 87-483-1-866-132-1150-392-284-261-426-619-426-1076 0-" + "292 67-560 200-803s321-433 562-569 514-204 821-204c405 0 739 125 1003 374 264 250 396 550 396 899 0 313-88 576" + "-265 787q-265.5 318-627 318c-191 0-308-94-352-281-133 187-315 281-546 281-172 0-315-67-428-200s-170-301-170-50" + "5c0-277 90-527 271-751 181-223 394-335 640-335 196 0 353 83 470 250 13-68 26-136 41-204q144 0 288 0c-74 376-14" + "8 752-224 1128-21 101-31 183-31 245 0 39 9 70 26 93 17 24 39 36 67 36 145 0 279-80 400-240s182-365 182-615c0-2" + "88-107-533-322-734s-487-301-816-301c-395 0-715 124-960 373s-368 569-368 958q0 577.5 357 900c237 216 557 324 95" + "8 325 189-1 389-27 600-77 211-52 378-110 503-174q40.5 105 81 210z"; + CPPUNIT_ASSERT_MESSAGE("re-importing '@' from SVG-D", tools::importFromSvgD( aReImport, aExport)); + CPPUNIT_ASSERT_MESSAGE("re-imported '@' needs to be identical", aReImport == aPoly); + + CPPUNIT_ASSERT_MESSAGE("exporting '@' to SVG-D", !aExport.compareToAscii(sExportString1)); + CPPUNIT_ASSERT_MESSAGE("importing '@' from SVG-D (round-trip", + tools::importFromSvgD( aPoly, + aExport )); + aExport = tools::exportToSvgD( aPoly ); + CPPUNIT_ASSERT_MESSAGE("exporting '@' to SVG-D (round-trip)", + !aExport.compareToAscii(sExportString1)); + + + CPPUNIT_ASSERT_MESSAGE("importing complex polygon from SVG-D", + tools::importFromSvgD( aPoly, + aPath3 )); + aExport = tools::exportToSvgD( aPoly ); + const char* sExportString2 = + "m1598 125h306v2334h-306v-1105h-1293v1105h-305v-2334h305v973h1293" + "zm2159 1015 78-44 85 235-91 47-91 40-90 34-90 29-89 21-88 16-88 10-88 3-102-4-97" + "-12-91-19-85-26-40-16-39-18-38-20-36-22-34-24-33-26-32-27-30-30-29-31-27-33-25-3" + "3-23-36-21-36-19-38-18-40-16-40-26-86-18-91-11-97-4-103 3-98 11-94 17-89 24-84 3" + "1-79 37-75 22-35 23-34 24-33 27-32 28-30 29-28 31-27 31-24 33-22 34-21 35-18 37-" + "17 38-14 38-13 41-11 41-8 86-12 91-4 82 4 78 10 37 9 37 9 36 12 35 14 33 15 33 1" + "7 32 19 31 21 30 22 30 25 55 54 26 29 24 31 22 32 21 33 19 34 18 36 30 74 23 80 " + "17 84 10 89 3 94v78h-1277l6 75 10 70 14 66 19 62 23 57 13 26 14 26 15 25 17 23 1" + "7 22 19 21 19 20 21 18 21 18 23 16 23 14 24 14 26 12 26 11 27 10 28 8 59 13 63 7" + " 67 3 80-3 81-9 79-14 80-21 78-26 79-32zm-1049-808-12 53h963l-7-51-11-49-14-46-1" + "7-43-21-40-24-38-27-36-31-32-33-29-35-25-37-22-38-17-40-14-41-9-42-6-44-2-48 2-4" + "6 6-44 9-42 13-40 17-38 21-36 24-34 28-32 32-29 34-26 38-23 41-20 44-17 47zm1648" + "-1293h288v2459h-288zm752-2459h288v2459h-288zm1286-1750 86-11 91-4 91 4 85 12 42 " + "8 39 11 39 13 38 14 36 17 35 18 34 20 33 23 31 24 30 26 29 28 28 30 26 32 25 33 " + "23 34 21 35 37 75 31 80 24 84 16 90 11 94 3 100-3 100-11 95-16 89-24 85-31 80-37" + " 74-21 35-23 35-25 32-26 32-28 30-29 28-30 26-31 24-33 22-34 21-35 18-36 17-38 1" + "4-39 13-39 10-42 9-85 12-91 4-91-4-86-12-41-9-40-10-39-13-37-14-36-17-35-18-34-2" + "1-33-22-31-24-30-26-29-28-28-30-26-32-25-32-23-35-21-35-38-74-30-80-24-85-17-89-" + "11-95-3-100 3-101 11-95 17-90 24-85 30-79 38-75 21-35 23-35 25-32 26-32 28-30 29" + "-28 30-26 31-24 33-22 34-20 35-18 36-16 37-15 39-12 40-11z"; + CPPUNIT_ASSERT_MESSAGE("exporting complex polygon to SVG-D", + !aExport.compareToAscii(sExportString2)); + CPPUNIT_ASSERT_MESSAGE("importing complex polygon from SVG-D (round-trip", + tools::importFromSvgD( aPoly, + aExport )); + aExport = tools::exportToSvgD( aPoly ); + CPPUNIT_ASSERT_MESSAGE("exporting complex polygon to SVG-D (round-trip)", + !aExport.compareToAscii(sExportString2)); + + const B2DPolygon aRect( + tools::createPolygonFromRect( B2DRange(0.0,0.0,4000.0,4000.0) )); + aExport = tools::exportToSvgD( B2DPolyPolygon(aRect), false, false); + + const char* sExportStringRect = "M0 0H4000V4000H0Z"; + CPPUNIT_ASSERT_MESSAGE("exporting to rectangle svg-d string", + !aExport.compareToAscii(sExportStringRect)); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dsvgdimpex); + CPPUNIT_TEST(impex); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dsvgdimpex + +class b2dpolyrange : public CppUnit::TestFixture +{ +private: +public: + void setUp() + {} + + void tearDown() + {} + + void check() + { + B2DPolyRange aRange; + aRange.appendElement(B2DRange(0,0,1,1),ORIENTATION_POSITIVE); + aRange.appendElement(B2DRange(2,2,3,3),ORIENTATION_POSITIVE); + + CPPUNIT_ASSERT_MESSAGE("simple poly range - count", + aRange.count() == 2); + CPPUNIT_ASSERT_MESSAGE("simple poly range - first element", + aRange.getElement(0).head == B2DRange(0,0,1,1)); + CPPUNIT_ASSERT_MESSAGE("simple poly range - second element", + aRange.getElement(1).head == B2DRange(2,2,3,3)); + + // B2DPolyRange relies on correctly orientated rects + const B2DRange aRect(0,0,1,1); + CPPUNIT_ASSERT_MESSAGE("createPolygonFromRect - correct orientation", + tools::getOrientation( + tools::createPolygonFromRect(aRect)) == ORIENTATION_POSITIVE ); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dpolyrange); + CPPUNIT_TEST(check); + CPPUNIT_TEST_SUITE_END(); +}; + +class b2dbeziertools : public CppUnit::TestFixture +{ +private: + B2DCubicBezier aHalfCircle; // not exactly, but a look-alike + B2DCubicBezier aQuarterCircle; // not exactly, but a look-alike + B2DCubicBezier aLoop; // identical endpoints, curve goes back to where it started + B2DCubicBezier aStraightLineDistinctEndPoints; // truly a line + B2DCubicBezier aStraightLineDistinctEndPoints2; // truly a line, with slightly different control points + B2DCubicBezier aStraightLineIdenticalEndPoints; // degenerate case of aLoop + B2DCubicBezier aStraightLineIdenticalEndPoints2;// degenerate case of aLoop, with slightly different control points + B2DCubicBezier aCrossing; // curve self-intersects somewhere in the middle + B2DCubicBezier aCusp; // curve has a point of undefined tangency + + +public: + // initialise your test code values here. + void setUp() + { + const B2DPoint a00(0.0, 0.0); + const B2DPoint a10(1.0, 0.0); + const B2DPoint a11(1.0, 1.0); + const B2DPoint a01(0.0, 1.0); + const B2DPoint middle( 0.5, 0.5 ); + const B2DPoint quarterDown( 0.25, 0.25 ); + const B2DPoint quarterUp( 0.75, 0.75 ); + + aHalfCircle = B2DCubicBezier(a00, a01, a11, a10); + + // The spline control points become + // + // (R * cos(A), R * sin(A)) + // (R * cos(A) - h * sin(A), R * sin(A) + h * cos (A)) + // (R * cos(B) + h * sin(B), R * sin(B) - h * cos (B)) + // (R * cos(B), R * sin(B)) + // + // where h = 4/3 * R * tan ((B-A)/4) + // + // with R being the radius, A start angle and B end angle (A < B). + // + // (This calculation courtesy Carl Worth, himself based on + // Michael Goldapp and Dokken/Daehlen) + + // Choosing R=1, A=0, B=pi/2 + const double h( 4.0/3.0 * tan(M_PI/8.0) ); + aQuarterCircle = B2DCubicBezier(a10 + B2DPoint(1.0,0.0), + B2DPoint(B2DPoint( 1.0, h ) + B2DPoint(1.0,0.0)), + B2DPoint(B2DPoint( h, 1.0) + B2DPoint(1.0,0.0)), + a01 + B2DPoint(1.0,0.0)); + + aCusp = B2DCubicBezier(a00 + B2DPoint(2.0,0.0), + B2DPoint(a11 + B2DPoint(2.0,0.0)), + B2DPoint(a01 + B2DPoint(2.0,0.0)), + a10 + B2DPoint(2.0,0.0)); + + aLoop = B2DCubicBezier(a00 + B2DPoint(3.0,0.0), + B2DPoint(a01 + B2DPoint(3.0,0.0)), + B2DPoint(a10 + B2DPoint(3.0,0.0)), + a00 + B2DPoint(3.0,0.0)); + + aStraightLineDistinctEndPoints = B2DCubicBezier(a00 + B2DPoint(4.0,0.0), + B2DPoint(middle + B2DPoint(4.0,0.0)), + B2DPoint(middle + B2DPoint(4.0,0.0)), + a11 + B2DPoint(4.0,0.0)); + + aStraightLineDistinctEndPoints2 = B2DCubicBezier(a00 + B2DPoint(5.0,0.0), + B2DPoint(quarterDown + B2DPoint(5.0,0.0)), + B2DPoint(quarterUp + B2DPoint(5.0,0.0)), + a11 + B2DPoint(5.0,0.0)); + + aStraightLineIdenticalEndPoints = B2DCubicBezier(a00 + B2DPoint(6.0,0.0), + B2DPoint(a11 + B2DPoint(6.0,0.0)), + B2DPoint(a11 + B2DPoint(6.0,0.0)), + a00 + B2DPoint(6.0,0.0)); + + aStraightLineIdenticalEndPoints2 = B2DCubicBezier(a00 + B2DPoint(7.0,0.0), + B2DPoint(quarterDown + B2DPoint(7.0,0.0)), + B2DPoint(quarterUp + B2DPoint(7.0,0.0)), + a00 + B2DPoint(7.0,0.0)); + + aCrossing = B2DCubicBezier(a00 + B2DPoint(8.0,0.0), + B2DPoint(B2DPoint(2.0,2.0) + B2DPoint(8.0,0.0)), + B2DPoint(B2DPoint(-1.0,2.0) + B2DPoint(8.0,0.0)), + a10 + B2DPoint(8.0,0.0)); + + ::std::ofstream output("bez_testcases.gnuplot"); + DebugPlotter aPlotter( "Original curves", + output ); + + aPlotter.plot( aHalfCircle, + "half circle" ); + aPlotter.plot( aQuarterCircle, + "quarter circle" ); + aPlotter.plot( aCusp, + "cusp" ); + aPlotter.plot( aLoop, + "loop" ); + aPlotter.plot( aStraightLineDistinctEndPoints, + "straight line 0" ); + aPlotter.plot( aStraightLineDistinctEndPoints2, + "straight line 1" ); + aPlotter.plot( aStraightLineIdenticalEndPoints, + "straight line 2" ); + aPlotter.plot( aStraightLineIdenticalEndPoints2, + "straight line 3" ); + aPlotter.plot( aCrossing, + "crossing" ); + + // break up a complex bezier (loopy, spiky or self intersecting) + // into simple segments (left to right) + B2DCubicBezier aSegment = aCrossing; + double fExtremePos(0.0); + + aPlotter.plot( aSegment, "segment" ); + while(aSegment.getMinimumExtremumPosition(fExtremePos)) + { + aSegment.split(fExtremePos, 0, &aSegment); + aPlotter.plot( aSegment, "segment" ); + } + } + + void tearDown() + { + } + + void adaptiveByDistance() + { + ::std::ofstream output("bez_adaptiveByDistance.gnuplot"); + DebugPlotter aPlotter( "distance-adaptive subdivision", + output ); + + const double fBound( 0.0001 ); + B2DPolygon result; + + aHalfCircle.adaptiveSubdivideByDistance(result, fBound); + aPlotter.plot(result, + "half circle"); result.clear(); + + aQuarterCircle.adaptiveSubdivideByDistance(result, fBound); + aPlotter.plot(result, + "quarter circle"); result.clear(); + + aLoop.adaptiveSubdivideByDistance(result, fBound); + aPlotter.plot(result, + "loop"); result.clear(); + + aStraightLineDistinctEndPoints.adaptiveSubdivideByDistance(result, fBound); + aPlotter.plot(result, + "straight line 0"); result.clear(); + + aStraightLineDistinctEndPoints2.adaptiveSubdivideByDistance(result, fBound); + aPlotter.plot(result, + "straight line 1"); result.clear(); + + aStraightLineIdenticalEndPoints.adaptiveSubdivideByDistance(result, fBound); + aPlotter.plot(result, + "straight line 2"); result.clear(); + + aStraightLineIdenticalEndPoints2.adaptiveSubdivideByDistance(result, fBound); + aPlotter.plot(result, + "straight line 3"); result.clear(); + + aCrossing.adaptiveSubdivideByDistance(result, fBound); + aPlotter.plot(result, + "straight line 4"); result.clear(); + + aCusp.adaptiveSubdivideByDistance(result, fBound); + aPlotter.plot(result, + "straight line 5"); result.clear(); + + CPPUNIT_ASSERT_MESSAGE("adaptiveByDistance", true ); + } + + void adaptiveByAngle() + { + const double fBound( 5.0 ); + B2DPolygon result; + + ::std::ofstream output("bez_adaptiveByAngle.gnuplot"); + DebugPlotter aPlotter( "angle-adaptive subdivision", + output ); + + aHalfCircle.adaptiveSubdivideByAngle(result, fBound, true); + aPlotter.plot(result, + "half circle"); result.clear(); + + aQuarterCircle.adaptiveSubdivideByAngle(result, fBound, true); + aPlotter.plot(result, + "quarter cirle"); result.clear(); + + aLoop.adaptiveSubdivideByAngle(result, fBound, true); + aPlotter.plot(result, + "loop"); result.clear(); + + aStraightLineDistinctEndPoints.adaptiveSubdivideByAngle(result, fBound, true); + aPlotter.plot(result, + "straight line 0"); result.clear(); + + aStraightLineDistinctEndPoints2.adaptiveSubdivideByAngle(result, fBound, true); + aPlotter.plot(result, + "straight line 1"); result.clear(); + + aStraightLineIdenticalEndPoints.adaptiveSubdivideByAngle(result, fBound, true); + aPlotter.plot(result, + "straight line 2"); result.clear(); + + aStraightLineIdenticalEndPoints2.adaptiveSubdivideByAngle(result, fBound, true); + aPlotter.plot(result, + "straight line 3"); result.clear(); + + aCrossing.adaptiveSubdivideByAngle(result, fBound, true); + aPlotter.plot(result, + "straight line 4"); result.clear(); + + aCusp.adaptiveSubdivideByAngle(result, fBound, true); + aPlotter.plot(result, + "straight line 5"); result.clear(); + + CPPUNIT_ASSERT_MESSAGE("adaptiveByAngle", true ); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dbeziertools); + CPPUNIT_TEST(adaptiveByDistance); // TODO: add tests for quadratic bezier (subdivide and degree reduction) + CPPUNIT_TEST(adaptiveByAngle); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dcubicbezier + + +class b2dcubicbezier : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + void EmptyMethod() + { + // this is demonstration code + // CPPUNIT_ASSERT_MESSAGE("a message", 1 == 1); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dcubicbezier); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dcubicbezier + + +class b2dhommatrix : public CppUnit::TestFixture +{ +private: + B2DHomMatrix maIdentity; + B2DHomMatrix maScale; + B2DHomMatrix maTranslate; + B2DHomMatrix maShear; + B2DHomMatrix maAffine; + B2DHomMatrix maPerspective; + +public: + // initialise your test code values here. + void setUp() + { + // setup some test matrices + maIdentity.identity(); // force compact layout + maIdentity.set(0,0, 1.0); + maIdentity.set(0,1, 0.0); + maIdentity.set(0,2, 0.0); + maIdentity.set(1,0, 0.0); + maIdentity.set(1,1, 1.0); + maIdentity.set(1,2, 0.0); + + maScale.identity(); // force compact layout + maScale.set(0,0, 2.0); + maScale.set(1,1, 20.0); + + maTranslate.identity(); // force compact layout + maTranslate.set(0,2, 20.0); + maTranslate.set(1,2, 2.0); + + maShear.identity(); // force compact layout + maShear.set(0,1, 3.0); + maShear.set(1,0, 7.0); + maShear.set(1,1, 22.0); + + maAffine.identity(); // force compact layout + maAffine.set(0,0, 1.0); + maAffine.set(0,1, 2.0); + maAffine.set(0,2, 3.0); + maAffine.set(1,0, 4.0); + maAffine.set(1,1, 5.0); + maAffine.set(1,2, 6.0); + + maPerspective.set(0,0, 1.0); + maPerspective.set(0,1, 2.0); + maPerspective.set(0,2, 3.0); + maPerspective.set(1,0, 4.0); + maPerspective.set(1,1, 5.0); + maPerspective.set(1,2, 6.0); + maPerspective.set(2,0, 7.0); + maPerspective.set(2,1, 8.0); + maPerspective.set(2,2, 9.0); + } + + void tearDown() + { + } + + void equal() + { + B2DHomMatrix aIdentity; + B2DHomMatrix aScale; + B2DHomMatrix aTranslate; + B2DHomMatrix aShear; + B2DHomMatrix aAffine; + B2DHomMatrix aPerspective; + + // setup some test matrices + aIdentity.identity(); // force compact layout + aIdentity.set(0,0, 1.0); + aIdentity.set(0,1, 0.0); + aIdentity.set(0,2, 0.0); + aIdentity.set(1,0, 0.0); + aIdentity.set(1,1, 1.0); + aIdentity.set(1,2, 0.0); + + aScale.identity(); // force compact layout + aScale.set(0,0, 2.0); + aScale.set(1,1, 20.0); + + aTranslate.identity(); // force compact layout + aTranslate.set(0,2, 20.0); + aTranslate.set(1,2, 2.0); + + aShear.identity(); // force compact layout + aShear.set(0,1, 3.0); + aShear.set(1,0, 7.0); + aShear.set(1,1, 22.0); + + aAffine.identity(); // force compact layout + aAffine.set(0,0, 1.0); + aAffine.set(0,1, 2.0); + aAffine.set(0,2, 3.0); + aAffine.set(1,0, 4.0); + aAffine.set(1,1, 5.0); + aAffine.set(1,2, 6.0); + + aPerspective.set(0,0, 1.0); + aPerspective.set(0,1, 2.0); + aPerspective.set(0,2, 3.0); + aPerspective.set(1,0, 4.0); + aPerspective.set(1,1, 5.0); + aPerspective.set(1,2, 6.0); + aPerspective.set(2,0, 7.0); + aPerspective.set(2,1, 8.0); + aPerspective.set(2,2, 9.0); + + CPPUNIT_ASSERT_MESSAGE("operator==: identity matrix", aIdentity == maIdentity); + CPPUNIT_ASSERT_MESSAGE("operator==: scale matrix", aScale == maScale); + CPPUNIT_ASSERT_MESSAGE("operator==: translate matrix", aTranslate == maTranslate); + CPPUNIT_ASSERT_MESSAGE("operator==: shear matrix", aShear == maShear); + CPPUNIT_ASSERT_MESSAGE("operator==: affine matrix", aAffine == maAffine); + CPPUNIT_ASSERT_MESSAGE("operator==: perspective matrix", aPerspective == maPerspective); + } + + void identity() + { + B2DHomMatrix ident; + + CPPUNIT_ASSERT_MESSAGE("identity", maIdentity == ident); + } + + void scale() + { + B2DHomMatrix mat; + mat.scale(2.0,20.0); + CPPUNIT_ASSERT_MESSAGE("scale", maScale == mat); + } + + void translate() + { + B2DHomMatrix mat; + mat.translate(20.0,2.0); + CPPUNIT_ASSERT_MESSAGE("translate", maTranslate == mat); + } + + void shear() + { + B2DHomMatrix mat; + mat.shearX(3.0); + mat.shearY(7.0); + CPPUNIT_ASSERT_MESSAGE("translate", maShear == mat); + } + + void multiply() + { + B2DHomMatrix affineAffineProd; + + affineAffineProd.set(0,0, 9); + affineAffineProd.set(0,1, 12); + affineAffineProd.set(0,2, 18); + affineAffineProd.set(1,0, 24); + affineAffineProd.set(1,1, 33); + affineAffineProd.set(1,2, 48); + + B2DHomMatrix affinePerspectiveProd; + + affinePerspectiveProd.set(0,0, 30); + affinePerspectiveProd.set(0,1, 36); + affinePerspectiveProd.set(0,2, 42); + affinePerspectiveProd.set(1,0, 66); + affinePerspectiveProd.set(1,1, 81); + affinePerspectiveProd.set(1,2, 96); + affinePerspectiveProd.set(2,0, 7); + affinePerspectiveProd.set(2,1, 8); + affinePerspectiveProd.set(2,2, 9); + + B2DHomMatrix perspectiveAffineProd; + + perspectiveAffineProd.set(0,0, 9); + perspectiveAffineProd.set(0,1, 12); + perspectiveAffineProd.set(0,2, 18); + perspectiveAffineProd.set(1,0, 24); + perspectiveAffineProd.set(1,1, 33); + perspectiveAffineProd.set(1,2, 48); + perspectiveAffineProd.set(2,0, 39); + perspectiveAffineProd.set(2,1, 54); + perspectiveAffineProd.set(2,2, 78); + + B2DHomMatrix perspectivePerspectiveProd; + + perspectivePerspectiveProd.set(0,0, 30); + perspectivePerspectiveProd.set(0,1, 36); + perspectivePerspectiveProd.set(0,2, 42); + perspectivePerspectiveProd.set(1,0, 66); + perspectivePerspectiveProd.set(1,1, 81); + perspectivePerspectiveProd.set(1,2, 96); + perspectivePerspectiveProd.set(2,0, 102); + perspectivePerspectiveProd.set(2,1, 126); + perspectivePerspectiveProd.set(2,2, 150); + + B2DHomMatrix temp; + + temp = maAffine; + temp*=maAffine; + CPPUNIT_ASSERT_MESSAGE("multiply: both compact", temp == affineAffineProd); + + temp = maPerspective; + temp*=maAffine; + CPPUNIT_ASSERT_MESSAGE("multiply: first compact", temp == affinePerspectiveProd); + + temp = maAffine; + temp*=maPerspective; + CPPUNIT_ASSERT_MESSAGE("multiply: second compact", temp == perspectiveAffineProd); + + temp = maPerspective; + temp*=maPerspective; + CPPUNIT_ASSERT_MESSAGE("multiply: none compact", temp == perspectivePerspectiveProd); + } + + void impFillMatrix(B2DHomMatrix& rSource, double fScaleX, double fScaleY, double fShearX, double fRotate) + { + // fill rSource with a linear combination of scale, shear and rotate + rSource.identity(); + rSource.scale(fScaleX, fScaleY); + rSource.shearX(fShearX); + rSource.rotate(fRotate); + } + + bool impDecomposeComposeTest(double fScaleX, double fScaleY, double fShearX, double fRotate) + { + // linear combine matrix with given values + B2DHomMatrix aSource; + impFillMatrix(aSource, fScaleX, fScaleY, fShearX, fRotate); + + // decompose that matrix + B2DTuple aDScale; + B2DTuple aDTrans; + double fDRot; + double fDShX; + bool bWorked = aSource.decompose(aDScale, aDTrans, fDRot, fDShX); + + // linear combine another matrix with decomposition results + B2DHomMatrix aRecombined; + impFillMatrix(aRecombined, aDScale.getX(), aDScale.getY(), fDShX, fDRot); + + // if decomposition worked, matrices need to be the same + return bWorked && aSource == aRecombined; + } + + void decompose() + { + // test matrix decompositions. Each matrix decomposed and rebuilt + // using the decompose result should be the same as before. Test + // with all ranges of values. Translations are not tested since these + // are just the two rightmost values and uncritical + static double fSX(10.0); + static double fSY(12.0); + static double fR(45.0 * F_PI180); + static double fS(15.0 * F_PI180); + + // check all possible scaling combinations + CPPUNIT_ASSERT_MESSAGE("decompose: error test A1", impDecomposeComposeTest(fSX, fSY, 0.0, 0.0)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test A2", impDecomposeComposeTest(-fSX, fSY, 0.0, 0.0)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test A3", impDecomposeComposeTest(fSX, -fSY, 0.0, 0.0)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test A4", impDecomposeComposeTest(-fSX, -fSY, 0.0, 0.0)); + + // check all possible scaling combinations with positive rotation + CPPUNIT_ASSERT_MESSAGE("decompose: error test B1", impDecomposeComposeTest(fSX, fSY, 0.0, fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test B2", impDecomposeComposeTest(-fSX, fSY, 0.0, fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test B3", impDecomposeComposeTest(fSX, -fSY, 0.0, fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test B4", impDecomposeComposeTest(-fSX, -fSY, 0.0, fR)); + + // check all possible scaling combinations with negative rotation + CPPUNIT_ASSERT_MESSAGE("decompose: error test C1", impDecomposeComposeTest(fSX, fSY, 0.0, -fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test C2", impDecomposeComposeTest(-fSX, fSY, 0.0, -fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test C3", impDecomposeComposeTest(fSX, -fSY, 0.0, -fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test C4", impDecomposeComposeTest(-fSX, -fSY, 0.0, -fR)); + + // check all possible scaling combinations with positive shear + CPPUNIT_ASSERT_MESSAGE("decompose: error test D1", impDecomposeComposeTest(fSX, fSY, tan(fS), 0.0)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test D2", impDecomposeComposeTest(-fSX, fSY, tan(fS), 0.0)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test D3", impDecomposeComposeTest(fSX, -fSY, tan(fS), 0.0)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test D4", impDecomposeComposeTest(-fSX, -fSY, tan(fS), 0.0)); + + // check all possible scaling combinations with negative shear + CPPUNIT_ASSERT_MESSAGE("decompose: error test E1", impDecomposeComposeTest(fSX, fSY, tan(-fS), 0.0)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test E2", impDecomposeComposeTest(-fSX, fSY, tan(-fS), 0.0)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test E3", impDecomposeComposeTest(fSX, -fSY, tan(-fS), 0.0)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test E4", impDecomposeComposeTest(-fSX, -fSY, tan(-fS), 0.0)); + + // check all possible scaling combinations with positive rotate and positive shear + CPPUNIT_ASSERT_MESSAGE("decompose: error test F1", impDecomposeComposeTest(fSX, fSY, tan(fS), fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test F2", impDecomposeComposeTest(-fSX, fSY, tan(fS), fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test F3", impDecomposeComposeTest(fSX, -fSY, tan(fS), fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test F4", impDecomposeComposeTest(-fSX, -fSY, tan(fS), fR)); + + // check all possible scaling combinations with negative rotate and positive shear + CPPUNIT_ASSERT_MESSAGE("decompose: error test G1", impDecomposeComposeTest(fSX, fSY, tan(fS), -fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test G2", impDecomposeComposeTest(-fSX, fSY, tan(fS), -fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test G3", impDecomposeComposeTest(fSX, -fSY, tan(fS), -fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test G4", impDecomposeComposeTest(-fSX, -fSY, tan(fS), -fR)); + + // check all possible scaling combinations with positive rotate and negative shear + CPPUNIT_ASSERT_MESSAGE("decompose: error test H1", impDecomposeComposeTest(fSX, fSY, tan(-fS), fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test H2", impDecomposeComposeTest(-fSX, fSY, tan(-fS), fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test H3", impDecomposeComposeTest(fSX, -fSY, tan(-fS), fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test H4", impDecomposeComposeTest(-fSX, -fSY, tan(-fS), fR)); + + // check all possible scaling combinations with negative rotate and negative shear + CPPUNIT_ASSERT_MESSAGE("decompose: error test I1", impDecomposeComposeTest(fSX, fSY, tan(-fS), -fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test I2", impDecomposeComposeTest(-fSX, fSY, tan(-fS), -fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test I3", impDecomposeComposeTest(fSX, -fSY, tan(-fS), -fR)); + CPPUNIT_ASSERT_MESSAGE("decompose: error test I4", impDecomposeComposeTest(-fSX, -fSY, tan(-fS), -fR)); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dhommatrix); + CPPUNIT_TEST(equal); + CPPUNIT_TEST(identity); + CPPUNIT_TEST(scale); + CPPUNIT_TEST(translate); + CPPUNIT_TEST(shear); + CPPUNIT_TEST(multiply); + CPPUNIT_TEST(decompose); + CPPUNIT_TEST_SUITE_END(); + +}; // class b2dhommatrix + + +class b2dhompoint : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + void EmptyMethod() + { + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dhompoint); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dhompoint + + +class b2dpoint : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + // this is only demonstration code + void EmptyMethod() + { + // CPPUNIT_ASSERT_MESSAGE("a message", 1 == 1); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dpoint); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dpoint + + +class b2dpolygon : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + void testBasics() + { + B2DPolygon aPoly; + + aPoly.appendBezierSegment(B2DPoint(1,1),B2DPoint(2,2),B2DPoint(3,3)); + + CPPUNIT_ASSERT_MESSAGE("#1 first polygon point wrong", + aPoly.getB2DPoint(0) == B2DPoint(3,3)); + CPPUNIT_ASSERT_MESSAGE("#1 first control point wrong", + aPoly.getPrevControlPoint(0) == B2DPoint(2,2)); + CPPUNIT_ASSERT_MESSAGE("#1 second control point wrong", + aPoly.getNextControlPoint(0) == B2DPoint(3,3)); + CPPUNIT_ASSERT_MESSAGE("next control point not used", + aPoly.isNextControlPointUsed(0) == false); + + aPoly.setNextControlPoint(0,B2DPoint(4,4)); + CPPUNIT_ASSERT_MESSAGE("#1.1 second control point wrong", + aPoly.getNextControlPoint(0) == B2DPoint(4,4)); + CPPUNIT_ASSERT_MESSAGE("next control point used", + aPoly.isNextControlPointUsed(0) == true); + CPPUNIT_ASSERT_MESSAGE("areControlPointsUsed() wrong", + aPoly.areControlPointsUsed() == true); + CPPUNIT_ASSERT_MESSAGE("getContinuityInPoint() wrong", + aPoly.getContinuityInPoint(0) == CONTINUITY_C2); + + aPoly.resetControlPoints(); + CPPUNIT_ASSERT_MESSAGE("resetControlPoints() did not clear", + aPoly.getB2DPoint(0) == B2DPoint(3,3)); + CPPUNIT_ASSERT_MESSAGE("resetControlPoints() did not clear", + aPoly.getPrevControlPoint(0) == B2DPoint(3,3)); + CPPUNIT_ASSERT_MESSAGE("resetControlPoints() did not clear", + aPoly.getNextControlPoint(0) == B2DPoint(3,3)); + CPPUNIT_ASSERT_MESSAGE("areControlPointsUsed() wrong #2", + aPoly.areControlPointsUsed() == false); + + aPoly.clear(); + aPoly.append(B2DPoint(0,0)); + aPoly.appendBezierSegment(B2DPoint(1,1),B2DPoint(2,2),B2DPoint(3,3)); + + CPPUNIT_ASSERT_MESSAGE("#2 first polygon point wrong", + aPoly.getB2DPoint(0) == B2DPoint(0,0)); + CPPUNIT_ASSERT_MESSAGE("#2 first control point wrong", + aPoly.getPrevControlPoint(0) == B2DPoint(0,0)); + CPPUNIT_ASSERT_MESSAGE("#2 second control point wrong", + aPoly.getNextControlPoint(0) == B2DPoint(1,1)); + CPPUNIT_ASSERT_MESSAGE("#2 third control point wrong", + aPoly.getPrevControlPoint(1) == B2DPoint(2,2)); + CPPUNIT_ASSERT_MESSAGE("#2 fourth control point wrong", + aPoly.getNextControlPoint(1) == B2DPoint(3,3)); + CPPUNIT_ASSERT_MESSAGE("#2 second polygon point wrong", + aPoly.getB2DPoint(1) == B2DPoint(3,3)); + } + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dpolygon); + CPPUNIT_TEST(testBasics); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dpolygon + + +class b2dpolygontools : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + // this is only demonstration code + void testIsRectangle() + { + B2DPolygon aRect1( + tools::createPolygonFromRect( + B2DRange(0,0,1,1) ) ); + + B2DPolygon aRect2; + aRect2.append( B2DPoint(0,0) ); + aRect2.append( B2DPoint(1,0) ); + aRect2.append( B2DPoint(1,.5)); + aRect2.append( B2DPoint(1,1) ); + aRect2.append( B2DPoint(0,1) ); + aRect2.setClosed(true); + + B2DPolygon aNonRect1; + aNonRect1.append( B2DPoint(0,0) ); + aNonRect1.append( B2DPoint(1,0) ); + aNonRect1.append( B2DPoint(1,1) ); + aNonRect1.append( B2DPoint(0.5,1) ); + aNonRect1.append( B2DPoint(0.5,0) ); + aNonRect1.setClosed(true); + + B2DPolygon aNonRect2; + aNonRect2.append( B2DPoint(0,0) ); + aNonRect2.append( B2DPoint(1,1) ); + aNonRect2.append( B2DPoint(1,0) ); + aNonRect2.append( B2DPoint(0,1) ); + aNonRect2.setClosed(true); + + B2DPolygon aNonRect3; + aNonRect3.append( B2DPoint(0,0) ); + aNonRect3.append( B2DPoint(1,0) ); + aNonRect3.append( B2DPoint(1,1) ); + aNonRect3.setClosed(true); + + B2DPolygon aNonRect4; + aNonRect4.append( B2DPoint(0,0) ); + aNonRect4.append( B2DPoint(1,0) ); + aNonRect4.append( B2DPoint(1,1) ); + aNonRect4.append( B2DPoint(0,1) ); + + B2DPolygon aNonRect5; + aNonRect5.append( B2DPoint(0,0) ); + aNonRect5.append( B2DPoint(1,0) ); + aNonRect5.append( B2DPoint(1,1) ); + aNonRect5.append( B2DPoint(0,1) ); + aNonRect5.setControlPoints(1,B2DPoint(1,0),B2DPoint(-11,0)); + aNonRect5.setClosed(true); + + CPPUNIT_ASSERT_MESSAGE("checking rectangle-ness of rectangle 1", + tools::isRectangle( aRect1 )); + CPPUNIT_ASSERT_MESSAGE("checking rectangle-ness of rectangle 2", + tools::isRectangle( aRect2 )); + CPPUNIT_ASSERT_MESSAGE("checking non-rectangle-ness of polygon 1", + !tools::isRectangle( aNonRect1 )); + CPPUNIT_ASSERT_MESSAGE("checking non-rectangle-ness of polygon 2", + !tools::isRectangle( aNonRect2 )); + CPPUNIT_ASSERT_MESSAGE("checking non-rectangle-ness of polygon 3", + !tools::isRectangle( aNonRect3 )); + CPPUNIT_ASSERT_MESSAGE("checking non-rectangle-ness of polygon 4", + !tools::isRectangle( aNonRect4 )); + CPPUNIT_ASSERT_MESSAGE("checking non-rectangle-ness of polygon 5", + !tools::isRectangle( aNonRect5 )); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dpolygontools); + CPPUNIT_TEST(testIsRectangle); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dpolygontools + + +class b2dpolypolygon : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + void EmptyMethod() + { + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dpolypolygon); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dpolypolygon + + +class b2dquadraticbezier : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + // this is only demonstration code + void EmptyMethod() + { + // CPPUNIT_ASSERT_MESSAGE("a message", 1 == 1); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dquadraticbezier); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dquadraticbezier + + +class b2drange : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + void EmptyMethod() + { + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2drange); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b2drange + + +class b2dtuple : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + // this is only demonstration code + void EmptyMethod() + { + // CPPUNIT_ASSERT_MESSAGE("a message", 1 == 1); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dtuple); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dtuple + + +class b2dvector : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + void EmptyMethod() + { + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b2dvector); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dvector + +class bcolor : public CppUnit::TestFixture +{ + BColor maWhite; + BColor maBlack; + BColor maRed; + BColor maGreen; + BColor maBlue; + BColor maYellow; + BColor maMagenta; + BColor maCyan; + +public: + bcolor() : + maWhite(1,1,1), + maBlack(0,0,0), + maRed(1,0,0), + maGreen(0,1,0), + maBlue(0,0,1), + maYellow(1,1,0), + maMagenta(1,0,1), + maCyan(0,1,1) + {} + + + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + void hslTest() + { + CPPUNIT_ASSERT_MESSAGE("white", + tools::rgb2hsl(maWhite) == BColor(0,0,1)); + CPPUNIT_ASSERT_MESSAGE("black", + tools::rgb2hsl(maBlack) == BColor(0,0,0)); + CPPUNIT_ASSERT_MESSAGE("red", + tools::rgb2hsl(maRed) == BColor(0,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("green", + tools::rgb2hsl(maGreen) == BColor(120,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("blue", + tools::rgb2hsl(maBlue) == BColor(240,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("yellow", + tools::rgb2hsl(maYellow) == BColor(60,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("magenta", + tools::rgb2hsl(maMagenta) == BColor(300,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("cyan", + tools::rgb2hsl(maCyan) == BColor(180,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("third hue case", + tools::rgb2hsl(BColor(0,0.5,1)) == BColor(210,1,0.5)); + + CPPUNIT_ASSERT_MESSAGE("roundtrip white", + tools::hsl2rgb(tools::rgb2hsl(maWhite)) == maWhite); + CPPUNIT_ASSERT_MESSAGE("roundtrip black", + tools::hsl2rgb(tools::rgb2hsl(maBlack)) == maBlack); + CPPUNIT_ASSERT_MESSAGE("roundtrip red", + tools::hsl2rgb(tools::rgb2hsl(maRed)) == maRed); + CPPUNIT_ASSERT_MESSAGE("roundtrip green", + tools::hsl2rgb(tools::rgb2hsl(maGreen)) == maGreen); + CPPUNIT_ASSERT_MESSAGE("roundtrip blue", + tools::hsl2rgb(tools::rgb2hsl(maBlue)) == maBlue); + CPPUNIT_ASSERT_MESSAGE("roundtrip yellow", + tools::hsl2rgb(tools::rgb2hsl(maYellow)) == maYellow); + CPPUNIT_ASSERT_MESSAGE("roundtrip magenta", + tools::hsl2rgb(tools::rgb2hsl(maMagenta)) == maMagenta); + CPPUNIT_ASSERT_MESSAGE("roundtrip cyan", + tools::hsl2rgb(tools::rgb2hsl(maCyan)) == maCyan); + + CPPUNIT_ASSERT_MESSAGE("grey10", + tools::rgb2hsl(maWhite*.1) == BColor(0,0,.1)); + CPPUNIT_ASSERT_MESSAGE("grey90", + tools::rgb2hsl(maWhite*.9) == BColor(0,0,.9)); + CPPUNIT_ASSERT_MESSAGE("red/2", + tools::rgb2hsl(maRed*.5) == BColor(0,1,0.25)); + CPPUNIT_ASSERT_MESSAGE("green/2", + tools::rgb2hsl(maGreen*.5) == BColor(120,1,0.25)); + CPPUNIT_ASSERT_MESSAGE("blue/2", + tools::rgb2hsl(maBlue*.5) == BColor(240,1,0.25)); + CPPUNIT_ASSERT_MESSAGE("yellow/2", + tools::rgb2hsl(maYellow*.5) == BColor(60,1,0.25)); + CPPUNIT_ASSERT_MESSAGE("magenta/2", + tools::rgb2hsl(maMagenta*.5) == BColor(300,1,0.25)); + CPPUNIT_ASSERT_MESSAGE("cyan/2", + tools::rgb2hsl(maCyan*.5) == BColor(180,1,0.25)); + + CPPUNIT_ASSERT_MESSAGE("pastel", + tools::rgb2hsl(BColor(.75,.25,.25)) == BColor(0,.5,.5)); + } + + // insert your test code here. + void hsvTest() + { + CPPUNIT_ASSERT_MESSAGE("white", + tools::rgb2hsv(maWhite) == BColor(0,0,1)); + CPPUNIT_ASSERT_MESSAGE("black", + tools::rgb2hsv(maBlack) == BColor(0,0,0)); + CPPUNIT_ASSERT_MESSAGE("red", + tools::rgb2hsv(maRed) == BColor(0,1,1)); + CPPUNIT_ASSERT_MESSAGE("green", + tools::rgb2hsv(maGreen) == BColor(120,1,1)); + CPPUNIT_ASSERT_MESSAGE("blue", + tools::rgb2hsv(maBlue) == BColor(240,1,1)); + CPPUNIT_ASSERT_MESSAGE("yellow", + tools::rgb2hsv(maYellow) == BColor(60,1,1)); + CPPUNIT_ASSERT_MESSAGE("magenta", + tools::rgb2hsv(maMagenta) == BColor(300,1,1)); + CPPUNIT_ASSERT_MESSAGE("cyan", + tools::rgb2hsv(maCyan) == BColor(180,1,1)); + + CPPUNIT_ASSERT_MESSAGE("roundtrip white", + tools::hsv2rgb(tools::rgb2hsv(maWhite)) == maWhite); + CPPUNIT_ASSERT_MESSAGE("roundtrip black", + tools::hsv2rgb(tools::rgb2hsv(maBlack)) == maBlack); + CPPUNIT_ASSERT_MESSAGE("roundtrip red", + tools::hsv2rgb(tools::rgb2hsv(maRed)) == maRed); + CPPUNIT_ASSERT_MESSAGE("roundtrip green", + tools::hsv2rgb(tools::rgb2hsv(maGreen)) == maGreen); + CPPUNIT_ASSERT_MESSAGE("roundtrip blue", + tools::hsv2rgb(tools::rgb2hsv(maBlue)) == maBlue); + CPPUNIT_ASSERT_MESSAGE("roundtrip yellow", + tools::hsv2rgb(tools::rgb2hsv(maYellow)) == maYellow); + CPPUNIT_ASSERT_MESSAGE("roundtrip magenta", + tools::hsv2rgb(tools::rgb2hsv(maMagenta)) == maMagenta); + CPPUNIT_ASSERT_MESSAGE("roundtrip cyan", + tools::hsv2rgb(tools::rgb2hsv(maCyan)) == maCyan); + + CPPUNIT_ASSERT_MESSAGE("grey10", + tools::rgb2hsv(maWhite*.1) == BColor(0,0,.1)); + CPPUNIT_ASSERT_MESSAGE("grey90", + tools::rgb2hsv(maWhite*.9) == BColor(0,0,.9)); + CPPUNIT_ASSERT_MESSAGE("red/2", + tools::rgb2hsv(maRed*.5) == BColor(0,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("green/2", + tools::rgb2hsv(maGreen*.5) == BColor(120,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("blue/2", + tools::rgb2hsv(maBlue*.5) == BColor(240,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("yellow/2", + tools::rgb2hsv(maYellow*.5) == BColor(60,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("magenta/2", + tools::rgb2hsv(maMagenta*.5) == BColor(300,1,0.5)); + CPPUNIT_ASSERT_MESSAGE("cyan/2", + tools::rgb2hsv(maCyan*.5) == BColor(180,1,0.5)); + + CPPUNIT_ASSERT_MESSAGE("pastel", + tools::rgb2hsv(BColor(.5,.25,.25)) == BColor(0,.5,.5)); + } + + void ciexyzTest() + { + tools::rgb2ciexyz(maWhite); + tools::rgb2ciexyz(maBlack); + tools::rgb2ciexyz(maRed); + tools::rgb2ciexyz(maGreen); + tools::rgb2ciexyz(maBlue); + tools::rgb2ciexyz(maYellow); + tools::rgb2ciexyz(maMagenta); + tools::rgb2ciexyz(maCyan); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(bcolor); + CPPUNIT_TEST(hslTest); + CPPUNIT_TEST(hsvTest); + CPPUNIT_TEST(ciexyzTest); + CPPUNIT_TEST_SUITE_END(); +}; // class b2dvector + +// ----------------------------------------------------------------------------- + +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dsvgdimpex); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dpolyrange); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dcubicbezier); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dhommatrix); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dhompoint); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dpoint); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dpolygon); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dpolygontools); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dpolypolygon); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dquadraticbezier); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2drange); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dtuple); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::b2dvector); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::bcolor); +} // namespace basegfx2d + + +// ----------------------------------------------------------------------------- + +// this macro creates an empty function, which will called by the RegisterAllFunctions() +// to let the user the possibility to also register some functions by hand. +// NOADDITIONAL; + diff --git a/basegfx/test/basegfx3d.cxx b/basegfx/test/basegfx3d.cxx new file mode 100644 index 000000000000..4871dcbd16c4 --- /dev/null +++ b/basegfx/test/basegfx3d.cxx @@ -0,0 +1,224 @@ +/************************************************************************* + * + * 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" +// autogenerated file with codegen.pl + +#include "preextstl.h" +#include "cppunit/TestAssert.h" +#include "cppunit/TestFixture.h" +#include "cppunit/extensions/HelperMacros.h" +#include "postextstl.h" + +namespace basegfx3d +{ + +class b3dhommatrix : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + // this is only demonstration code + void EmptyMethod() + { + // CPPUNIT_ASSERT_MESSAGE("a message", 1 == 1); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b3dhommatrix); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b3dhommatrix + + +class b3dhompoint : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + void EmptyMethod() + { + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b3dhompoint); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b3dhompoint + + +class b3dpoint : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + // this is only demonstration code + void EmptyMethod() + { + // CPPUNIT_ASSERT_MESSAGE("a message", 1 == 1); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b3dpoint); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b3dpoint + + +class b3drange : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + void EmptyMethod() + { + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b3drange); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b3drange + + +class b3dtuple : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + // this is only demonstration code + void EmptyMethod() + { + // CPPUNIT_ASSERT_MESSAGE("a message", 1 == 1); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b3dtuple); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b3dtuple + + +class b3dvector : public CppUnit::TestFixture +{ +public: + // initialise your test code values here. + void setUp() + { + } + + void tearDown() + { + } + + // insert your test code here. + void EmptyMethod() + { + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(b3dvector); + CPPUNIT_TEST(EmptyMethod); + CPPUNIT_TEST_SUITE_END(); +}; // class b3dvector + +// ----------------------------------------------------------------------------- +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx3d::b3dhommatrix); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx3d::b3dhompoint); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx3d::b3dpoint); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx3d::b3drange); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx3d::b3dtuple); +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx3d::b3dvector); +} // namespace basegfx3d + + +// ----------------------------------------------------------------------------- + +// this macro creates an empty function, which will called by the RegisterAllFunctions() +// to let the user the possibility to also register some functions by hand. +// NOADDITIONAL; + diff --git a/basegfx/test/basegfxtools.cxx b/basegfx/test/basegfxtools.cxx new file mode 100644 index 000000000000..7e385f1eb78c --- /dev/null +++ b/basegfx/test/basegfxtools.cxx @@ -0,0 +1,112 @@ +/************************************************************************* + * + * 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" +// autogenerated file with codegen.pl + +#include "preextstl.h" +#include "cppunit/TestAssert.h" +#include "cppunit/TestFixture.h" +#include "cppunit/extensions/HelperMacros.h" +#include "postextstl.h" + +#include <basegfx/tools/keystoplerp.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <boost/tuple/tuple.hpp> + +using namespace ::basegfx; +using namespace ::boost::tuples; + +namespace basegfxtools +{ + +class KeyStopLerpTest : public CppUnit::TestFixture +{ + tools::KeyStopLerp maKeyStops; + + static std::vector<double> getTestVector() + { + std::vector<double> aStops(3); + aStops[0] = 0.1; + aStops[1] = 0.5; + aStops[2] = 0.9; + return aStops; + } + +public: + KeyStopLerpTest() : + maKeyStops(getTestVector()) + {} + + void setUp() + {} + + void tearDown() + {} + + void test() + { + double fAlpha; + std::ptrdiff_t nIndex; + + tie(nIndex,fAlpha) = maKeyStops.lerp(-1.0); + CPPUNIT_ASSERT_MESSAGE("-1.0", nIndex==0 && fAlpha==0.0); + + tie(nIndex,fAlpha) = maKeyStops.lerp(0.1); + CPPUNIT_ASSERT_MESSAGE("0.1", nIndex==0 && fAlpha==0.0); + + tie(nIndex,fAlpha) = maKeyStops.lerp(0.3); + CPPUNIT_ASSERT_MESSAGE("0.3", nIndex==0 && fTools::equal(fAlpha,0.5)); + + tie(nIndex,fAlpha) = maKeyStops.lerp(0.5); + CPPUNIT_ASSERT_MESSAGE("0.5", nIndex==0 && fTools::equal(fAlpha,1.0)); + + tie(nIndex,fAlpha) = maKeyStops.lerp(0.51); + CPPUNIT_ASSERT_MESSAGE("0.51", nIndex==1 && fTools::equal(fAlpha,0.025)); + + tie(nIndex,fAlpha) = maKeyStops.lerp(0.9); + CPPUNIT_ASSERT_MESSAGE("0.51", nIndex==1 && fTools::equal(fAlpha,1.0)); + + tie(nIndex,fAlpha) = maKeyStops.lerp(1.0); + CPPUNIT_ASSERT_MESSAGE("0.51", nIndex==1 && fAlpha==1.0); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(KeyStopLerpTest); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); +}; + +// ----------------------------------------------------------------------------- +CPPUNIT_TEST_SUITE_REGISTRATION(basegfxtools::KeyStopLerpTest); +} // namespace basegfxtools diff --git a/basegfx/test/boxclipper.cxx b/basegfx/test/boxclipper.cxx new file mode 100644 index 000000000000..b1e08087136f --- /dev/null +++ b/basegfx/test/boxclipper.cxx @@ -0,0 +1,421 @@ +/************************************************************************* + * + * 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" +// autogenerated file with codegen.pl + +#include "preextstl.h" +#include "cppunit/TestAssert.h" +#include "cppunit/TestFixture.h" +#include "cppunit/extensions/HelperMacros.h" +#include "postextstl.h" + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <basegfx/curve/b2dbeziertools.hxx> +#include <basegfx/range/b2dpolyrange.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <boost/bind.hpp> + +using namespace ::basegfx; + + +namespace basegfx2d +{ +/// Gets a random ordinal [0,n) +inline double getRandomOrdinal( const ::std::size_t n ) +{ + // use this one when displaying polygons in OOo, which still sucks + // great rocks when trying to import non-integer svg:d attributes + // return sal_Int64(double(n) * rand() / (RAND_MAX + 1.0)); + return double(n) * rand() / (RAND_MAX + 1.0); +} + +inline bool compare(const B2DPoint& left, const B2DPoint& right) +{ + return left.getX()<right.getX() + || (left.getX()==right.getX() && left.getY()<right.getY()); +} + + +class boxclipper : public CppUnit::TestFixture +{ +private: + B2DPolyRange aDisjunctRanges; + B2DPolyRange aEqualRanges; + B2DPolyRange aIntersectionN; + B2DPolyRange aIntersectionE; + B2DPolyRange aIntersectionS; + B2DPolyRange aIntersectionW; + B2DPolyRange aIntersectionNE; + B2DPolyRange aIntersectionSE; + B2DPolyRange aIntersectionSW; + B2DPolyRange aIntersectionNW; + B2DPolyRange aRingIntersection; + B2DPolyRange aRingIntersection2; + B2DPolyRange aRingIntersectExtraStrip; + B2DPolyRange aComplexIntersections; + B2DPolyRange aRandomIntersections; + +public: + // initialise your test code values here. + void setUp() + { + B2DRange aCenter(100, 100, -100, -100); + B2DRange aOffside(800, 800, 1000, 1000); + B2DRange aNorth(100, 0, -100, -200); + B2DRange aSouth(100, 200, -100, 0); + B2DRange aEast(0, 100, 200, -100); + B2DRange aWest(-200, 100, 0, -100); + B2DRange aNorthEast(0, 0, 200, -200); + B2DRange aSouthEast(0, 0, 200, 200); + B2DRange aSouthWest(0, 0, -200, 200); + B2DRange aNorthWest(0, 0, -200, -200); + + B2DRange aNorth2(-150, 50, 150, 350); + B2DRange aSouth2(-150, -50, 150, -350); + B2DRange aEast2 (50, -150, 350, 150); + B2DRange aWest2 (-50, -150,-350, 150); + + aDisjunctRanges.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aDisjunctRanges.appendElement( aOffside, ORIENTATION_NEGATIVE ); + + aEqualRanges.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aEqualRanges.appendElement( aCenter, ORIENTATION_NEGATIVE ); + + aIntersectionN.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aIntersectionN.appendElement( aNorth, ORIENTATION_NEGATIVE ); + + aIntersectionE.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aIntersectionE.appendElement( aEast, ORIENTATION_NEGATIVE ); + + aIntersectionS.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aIntersectionS.appendElement( aSouth, ORIENTATION_NEGATIVE ); + + aIntersectionW.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aIntersectionW.appendElement( aWest, ORIENTATION_NEGATIVE ); + + aIntersectionNE.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aIntersectionNE.appendElement( aNorthEast, ORIENTATION_NEGATIVE ); + + aIntersectionSE.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aIntersectionSE.appendElement( aSouthEast, ORIENTATION_NEGATIVE ); + + aIntersectionSW.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aIntersectionSW.appendElement( aSouthWest, ORIENTATION_NEGATIVE ); + + aIntersectionNW.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aIntersectionNW.appendElement( aNorthWest, ORIENTATION_NEGATIVE ); + + aRingIntersection.appendElement( aNorth2, ORIENTATION_NEGATIVE ); + aRingIntersection.appendElement( aEast2, ORIENTATION_NEGATIVE ); + aRingIntersection.appendElement( aSouth2, ORIENTATION_NEGATIVE ); + + aRingIntersection2 = aRingIntersection; + aRingIntersection2.appendElement( aWest2, ORIENTATION_NEGATIVE ); + + aRingIntersectExtraStrip = aRingIntersection2; + aRingIntersectExtraStrip.appendElement( B2DRange(0, -25, 200, 25), + ORIENTATION_NEGATIVE ); + + aComplexIntersections.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aComplexIntersections.appendElement( aOffside, ORIENTATION_NEGATIVE ); + aComplexIntersections.appendElement( aCenter, ORIENTATION_NEGATIVE ); + aComplexIntersections.appendElement( aNorth, ORIENTATION_NEGATIVE ); + aComplexIntersections.appendElement( aEast, ORIENTATION_NEGATIVE ); + aComplexIntersections.appendElement( aSouth, ORIENTATION_NEGATIVE ); + aComplexIntersections.appendElement( aWest, ORIENTATION_NEGATIVE ); + aComplexIntersections.appendElement( aNorthEast, ORIENTATION_NEGATIVE ); + aComplexIntersections.appendElement( aSouthEast, ORIENTATION_NEGATIVE ); + aComplexIntersections.appendElement( aSouthWest, ORIENTATION_NEGATIVE ); + aComplexIntersections.appendElement( aNorthWest, ORIENTATION_NEGATIVE ); + +#ifdef GENERATE_RANDOM + for( int i=0; i<800; ++i ) + { + B2DRange aRandomRange( + getRandomOrdinal( 1000 ), + getRandomOrdinal( 1000 ), + getRandomOrdinal( 1000 ), + getRandomOrdinal( 1000 ) ); + + aRandomIntersections.appendElement( aRandomRange, ORIENTATION_NEGATIVE ); + } +#else + const char* randomSvg="m394 783h404v57h-404zm-197-505h571v576h-571zm356-634h75v200h-75zm-40-113h403v588h-403zm93-811h111v494h-111zm-364-619h562v121h-562zm-134-8h292v27h-292zm110 356h621v486h-621zm78-386h228v25h-228zm475-345h201v201h-201zm-2-93h122v126h-122zm-417-243h567v524h-567zm-266-738h863v456h-863zm262-333h315v698h-315zm-328-826h43v393h-43zm830-219h120v664h-120zm-311-636h221v109h-221zm-500 137h628v19h-628zm681-94h211v493h-211zm-366-646h384v355h-384zm-189-199h715v247h-715zm165-459h563v601h-563zm258-479h98v606h-98zm270-517h65v218h-65zm-44-259h96v286h-96zm-599-202h705v468h-705zm216-803h450v494h-450zm-150-22h26v167h-26zm-55-599h50v260h-50zm190-278h490v387h-490zm-290-453h634v392h-634zm257 189h552v300h-552zm-151-690h136v455h-136zm12-597h488v432h-488zm501-459h48v39h-48zm-224-112h429v22h-429zm-281 102h492v621h-492zm519-158h208v17h-208zm-681-563h56v427h-56zm126-451h615v392h-615zm-47-410h598v522h-598zm-32 316h79v110h-79zm-71-129h18v127h-18zm126-993h743v589h-743zm211-430h428v750h-428zm61-554h100v220h-100zm-353-49h658v157h-658zm778-383h115v272h-115zm-249-541h119v712h-119zm203 86h94v40h-94z"; + B2DPolyPolygon randomPoly; + tools::importFromSvgD( + randomPoly, + rtl::OUString::createFromAscii(randomSvg)); + std::for_each(randomPoly.begin(), + randomPoly.end(), + boost::bind( + &B2DPolyRange::appendElement, + boost::ref(aRandomIntersections), + boost::bind( + &B2DPolygon::getB2DRange, + _1), + ORIENTATION_NEGATIVE, + 1)); +#endif + } + + void tearDown() + { + } + + B2DPolyPolygon normalizePoly( const B2DPolyPolygon& rPoly ) + { + B2DPolyPolygon aRes; + for( sal_uInt32 i=0; i<rPoly.count(); ++i ) + { + B2DPolygon aTmp=rPoly.getB2DPolygon(i); + if( ORIENTATION_NEGATIVE == tools::getOrientation(aTmp) ) + aTmp.flip(); + + aTmp=tools::removeNeutralPoints(aTmp); + + B2DPoint* pSmallest=0; + for(B2DPoint* pCurr=aTmp.begin(); pCurr!=aTmp.end(); ++pCurr) + { + if( ! pSmallest || compare(*pCurr, *pSmallest) ) + { + pSmallest=pCurr; + } + } + + if( pSmallest ) + std::rotate(aTmp.begin(),pSmallest,aTmp.end()); + + aRes.append(aTmp); + } + + // boxclipper & generic clipper disagree slightly on area-less + // polygons (one or two points only) + aRes = tools::stripNeutralPolygons(aRes); + + // now, sort all polygons with increasing 0th point + std::sort(aRes.begin(), + aRes.end(), + boost::bind( + &compare, + boost::bind( + &B2DPolygon::getB2DPoint, + _1,0), + boost::bind( + &B2DPolygon::getB2DPoint, + _2,0))); + + return aRes; + } + + void verifyPoly(const char* sName, const char* sSvg, const B2DPolyRange& toTest) + { + B2DPolyPolygon aTmp1; + CPPUNIT_ASSERT_MESSAGE(sName, + tools::importFromSvgD( + aTmp1, + rtl::OUString::createFromAscii(sSvg))); + + const rtl::OUString aSvg= + tools::exportToSvgD(toTest.solveCrossovers()); + B2DPolyPolygon aTmp2; + CPPUNIT_ASSERT_MESSAGE(sName, + tools::importFromSvgD( + aTmp2, + aSvg)); + + CPPUNIT_ASSERT_MESSAGE( + sName, + normalizePoly(aTmp2) == normalizePoly(aTmp1)); + } + + void verifyPoly() + { + const char* disjunct="m100 100v-200h-200v200zm1100 900v-200h-200v200z"; + const char* equal="m100 100v-200h-200v200zm200 0v-200h-200v200h200z"; + const char* intersectionN="m100 0v-100h-200v100zm200 100v-200-100h-200v100 200z"; + const char* intersectionE="m100 100v-200h-100v200zm200 0v-200h-200-100v200h100z"; + const char* intersectionS="m100 100v-200h-200v200 100h200v-100zm0 0v-100h-200v100z"; + const char* intersectionW="m0 100v-200h-100v200zm200 0v-200h-200-100v200h100z"; + const char* intersectionNE="m100 0v-100h-100v100zm200 0v-200h-200v100h-100v200h200v-100z"; + const char* intersectionSE="m200 200v-200h-100v-100h-200v200h100v100zm100-100v-100h-100v100z"; + const char* intersectionSW="m0 100v-100h-100v100zm200 0v-200h-200v100h-100v200h200v-100z"; + const char* intersectionNW="m100 100v-200h-100v-100h-200v200h100v100zm100-100v-100h-100v100z"; + const char* ringIntersection="m150 150v-100h-100v100zm300 0v-300h-200v-200h-300v300h200v100h-200v300h300v-200zm0-200v-100h-100v100z"; + const char* ringIntersection2="m-50-50v-100h-100v100zm100 200v-100h-100v100zm500 0v-300h-200v-200h-300v200h-200v300h200v200h300v-200zm-200-100v-100h100v100zm100-100v-100h-100v100zm100 200v-100h-100v100z"; + const char* ringIntersectExtraStrip="m-50-50v-100h-100v100zm100 200v-100h-100v100zm500 0v-300h-200v-200h-300v200h-200v300h200v200h300v-200zm-200-100v-100h100v25h-50v50h50v25zm150-25v-50h-150v50zm100-75v-100h-100v100zm100 200v-100h-100v100z"; + // TODO: old clipper impl. debug difference + //const char* complexIntersections="m100 0h-100v-100 100h-100 100v100-100zm0 0zm200 0h-100v-100h-100v-100 100h-100v100h-100 100v100h100v100-100h100v-100zm0 0h-100v-100 100h-100 100v100-100h100zm0 0v-100h-100-100v100 100h100 100v-100zm100 0v-100h-100v-100h-100-100v100h-100v100 100h100v100h100 100v-100h100v-100zm-200 0zm100 0v-100h-100-100v100 100h100 100v-100zm100 100v-200-100h-200-100-100v100 200 100h100 100 200v-100zm-200-100zm1000 1000v-200h-200v200z"; + const char* complexIntersections="m0 0zm0 0zm0 0zm0 0v-100 100h-100 100v100-100h100zm-100 0v-100 100h-100 100v100-100h100zm0 0v-100h-100-100v100 100h100 100v-100zm0 0v-100h-100-100v100 100h100 100v-100zm0 0v-100h-100v-100 100h-100v100h-100 100v100h100v100-100h100v-100h100zm-100-100v-100h-100-100v100h-100v100 100h100v100h100 100v-100h100v-100-100zm0 0v-100h-200-100-100v100 200 100h100 100 200v-100-200zm600 900v200h200v-200z"; + const char* randomIntersections="m63 457v-393h-43v393zm114 63v-8h-48v8zm-14 477v-127h-18v127zm693-923v-5h-119v5zm-260 457v-1h-14v1zm-220-375v-27h-8v27zm78 755v-22h-7v22zm203-774v-8h-158v8zm-108 375v-17h23v17zm813-19v-189h-21v-12h-26v-54h-17v-69h-25v-22h-62v-73h104v-5h-104-15v-17h-49v-1h-8v-16h-119v16h-386v18h-38-24v34h-23v26h-23v-26h-8v26h-18v27h18v339h8v-339h23v339h8v17h-8v13h8v5h-8v1h8v42h15v20h17v94h18 3v224h165v39h130v2h75v4h98v-4h153v-2h77v-20h4v-28h11v-218h-11v-27h3v-1h8v-17h-8v-63h8v-51h18v-32zm-581 32v-13h-14v13zm-78-78v-7h-32v7zm124 14v-21h-14v21zm595 32v-189h-26v-12h-4v-9h-13v-45h-13v-10h-12v-59h-62v-22h-26v-10h11v-63h15v-5h-15-49v-17h-8v-1h-119v1h-107v17h-279-38v34h-24v26h-23v27h23v284h-15v55h15v17h-15v13h15v5h-15v1h15v42h17v20h18v94h3 14v62h8v48h90v32h18v61h35v21h8v2h122v37h75v2h98v-2h153v-20h77v-28h4v-29h5v-40h-5v-149h-1v-27h1v-1h3v-17h-3v-46h3v-17-51h8v-32zm-563 2v-13h-14v13zm198 30v-13h-39v13zm204-43v-21h3v21zm-168-21v-21h-39v21zm306 0v-21h-5v21zm178 115v-272h-20v-12h-2v-54h-21v-164h1v-22h-27v-48h-743v47h-23v18h-71v24h-8v419h-39v19h60v156h66 32v202h-72v110h79v-88h11v39h3v48h621v-14h96v-82h35v-326zm-570-420v-4h-156v4zm63 481v-18h-11v18zm72 0v-25h-14v25zm465-112v-13h-5v13zm-46-43v-21h1v21zm-37-21v-21h-12v21zm-352 21v-21h23v21zm-23 30v-17h23v17zm-23 18v-5h23v5zm-23 82v-19h23v19zm272 75v-3h-35v3zm-76-192v-13h-39v13zm150 30v-13h-35v13zm-76 6v-1h-39v1zm11 106v-25h-11v25zm150 160v-14h-75v14zm318-304v-11h-13v-43h-2v-2h-10v-37h-4v37h-27v3h-31v-3-37h-5v37h-43v3h-2v21h2v21h-2v30h-1v-30h-8v-21h8v-21h-8v-3h-5v-62h5v-11h-5v-29h-8v-52h-15v-17-38h-15v-52h-89v16h-22v36h-175v55h-15v1h-25v51h-23v-41h-14v41h-2v105h-4v21h4v21h-4v13h4v17h-4-18v13h18v5h-18v1h18v42h4v11h2v9h14v-9h23v9h40v19h-40v25h40v2h82v2h75v43h-75v3h75 40v60h35v-60h23 34 12 15v-3h-15v-43h15v-48h10v-37h11v-31h1v1h45v30h5v-30h20v-1h11v1h8v30h19v20h3v-20h1v-30h10v-1h2v-32zm-146-329v-1h-2v1zm-117 211v-11 11zm-76 0v-11h-13v11zm13 65v-65h1v65zm-1 42v-21h1v16h35v5zm-36 30v-17h36v17zm-36 18v-5h36v5zm180-5v-13h-13v-17h5v-13h-5v-21h5v-21h-5v-3h-8v-62h8v-11h-8v-29h-9v-51h-6v-1-17h-15v-38h-54v-36h-35v36h-22v38h-67v17h-108v1h-15v51h-25v105h-23v-105h-14v105h-2v21h2v21h-2v13h2v17h-2-4v13h4v5h-4v1h4v42h2v11h14v-11h23v11h40v9h82v19h-82v25h82v2h75v2h40v43h-40v3h40 35 23v-3h-23v-43h23v-2h34v2h12v-2h6v-46h9v-20h8v-17h2v-26h-2v-5zm-127-64v-21 21zm89 51v-17h3v17zm-57-17v-13h-35v13zm58 61v-26h-19v-5h19v-13h-23v-17h23v-13h-23v-21h23v-21h-23v-65h23v-11h-23v-14h-35v-15 15h-22v14h-18v11h18v65h-18v21h18v16h22v5h-22v13h22v17h-22-18v13h18v5h-18v1h18v25h22v17h35v-17zm0-25v-1h-35v1zm-22-390v6h-175v5h-31v-15h228v4zm344 352v-189h-2v-12h-21v-54h-26v-164h26v-5h-26v-17h-119v-36h-562v35h-62v18h-23v34h-23v-10h-48v419h-8v8h8v5h71v5h-58v1h58v42h8v114h32 18v224h3v39h165v34h456v-32h77v-2h4v-20h11v-28h4v-218h-4v-28h36v-17h-36v-63h39v-83zm-50 0v-11h-1v-43h-3v-2h-6v-39h-4v-34h-13v-60h-12v-12h-31v72h-31v-72-9h-59v-17-38h-5v-59h-8v-5h8v-1h-8v-16h-2v16h-13v-11h-15v-5h-89v5h-22v11h-175v6h-15v7h-25v16h-43v36h-18v66h-54v-107h-32v107h-4v41h-8v105h-6v7h6v14h8v21h-8v13h8v17h-8-14v13h14v5h-14v1h14v42h8v20h90v19h-34v7h-15v68h26v-50h23v50h18 4v62h16v-62h15v110h8v10h3v22h119v11h75v50h75v-50h23v-11h34v11h48v-11h30v-22h21v-120h20v-3h11v3h30v-3h13-13v-27h13v-1h17v-17h-17v-46h17v-17h6v-51h3v-32zm-256-32v-21h-35v-65h35v-11h-35v-14 14h-22v11h22v65h-22v21h22v16-16zm89 69v-5h3v5zm-3 26v-26h-31v-5h31v-13h-31v-17h31v-13h-31v-21h31v-21h-31v-65h31v-11h-31v-14h-23v-15h-35v-51 51h-22v15h-18v14h-35v11h35v65h-35v21h35v16h18v5h-18v13h18v17h-18-36-39-61v13h61v5h-61v1h61v25h39v-25h36v25h18v17h22v11h35v-11h23v-17zm-19-25v-1h-4v-5h4v-13h-4-35-22v13h22v5h-22v1h22v25h35v-25zm23 252v-36h34v36zm-34-99v-43h34v43zm35-128v-26h-8v-5h8v-13h-8v-17h8v-13h-8v-21h8v-21h-8v-3h-9v-62h9v-11h-9v-29h-6v-51-1h-15v-17h-54v-38h-35v38h-22v11h-53v6h-14v1h-108v51h-15v105h-25v21h25v21h-25v13h25v17h-25-23-14-2v13h2v5h-2v1h2v42h14v-42h23v42h40v11h82v9h75v46h40v2h35v-2h23v-4h31v-42h3v46h12v-46h6v-20h9v-17zm-15-61v-13h-12v13zm12 30v-13h-12v13zm12 31v-26h-12v26zm12 131v-3h-12v3zm12 110v-14h-12v14zm27-241v-26h-9v-5h9v-13h-9v-17h9v-13h-9v-21h9v-21h-9v-3h-6v-62h6v-11h-6v-29-51h-15v-1h-54v-17h-35v11h-22v6h-53v1h-14v51h-108v105h-15v21h15v21h-15v13h15v17h-15-25v13h25v5h-25v1h25v25h15v17h21v6h61v5h75v9h18v42h22v4h35v-4h23v-42h31v-9h3v9h12v-9-11h6v-17zm0 0v-26h-6v-5h6v-13h-6v-17h6v-13h-6v-21h6v-21h-6v-3-62-11-29h-15v-51h-54v-1h-35v-6 6h-22v1h-53v51h-14v24h-87v81h-21v21h21v21h-21v13h21v17h-21-15v13h15v5h-15v1h15v25h21v17h61v6h39v-6h36v11h18v9h22v42h35v-42h23v-9h31v-11h3v11h12v-11-17zm0 0v-26-5-13-17-13-21-21-3h-12v3h-3v-65h15v-11h-15v-29h-54v-51h-35v-1 1h-22v51h-53v29h-1v-5h-13v5h-26v76h-61v21h61v21h-61v13h61v17h-61-21v13h21v5h-21v1h21v25h61v17h39v-17h36v17h18v11h22v9h35v-9h23v-11h31v-17h3v17h12v-17zm15-419v-12h-2v12zm186 356v-56h-8v-133h-4v-12h-13v-9h-13v-45h-12v-10h-62v-59-6h-26v-16h-33v-10h33v-12h-33v-22h-5v-29h49v-5h-49-8v-17h-119v17h-107-279v34h-38v26h-24v27h24v179h-7v105h-17v55h17v17h-17v13h17v5h-17v1h17v42h18v20h3v94h14 8v62h41v37h26v-37h23v48h18v32h35v61h8v21h122v2h75v37h98v-37h34v17h119v-57h11v29h66v-29h4v-40h-4v-26h3v-123h-3v-27h3v-1h1v-17h-1v-46h1v-17h3v-51-32zm0 0v-54h-4v-2h-3v-73h-10v-60h-13v-12h-12v-9h-31v9h-31v-9-55h-59v-59h-5v-5h5v-1h-5v-16h-8v-10h8v-12h-8v-22h-119v34h117v10h-28v-6h-89v6h-22v5h-175v11h-40v13h-147v11h-4v107h-8v41h-6v105h-22v21h28v21h-17v13h17v17h-14-3v13h3v5h-3v1h3v42h14v20h8v94h41 26 23 18v62h4v48h31v10h8v22h3v11h119v50h75v21h98v-71h34v71h48v-71h30v-11h21v-22h20v-120h11v120h43v-123h17-17v-27h17v-1h6v-17h-6v-46h6v-17h3v-51h1v-32zm-4 0v-11h-6v-43h-4v-2h-13v-39h-12v-34h-4v34h-27v2h-31v-2-34h-48v36h-2v37h-1v-73h-8v-29-52h-5v-17h-8v-38h-15v-59h-15v-6h-89v6h-22v7h-175v16h-15v36h-25v55h-39v11h-4v41h-18v105h-54v-105h-32v105h-4v7h4v14h86v21h-86v13h86v17h-86-4v13h4v5h-4v1h4v42h86v11h18v9h4v19h-4v25h4v50h16v-48h23v45h-8v3h8 122v96h-119v14h119v10h75v22h75v-22h23v-10h34v10h48v-24h-36v-36h15v-60h21v-3h-11v-43h2v15h9v-15h46v15h5v-15h20v-2h-20v-46h20v-37h11v37h8v46h-8v2h8v15h22v-15h1v-2h-1v-46h1v-17h12v-20h13v-31h4v-32zm-142 148v-2h-9v2zm9-2v-46h46v46zm-46 45v-28h46v28zm67-191v-11h-1v-42h-3v42h-19v11h19v32h3v-32zm-61 0v-11h-5v11zm96 0v-11h-4v-43h-13v-2h-2v-37h-10v-2h-4v2h-27v37h-31v-37-2h-5v2h-43v37h-2v3h-1v-3h-8v-62-11-29h-5v-52h-8v-17h-15v-38-52h-15v-7h-89v7h-22v16h-175v36h-15v55h-25v1h-37v10h-2v41h-4v105h-18v21h18v21h-18v13h18v17h-18-86v13h86v5h-86v1h86v42h18v11h4v9h2v19h-2v25h2v2h14v-2h23v2h40v2h82v43h-122v3h122 75v96h-75v14h75v10h75v-10h23v-14h-23v-36h23v-60h34v60h12v-60h15 10v-3h-10v-43h10v-48h11v-37h46v37h5v-37h20v-30h11v30h8v37h22v-17h1v-20h12v-31h13v-32zm-13 0v-11h-2v-43h-10v-2h-4v2h-19v1h-8v42h-31v-21-21-3h-5v3h-43v21h43v21h-43v11h43v19h-45v13h45v1h5v-1h20v-13h-20v-19h31v32h8v1h19v30h3v-30h1v-1h10v-32zm-72 148v-2h-5v2zm5 43h-5zm66-191v-11h-3v11zm-38 146v-46h11v46zm-11 45v-28h11v28zm-11 149v-4h11v4zm-11 40v-40h-8v40zm92-380v-54-2h-4v-133h-13v-12h-13v-9h-12v-45h-31v45h-31v-55-59h-59v-5h33v-1h-33v-16h-5v-10h5v-12h-5v-22h-8v-29h8v-5h-8-119-107v5h107v29h-386v26h-38v27h40v20h-4v11h-14v148h-22v105h-7v55h18v17h-18v13h18v5h-18v1h18v42h3v20h14v94h8 41v62h26v-62h23v62h18v48h4v10h31v22h8v61h122v21h75v2h98v-2h34v2h99v-84h20v-22h11v22h43v-22h23v-123h-6v-27h6v-1h3v-17h-3v-46h3v-17h1v-51h3v-32zm-43 148v-2h-22v2zm22 43h-30zm66 189v-40h-66v40zm41-380v-11h-10v-43h-4v1h-19v42h-8v11h8v32h19v1h3v-1h1v-32zm38 0v-11h-3v-43h-6v-2h-4v-39h-13v-34h-12v-60h-4v60h-27v34h-31v-34-72h-48v72h-3v-29h-8v-52-17h-5v-38h-8v-59h-15v-6h-15v-11h-89v11h-22v6h-175v7h-15v16h-25v36h-43v66h-18v41h-54v-41h-32v41h-4v105h-8v7h8v14h4v21h-4v13h4v17h-4-8v13h8v5h-8v1h8v42h4v11h86v9h18v19h-18v25h18v50h4 16 15 8v110h3v10h119v22h75v11h75v-11h23v-22h34v22h48v-22h30v-24h-30v-96h51v-3h20-20v-28h20v-15h11v15h8v1h22v-1h13v-17h-12v-46h12v-17h17v-51h6v-32z"; + + verifyPoly("disjunct", disjunct, aDisjunctRanges); + verifyPoly("equal", equal, aEqualRanges); + verifyPoly("intersectionN", intersectionN, aIntersectionN); + verifyPoly("intersectionE", intersectionE, aIntersectionE); + verifyPoly("intersectionS", intersectionS, aIntersectionS); + verifyPoly("intersectionW", intersectionW, aIntersectionW); + verifyPoly("intersectionNE", intersectionNE, aIntersectionNE); + verifyPoly("intersectionSE", intersectionSE, aIntersectionSE); + verifyPoly("intersectionSW", intersectionSW, aIntersectionSW); + verifyPoly("intersectionNW", intersectionNW, aIntersectionNW); + verifyPoly("ringIntersection", ringIntersection, aRingIntersection); + verifyPoly("ringIntersection2", ringIntersection2, aRingIntersection2); + verifyPoly("ringIntersectExtraStrip", ringIntersectExtraStrip, aRingIntersectExtraStrip); + verifyPoly("complexIntersections", complexIntersections, aComplexIntersections); + verifyPoly("randomIntersections", randomIntersections, aRandomIntersections); + } + + void dumpSvg(const char* pName, + const ::basegfx::B2DPolyPolygon& rPoly) + { + (void)pName; (void)rPoly; +#if defined(VERBOSE) + fprintf(stderr, "%s - svg:d=\"%s\"\n", + pName, rtl::OUStringToOString( + basegfx::tools::exportToSvgD(rPoly), + RTL_TEXTENCODING_UTF8).getStr() ); +#endif + } + + void getPolyPolygon() + { + dumpSvg("disjunct",aDisjunctRanges.solveCrossovers()); + dumpSvg("equal",aEqualRanges.solveCrossovers()); + dumpSvg("intersectionN",aIntersectionN.solveCrossovers()); + dumpSvg("intersectionE",aIntersectionE.solveCrossovers()); + dumpSvg("intersectionS",aIntersectionS.solveCrossovers()); + dumpSvg("intersectionW",aIntersectionW.solveCrossovers()); + dumpSvg("intersectionNE",aIntersectionNE.solveCrossovers()); + dumpSvg("intersectionSE",aIntersectionSE.solveCrossovers()); + dumpSvg("intersectionSW",aIntersectionSW.solveCrossovers()); + dumpSvg("intersectionNW",aIntersectionNW.solveCrossovers()); + dumpSvg("ringIntersection",aRingIntersection.solveCrossovers()); + dumpSvg("ringIntersection2",aRingIntersection2.solveCrossovers()); + dumpSvg("aRingIntersectExtraStrip",aRingIntersectExtraStrip.solveCrossovers()); + dumpSvg("complexIntersections",aComplexIntersections.solveCrossovers()); + dumpSvg("randomIntersections",aRandomIntersections.solveCrossovers()); + + CPPUNIT_ASSERT_MESSAGE("getPolyPolygon", true ); + } + + void validatePoly( const char* pName, const B2DPolyRange& rRange ) + { + B2DPolyPolygon genericClip; + const sal_uInt32 nCount=rRange.count(); + for( sal_uInt32 i=0; i<nCount; ++i ) + { + B2DPolygon aRect=tools::createPolygonFromRect( + rRange.getElement(i).head); + if( rRange.getElement(i).tail.head == ORIENTATION_NEGATIVE ) + aRect.flip(); + + genericClip.append(aRect); + } + +#if defined(VERBOSE) + fprintf(stderr, "%s input - svg:d=\"%s\"\n", + pName, rtl::OUStringToOString( + basegfx::tools::exportToSvgD( + genericClip), + RTL_TEXTENCODING_UTF8).getStr() ); +#endif + + const B2DPolyPolygon boxClipResult=rRange.solveCrossovers(); + const rtl::OUString boxClipSvg( + basegfx::tools::exportToSvgD( + normalizePoly( + boxClipResult))); +#if defined(VERBOSE) + fprintf(stderr, "%s boxclipper - svg:d=\"%s\"\n", + pName, rtl::OUStringToOString( + boxClipSvg, + RTL_TEXTENCODING_UTF8).getStr() ); +#endif + + genericClip = tools::solveCrossovers(genericClip); + const rtl::OUString genericClipSvg( + basegfx::tools::exportToSvgD( + normalizePoly( + genericClip))); +#if defined(VERBOSE) + fprintf(stderr, "%s genclipper - svg:d=\"%s\"\n", + pName, rtl::OUStringToOString( + genericClipSvg, + RTL_TEXTENCODING_UTF8).getStr() ); +#endif + + CPPUNIT_ASSERT_MESSAGE(pName, + genericClipSvg == boxClipSvg); + } + + void validatePoly() + { + validatePoly("disjunct", aDisjunctRanges); + validatePoly("equal", aEqualRanges); + validatePoly("intersectionN", aIntersectionN); + validatePoly("intersectionE", aIntersectionE); + validatePoly("intersectionS", aIntersectionS); + validatePoly("intersectionW", aIntersectionW); + validatePoly("intersectionNE", aIntersectionNE); + validatePoly("intersectionSE", aIntersectionSE); + validatePoly("intersectionSW", aIntersectionSW); + validatePoly("intersectionNW", aIntersectionNW); + // subtle differences on Solaris Intel, comparison not smart enough + // (due to floating point inaccuracies) + //validatePoly("ringIntersection", aRingIntersection); + //validatePoly("ringIntersection2", aRingIntersection2); + //validatePoly("ringIntersectExtraStrip", aRingIntersectExtraStrip); + // generic clipper buggy here, likely + //validatePoly("complexIntersections", aComplexIntersections); + //validatePoly("randomIntersections", aRandomIntersections); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(boxclipper); + CPPUNIT_TEST(validatePoly); + CPPUNIT_TEST(verifyPoly); + CPPUNIT_TEST(getPolyPolygon); + CPPUNIT_TEST_SUITE_END(); +}; + +// ----------------------------------------------------------------------------- +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::boxclipper); +} // namespace basegfx2d diff --git a/basegfx/test/clipstate.cxx b/basegfx/test/clipstate.cxx new file mode 100644 index 000000000000..48c1e5967260 --- /dev/null +++ b/basegfx/test/clipstate.cxx @@ -0,0 +1,180 @@ +/************************************************************************* + * + * 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" +// autogenerated file with codegen.pl + +#include "preextstl.h" +#include "cppunit/TestAssert.h" +#include "cppunit/TestFixture.h" +#include "cppunit/extensions/HelperMacros.h" +#include "postextstl.h" + +#include <basegfx/tools/b2dclipstate.hxx> +#include <basegfx/range/b2dpolyrange.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <boost/bind.hpp> + +using namespace ::basegfx; + + +namespace basegfx2d +{ + +class clipstate : public CppUnit::TestFixture +{ +private: + tools::B2DClipState aUnion1; + tools::B2DClipState aUnion2; + tools::B2DClipState aIntersect; + tools::B2DClipState aXor; + tools::B2DClipState aSubtract; + +public: + void setUp() + { + B2DRange aCenter(100, 100, -100, -100); + B2DRange aNorth(-10, -110, 10, -90); + B2DRange aWest(-110, -10, -90, 10); + B2DRange aSouth(-10, 110, 10, 90); + B2DRange aEast(110, -10, 90, 10); + + aUnion1.unionRange(aCenter); + aUnion1.unionRange(aNorth); + aUnion1.unionRange(aWest); + aUnion1.unionRange(aSouth); + aUnion1.unionRange(aEast); + + aUnion2.makeNull(); + aUnion2.unionRange(aCenter); + aUnion2.unionRange(aNorth); + aUnion2.unionRange(aWest); + aUnion2.unionRange(aSouth); + aUnion2.unionRange(aEast); + + aIntersect.intersectRange(aCenter); + aIntersect.intersectRange(aNorth); + aIntersect.intersectRange(aWest); + aIntersect.intersectRange(aSouth); + aIntersect.intersectRange(aEast); + + aXor.makeNull(); + aXor.xorRange(aCenter); + aXor.xorRange(aNorth); + aXor.xorRange(aWest); + aXor.xorRange(aSouth); + aXor.xorRange(aEast); + + aSubtract.intersectRange(aCenter); + aSubtract.subtractRange(aNorth); + aSubtract.subtractRange(aWest); + aSubtract.subtractRange(aSouth); + aSubtract.subtractRange(aEast); + } + + void tearDown() + {} + + void verifyPoly(const char* sName, const char* sSvg, const tools::B2DClipState& toTest) + { +#if defined(VERBOSE) + fprintf(stderr, "%s - svg:d=\"%s\"\n", + sName, rtl::OUStringToOString( + basegfx::tools::exportToSvgD(toTest.getClipPoly()), + RTL_TEXTENCODING_UTF8).getStr() ); +#endif + + B2DPolyPolygon aTmp1; + CPPUNIT_ASSERT_MESSAGE(sName, + tools::importFromSvgD( + aTmp1, + rtl::OUString::createFromAscii(sSvg))); + + const rtl::OUString aSvg= + tools::exportToSvgD(toTest.getClipPoly()); + B2DPolyPolygon aTmp2; + CPPUNIT_ASSERT_MESSAGE(sName, + tools::importFromSvgD( + aTmp2, + aSvg)); + + CPPUNIT_ASSERT_MESSAGE( + sName, + aTmp2 == aTmp1); + } + + void verifySimpleRange() + { + const char* unionSvg="m100 10v90h-90v10h-20v-10h-90v-90h-10v-20h10v-90h90v-10h20v10h90v90h10v20z"; + const char* intersectSvg="m-100 10v-20h10v20zm80 90v-10h20v10zm-20-190v-10h20v10zm80 100v-20h10v20z"; + const char* xorSvg="m-100 10h10v-20h-10zm90 110h20v-10h-20zm0-180h20v-10h-20zm100 110h10v-20h-10zm10 20v90h-90v10h-20v-10h-90v-90h-10v-20h10v-90h90v-10h20v10h90v90h10v20z"; + const char* subtractSvg="m-90 10v-20h-10v-90h90v10h20v-10h90v90h-10v20h10v90h-90v-10h-20v10h-90v-90z"; + + CPPUNIT_ASSERT_MESSAGE("cleared clip stays empty under union operation", + aUnion1.isCleared()); + verifyPoly("union", unionSvg, aUnion2); + verifyPoly("intersect", intersectSvg, aIntersect); + verifyPoly("xor", xorSvg, aXor); + verifyPoly("subtract", subtractSvg, aSubtract); + } + + void verifyMixedClips() + { + tools::B2DClipState aMixedClip; + + const char* unionSvg="m100 10v90h-90v10h-20v-10h-90v-90h-10v-20h10v-90h90v-10h20v10h90v90h10v20z"; + + B2DPolyPolygon aTmp1; + tools::importFromSvgD( + aTmp1, + rtl::OUString::createFromAscii(unionSvg)); + + aMixedClip.intersectPolyPolygon(aTmp1); + aMixedClip.subtractRange(B2DRange(-20,-150,20,0)); + aMixedClip.subtractRange(B2DRange(-150,-20,0,20)); + aMixedClip.xorRange(B2DRange(-150,-150,150,150)); + + const char* mixedClipSvg="m0 0v20h-100v80h90v10h20v-10h90v-90h10v-20h-10v-90h-80v100zm-40-20v-80h-80v80zm-50 170v-300h300v300z"; + verifyPoly("mixed clip", mixedClipSvg, aMixedClip); + } + + CPPUNIT_TEST_SUITE(clipstate); + CPPUNIT_TEST(verifySimpleRange); + CPPUNIT_TEST(verifyMixedClips); + CPPUNIT_TEST_SUITE_END(); +}; + +// ----------------------------------------------------------------------------- +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::clipstate); +} // namespace basegfx2d diff --git a/basegfx/test/export.map b/basegfx/test/export.map new file mode 100644 index 000000000000..3308588ef6f8 --- /dev/null +++ b/basegfx/test/export.map @@ -0,0 +1,34 @@ +#************************************************************************* +# +# 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. +# +#************************************************************************* + +UDK_3_0_0 { + global: + cppunitTestPlugIn; + + local: + *; +}; diff --git a/basegfx/test/genericclipper.cxx b/basegfx/test/genericclipper.cxx new file mode 100644 index 000000000000..d6c97c0567dc --- /dev/null +++ b/basegfx/test/genericclipper.cxx @@ -0,0 +1,161 @@ +/************************************************************************* + * + * 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" +// autogenerated file with codegen.pl + +#include "preextstl.h" +#include "cppunit/TestAssert.h" +#include "cppunit/TestFixture.h" +#include "cppunit/extensions/HelperMacros.h" +#include "postextstl.h" + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <basegfx/curve/b2dbeziertools.hxx> +#include <basegfx/range/b2dpolyrange.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <boost/bind.hpp> + +using namespace ::basegfx; + + +namespace basegfx2d +{ + +class genericclipper : public CppUnit::TestFixture +{ +private: + B2DPolygon aSelfIntersecting; + B2DPolygon aShiftedRectangle; + +public: + // initialise your test code values here. + void setUp() + { + aSelfIntersecting.append(B2DPoint(0, 0)); + aSelfIntersecting.append(B2DPoint(0, 100)); + aSelfIntersecting.append(B2DPoint(75, 100)); + aSelfIntersecting.append(B2DPoint(75, 50)); + aSelfIntersecting.append(B2DPoint(25, 50)); + aSelfIntersecting.append(B2DPoint(25, 150)); + aSelfIntersecting.append(B2DPoint(100,150)); + aSelfIntersecting.append(B2DPoint(100,0)); + aSelfIntersecting.setClosed(true); + + aShiftedRectangle = tools::createPolygonFromRect( + B2DRange(0,90,20,150)); + } + + void tearDown() + {} + + void validate(const char* pName, + const char* pValidSvgD, + B2DPolyPolygon (*pFunc)(const B2DPolyPolygon&, const B2DPolyPolygon&)) + { + const B2DPolyPolygon aSelfIntersect( + tools::prepareForPolygonOperation(aSelfIntersecting)); + const B2DPolyPolygon aRect( + tools::prepareForPolygonOperation(aShiftedRectangle)); +#if defined(VERBOSE) + fprintf(stderr, "%s input LHS - svg:d=\"%s\"\n", + pName, rtl::OUStringToOString( + basegfx::tools::exportToSvgD( + aSelfIntersect), + RTL_TEXTENCODING_UTF8).getStr() ); + fprintf(stderr, "%s input RHS - svg:d=\"%s\"\n", + pName, rtl::OUStringToOString( + basegfx::tools::exportToSvgD( + aRect), + RTL_TEXTENCODING_UTF8).getStr() ); +#endif + + const B2DPolyPolygon aRes= + pFunc(aSelfIntersect, aRect); + +#if defined(VERBOSE) + fprintf(stderr, "%s - svg:d=\"%s\"\n", + pName, rtl::OUStringToOString( + basegfx::tools::exportToSvgD(aRes), + RTL_TEXTENCODING_UTF8).getStr() ); +#endif + + rtl::OUString aValid=rtl::OUString::createFromAscii(pValidSvgD); + + CPPUNIT_ASSERT_MESSAGE(pName, + basegfx::tools::exportToSvgD(aRes) == aValid); + } + + void validateOr() + { + const char* pValid="m0 0h100v150h-75v-50h-5v50h-20v-50-10zm75 10v-50h-50v50z"; + validate("validateOr", pValid, &tools::solvePolygonOperationOr); + } + + void validateXor() + { + const char* pValid="m0 0h100v150h-75v-50h-5v50h-20v-50-10zm0 10h20v-10h-20zm75 10v-50h-50v50z"; + validate("validateXor", pValid, &tools::solvePolygonOperationXor); + } + + void validateAnd() + { + const char* pValid="m0 100v-10h20v10z"; + validate("validateAnd", pValid, &tools::solvePolygonOperationAnd); + } + + void validateDiff() + { + const char* pValid="m0 90v-90h100v150h-75v-50h-5v-10zm55 10v-50h-50v50z"; + validate("validateDiff", pValid, &tools::solvePolygonOperationDiff); + } + + // Change the following lines only, if you add, remove or rename + // member functions of the current class, + // because these macros are need by auto register mechanism. + + CPPUNIT_TEST_SUITE(genericclipper); + CPPUNIT_TEST(validateOr); + CPPUNIT_TEST(validateXor); + CPPUNIT_TEST(validateAnd); + CPPUNIT_TEST(validateDiff); + CPPUNIT_TEST_SUITE_END(); +}; + +// ----------------------------------------------------------------------------- +CPPUNIT_TEST_SUITE_REGISTRATION(basegfx2d::genericclipper); +} // namespace basegfx2d diff --git a/basegfx/test/makefile.mk b/basegfx/test/makefile.mk new file mode 100644 index 000000000000..09d8b805f9f5 --- /dev/null +++ b/basegfx/test/makefile.mk @@ -0,0 +1,87 @@ +#************************************************************************* +# +# 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=tests + +ENABLE_EXCEPTIONS=TRUE + +# --- Settings ----------------------------------------------------- + +.INCLUDE : settings.mk + +#building with stlport, but cppunit was not built with stlport +.IF "$(USE_SYSTEM_STL)"!="YES" +.IF "$(SYSTEM_CPPUNIT)"=="YES" +CFLAGSCXX+=-DADAPT_EXT_STL +.ENDIF +.ENDIF + +CFLAGSCXX += $(CPPUNIT_CFLAGS) + +# --- Common ---------------------------------------------------------- + +SHL1OBJS= \ + $(SLO)$/basegfx1d.obj \ + $(SLO)$/basegfx2d.obj \ + $(SLO)$/basegfx3d.obj \ + $(SLO)$/boxclipper.obj \ + $(SLO)$/basegfxtools.obj \ + $(SLO)$/clipstate.obj \ + $(SLO)$/genericclipper.obj \ + $(SLO)$/testtools.obj + +SHL1TARGET= basegfx_tests +SHL1STDLIBS= \ + $(BASEGFXLIB) \ + $(SALLIB) \ + $(CPPUHELPERLIB) \ + $(CPPULIB) \ + $(CPPUNITLIB) + +SHL1IMPLIB= i$(SHL1TARGET) + +DEF1NAME =$(SHL1TARGET) +SHL1VERSIONMAP = export.map +SHL1RPATH = NONE + +# END ------------------------------------------------------------------ + +#------------------------------- All object files ------------------------------- +# do this here, so we get right dependencies +SLOFILES=$(SHL1OBJS) + +# --- Targets ------------------------------------------------------ + +.INCLUDE : target.mk +.INCLUDE : _cppunit.mk + +.IF "$(verbose)"!="" || "$(VERBOSE)"!="" +CDEFS+= -DVERBOSE +.ENDIF diff --git a/basegfx/test/testtools.cxx b/basegfx/test/testtools.cxx new file mode 100644 index 000000000000..e96eb4d8edce --- /dev/null +++ b/basegfx/test/testtools.cxx @@ -0,0 +1,235 @@ +/************************************************************************* + * + * 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 "testtools.hxx" + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include <algorithm> + + +namespace basegfx +{ + namespace testtools + { + Plotter::Plotter( ::std::ostream& rOutputStream ) : + mrOutputStream(rOutputStream), + maPoints(), + mbFirstElement( true ) + { + // output gnuplot setup. We switch gnuplot to parametric + // mode, therefore every plot has at least _two_ + // functions: one for the x and one for the y value, both + // depending on t. + mrOutputStream << "#!/usr/bin/gnuplot -persist" << ::std::endl + << "#" << ::std::endl + << "# automatically generated by basegfx::testtools::Plotter, don't change!" << ::std::endl + << "#" << ::std::endl + << "set parametric" << ::std::endl + // This function plots a cubic bezier curve. P,q,r,s + // are the control point elements of the corresponding + // output coordinate component (i.e. x components for + // the x plot, and y components for the y plot) + << "cubicBezier(p,q,r,s,t) = p*(1-t)**3+q*3*(1-t)**2*t+r*3*(1-t)*t**2+s*t**3" << ::std::endl + + // This function plots the derivative of a cubic + // bezier curve. P,q,r,s are the control point + // components of the _original_ curve + << "cubicBezDerivative(p,q,r,s,t) = 3*(q-p)*(1-t)**2+6*(r-q)*(1-t)*t+3*(s-r)*t**2" << ::std::endl + + // Plot a line's x component of a line in implicit + // form ax + by + c = 0 + << "implicitLineX(a,b,c,t) = a*-c + t*-b" << ::std::endl + + // Plot a line's y component of a line in implicit + // form ax + by + c = 0 + << "implicitLineY(a,b,c,t) = b*-c + t*a" << ::std::endl + + // Plot a line's component of a line between a and b + // (where a and b should be the corresponding + // components of the line's start and end point, + // respectively) + << "line(a,b,t) = a*(1-t) + b*t" << ::std::endl << ::std::endl + << "# end of setup" << ::std::endl << ::std::endl + + // Start the actual plot line + << "plot [t=0:1] "; + } + + namespace + { + class PointWriter + { + public: + PointWriter( ::std::ostream& rOutputStream ) : + mrOutputStream( rOutputStream ) + { + } + + void operator()( const B2DPoint& rPoint ) const + { + mrOutputStream << rPoint.getX() << "\t" << rPoint.getY() << ::std::endl; + mrOutputStream << "e" << ::std::endl; + } + + private: + ::std::ostream& mrOutputStream; + }; + } + + Plotter::~Plotter() + { + // End the plot line + mrOutputStream << ::std::endl; + + // write stored data points. Cannot write before, since + // this is an inline dataset, which must be after the plot <...> + // line + ::std::for_each( maPoints.begin(), maPoints.end(), PointWriter(mrOutputStream) ); + } + + void Plotter::plot( const B2DPolygon& rPoly ) + { + const sal_uInt32 pointCount( rPoly.count() ); + + if( pointCount < 1 ) + return; + + if( pointCount == 1 ) + { + plot( rPoly.getB2DPoint(0) ); + return; + } + + sal_uInt32 i; + for( i=0; i<pointCount-1; ++i ) + { + if(rPoly.isNextControlPointUsed(i) || rPoly.isPrevControlPointUsed(i + 1)) + { + const B2DCubicBezier aBezierPlot( + rPoly.getB2DPoint(i), rPoly.getNextControlPoint(i), + rPoly.getPrevControlPoint(i + 1), rPoly.getB2DPoint(i + 1)); + + plot(aBezierPlot); + } + else + { + plot( rPoly.getB2DPoint(i), rPoly.getB2DPoint(i+1) ); + } + } + } + + void Plotter::plot( const B2DPolyPolygon& rPolyPoly ) + { + const sal_uInt32 nPolyCount( rPolyPoly.count() ); + + sal_uInt32 i; + for( i=0; i<nPolyCount; ++i ) + { + plot( rPolyPoly.getB2DPolygon(i) ); + } + } + + void Plotter::plot( const B2DPoint& rPoint ) + { + maPoints.push_back( rPoint ); + writeSeparator(); + mrOutputStream << "'-' using ($1):($2) title \"Point " << maPoints.size() << "\" with points"; + } + + void Plotter::plot( const B2DRange& rRect ) + { + // TODO: do that also as a data file plot. maPoints must + // then become polymorph, but WTF. + + // decompose into four lines + plot( B2DPoint(rRect.getMinX(), + rRect.getMinY()), + B2DPoint(rRect.getMaxX(), + rRect.getMinY()) ); + plot( B2DPoint(rRect.getMaxX(), + rRect.getMinY()), + B2DPoint(rRect.getMaxX(), + rRect.getMaxY()) ); + plot( B2DPoint(rRect.getMaxX(), + rRect.getMaxY()), + B2DPoint(rRect.getMinX(), + rRect.getMaxY()) ); + plot( B2DPoint(rRect.getMinX(), + rRect.getMaxY()), + B2DPoint(rRect.getMinX(), + rRect.getMinY()) ); + } + + void Plotter::plot( const B2DPoint& rStartPoint, const B2DPoint& rEndPoint ) + { + writeSeparator(); + mrOutputStream << "line(" << rStartPoint.getX() + << "," << rEndPoint.getX() + << ",t), " + << "line(" << rStartPoint.getY() + << "," << rEndPoint.getY() + << ",t)"; + } + + void Plotter::plot( const B2DCubicBezier& rCurve ) + { + writeSeparator(); + mrOutputStream << "cubicBezier(" << rCurve.getStartPoint().getX() + << "," << rCurve.getControlPointA().getX() + << "," << rCurve.getControlPointB().getX() + << "," << rCurve.getEndPoint().getX() + << ",t), " + << "cubicBezier(" << rCurve.getStartPoint().getY() + << "," << rCurve.getControlPointA().getY() + << "," << rCurve.getControlPointB().getY() + << "," << rCurve.getEndPoint().getY() + << ",t)"; + } + + void Plotter::writeSeparator() + { + if( mbFirstElement ) + { + mbFirstElement = false; + } + else + { + mrOutputStream << ", "; + } + } + + } +} diff --git a/basegfx/test/testtools.hxx b/basegfx/test/testtools.hxx new file mode 100644 index 000000000000..1837f9f31a89 --- /dev/null +++ b/basegfx/test/testtools.hxx @@ -0,0 +1,98 @@ +/************************************************************************* + * + * 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. + * + ************************************************************************/ + +#ifndef _BASEGFX_TESTTOOLS_HXX +#define _BASEGFX_TESTTOOLS_HXX + +#include <basegfx/point/b2dpoint.hxx> + +#include <vector> +#include <iostream> + + +namespace basegfx +{ + class B2DCubicBezier; + class B2DPolygon; + class B2DPolyPolygon; + class B2DRange; + + namespace testtools + { + class Plotter + { + public: + /** Create a plotter for the given output stream + + This class can be used to generate gnuplot scripts for + a number of basegfx graphics primitives, useful for + debugging, regression-testing and comparing basegfx. + */ + Plotter( ::std::ostream& rOutputStream ); + + /** Delete the plotter + + This implicitely flushes all potential pending writes + to the output stream + */ + ~Plotter(); + + /** Plot a 2d polygon into the current graph + */ + void plot( const B2DPolygon& rPoly ); + + /** Plot a 2d polyPolygon into the current graph + */ + void plot( const B2DPolyPolygon& rPolyPoly ); + + /** Plot a 2d point into the current graph + */ + void plot( const B2DPoint& rPoint ); + + /** Plot a 2d rectangle into the current graph + */ + void plot( const B2DRange& rRect ); + + /** Plot a 2d line into the current graph + */ + void plot( const B2DPoint& rStartPoint, const B2DPoint& rEndPoint ); + + /** Plot a 2d cubic bezier curve into the current graph + */ + void plot( const B2DCubicBezier& rCurve ); + + private: + void writeSeparator(); + + ::std::ostream& mrOutputStream; + ::std::vector< B2DPoint > maPoints; + bool mbFirstElement; + }; + } +} + +#endif /* _BASEGFX_TESTTOOLS_HXX */ diff --git a/basegfx/util/basegfx.flt b/basegfx/util/basegfx.flt new file mode 100644 index 000000000000..57bb2e2ff324 --- /dev/null +++ b/basegfx/util/basegfx.flt @@ -0,0 +1,6 @@ +__CT +__real ++getImplementation +Impl +IMP +internal
\ No newline at end of file diff --git a/basegfx/util/makefile.mk b/basegfx/util/makefile.mk new file mode 100644 index 000000000000..4d5d92ce7551 --- /dev/null +++ b/basegfx/util/makefile.mk @@ -0,0 +1,85 @@ +#************************************************************************* +# +# 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=basegfx + +# --- Settings --------------------------------------------------- + +.INCLUDE : settings.mk + +# --- Allgemein --------------------------------------------------- + +LIB1TARGET=$(SLB)$/basegfx.lib +LIB1FILES=\ + $(SLB)$/curve.lib \ + $(SLB)$/matrix.lib \ + $(SLB)$/numeric.lib \ + $(SLB)$/point.lib \ + $(SLB)$/polygon.lib \ + $(SLB)$/range.lib \ + $(SLB)$/tuple.lib \ + $(SLB)$/tools.lib \ + $(SLB)$/vector.lib \ + $(SLB)$/color.lib \ + $(SLB)$/pixel.lib \ + $(SLB)$/raster.lib + +SHL1TARGET= basegfx$(DLLPOSTFIX) +.IF "$(GUI)" == "OS2" +SHL1TARGET= bgfx +.ENDIF +SHL1IMPLIB= ibasegfx + +SHL1STDLIBS=\ + $(SALLIB) \ + $(CPPUHELPERLIB) \ + $(CPPULIB) + +SHL1DEF= $(MISC)$/$(SHL1TARGET).def +SHL1LIBS= $(SLB)$/basegfx.lib + +DEF1NAME =$(SHL1TARGET) +DEF1DEPN =$(MISC)$/$(SHL1TARGET).flt \ + $(LIB1TARGET) + +DEF1DES =BaseGFX +DEFLIB1NAME =basegfx + +LIB2TARGET=$(LB)$/$(TARGET)_s.lib +LIB2ARCHIV=$(LB)$/lib$(TARGET)_s.a +LIB2FILES=$(LIB1FILES) + +# --- Targets ----------------------------------------------------------- + +.INCLUDE : target.mk + +$(MISC)$/$(SHL1TARGET).flt : makefile.mk + @$(TYPE) $(TARGET).flt > $@ + |