diff options
Diffstat (limited to 'basegfx/source')
79 files changed, 31255 insertions, 0 deletions
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..f7f26c6dd843 --- /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 - h)/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..80bd8922160b --- /dev/null +++ b/basegfx/source/curve/b2dcubicbezier.cxx @@ -0,0 +1,1106 @@ +/************************************************************************* + * + * 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 aRelativeEndPoint(maEndPoint-maStartPoint); + const double fAX = 3 * (maControlPointA.getX() - maControlPointB.getX()) + aRelativeEndPoint.getX(); + const double fBX = 2 * maControlPointA.getX() - maControlPointB.getX() - maStartPoint.getX(); + double fCX(maControlPointA.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); + // same as above but for very small fAX and/or fCX + // this has much better numerical stability + // see NRC chapter 5-6 (thanks THB!) + const double fQ = fBX + ((fBX >= 0) ? +fS : -fS); + impCheckExtremumResult(fQ / fAX, rResults); + 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 + const double fAY = 3 * (maControlPointA.getY() - maControlPointB.getY()) + aRelativeEndPoint.getY(); + const double fBY = 2 * maControlPointA.getY() - maControlPointB.getY() - maStartPoint.getY(); + double fCY(maControlPointA.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 ) + { + const double fS = sqrt(fD); + // same as above but for very small fAX and/or fCX + // this has much better numerical stability + // see NRC chapter 5-6 (thanks THB!) + const double fQ = fBY + ((fBY >= 0) ? +fS : -fS); + impCheckExtremumResult(fQ / fAY, rResults); + 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),-BEZIERx(t)) + // 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 * (maEndPoint.getX() - maControlPointB.getX()) * aRelativeEndPoint.getY() + - 3 * (maEndPoint.getY() - maControlPointB.getY()) * aRelativeEndPoint.getX(); + const double fB = (maControlPointB.getX() - maControlPointA.getX()) * aRelativeEndPoint.getY() + - (maControlPointB.getY() - maControlPointA.getY()) * aRelativeEndPoint.getX(); + const double fC = (maControlPointA.getX() - maStartPoint.getX()) * aRelativeEndPoint.getY() + - (maControlPointA.getY() - maStartPoint.getY()) * aRelativeEndPoint.getX(); + + // test for degenerated case: non-cubic curve + if( fTools::equalZero(fA) ) + { + // test for degenerated case: straight line + if( fTools::equalZero(fB) ) + return 0; + + // degenerated case: quadratic bezier + pResult[0] = -fC / (2*fB); + + // test root: 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 the first root + const double fS = sqrt(fD); + const double fQ = fB + ((fB >= 0) ? +fS : -fS); + pResult[0] = fQ / fA; + // test root: ignore it when it is outside the curve + int nCount = ((pResult[0] > 0) && (pResult[0] < 1)); + + // ignore multiplicit roots + if( !fTools::equalZero(fD) ) + { + // calculate the second root + const double fRoot = fC / fQ; + pResult[ nCount ] = fC / fQ; + // test root: ignore it when it is outside the curve + nCount += ((fRoot > 0) && (fRoot < 1)); + } + + 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..0f294d6a262f --- /dev/null +++ b/basegfx/source/matrix/b2dhommatrixtools.cxx @@ -0,0 +1,373 @@ +/************************************************************************* + * + * 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> + +/////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + 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..dc16938a3f99 --- /dev/null +++ b/basegfx/source/polygon/b2dpolygon.cxx @@ -0,0 +1,1592 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <osl/diagnose.h> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <rtl/instance.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <boost/scoped_ptr.hpp> +#include <vector> +#include <algorithm> + +////////////////////////////////////////////////////////////////////////////// + +class CoordinateData2D +{ + basegfx::B2DPoint maPoint; + +public: + CoordinateData2D() + : maPoint() + {} + + explicit CoordinateData2D(const basegfx::B2DPoint& rData) + : maPoint(rData) + {} + + const basegfx::B2DPoint& getCoordinate() const + { + return maPoint; + } + + void setCoordinate(const basegfx::B2DPoint& rValue) + { + if(rValue != maPoint) + maPoint = rValue; + } + + bool operator==(const CoordinateData2D& rData ) const + { + return (maPoint == rData.getCoordinate()); + } + + void transform(const basegfx::B2DHomMatrix& rMatrix) + { + maPoint *= rMatrix; + } +}; + +////////////////////////////////////////////////////////////////////////////// + +class CoordinateDataArray2D +{ + typedef ::std::vector< CoordinateData2D > CoordinateData2DVector; + + CoordinateData2DVector maVector; + +public: + explicit CoordinateDataArray2D(sal_uInt32 nCount) + : maVector(nCount) + { + } + + explicit CoordinateDataArray2D(const CoordinateDataArray2D& rOriginal) + : maVector(rOriginal.maVector) + { + } + + CoordinateDataArray2D(const CoordinateDataArray2D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount) + : maVector(rOriginal.maVector.begin() + nIndex, rOriginal.maVector.begin() + (nIndex + nCount)) + { + } + + sal_uInt32 count() const + { + return maVector.size(); + } + + bool operator==(const CoordinateDataArray2D& rCandidate) const + { + return (maVector == rCandidate.maVector); + } + + const basegfx::B2DPoint& getCoordinate(sal_uInt32 nIndex) const + { + return maVector[nIndex].getCoordinate(); + } + + void setCoordinate(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue) + { + maVector[nIndex].setCoordinate(rValue); + } + + void reserve(sal_uInt32 nCount) + { + maVector.reserve(nCount); + } + + void append(const CoordinateData2D& rValue) + { + maVector.push_back(rValue); + } + + void insert(sal_uInt32 nIndex, const CoordinateData2D& rValue, sal_uInt32 nCount) + { + if(nCount) + { + // add nCount copies of rValue + CoordinateData2DVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + maVector.insert(aIndex, nCount, rValue); + } + } + + void insert(sal_uInt32 nIndex, const CoordinateDataArray2D& rSource) + { + const sal_uInt32 nCount(rSource.maVector.size()); + + if(nCount) + { + // insert data + CoordinateData2DVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + CoordinateData2DVector::const_iterator aStart(rSource.maVector.begin()); + CoordinateData2DVector::const_iterator aEnd(rSource.maVector.end()); + maVector.insert(aIndex, aStart, aEnd); + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + // remove point data + CoordinateData2DVector::iterator aStart(maVector.begin()); + aStart += nIndex; + const CoordinateData2DVector::iterator aEnd(aStart + nCount); + maVector.erase(aStart, aEnd); + } + } + + void flip(bool bIsClosed) + { + if(maVector.size() > 1) + { + // to keep the same point at index 0, just flip all points except the + // first one when closed + const sal_uInt32 nHalfSize(bIsClosed ? (maVector.size() - 1) >> 1 : maVector.size() >> 1); + CoordinateData2DVector::iterator aStart(bIsClosed ? maVector.begin() + 1 : maVector.begin()); + CoordinateData2DVector::iterator aEnd(maVector.end() - 1); + + for(sal_uInt32 a(0); a < nHalfSize; a++) + { + ::std::swap(*aStart, *aEnd); + aStart++; + aEnd--; + } + } + } + + void removeDoublePointsAtBeginEnd() + { + // remove from end as long as there are at least two points + // and begin/end are equal + while((maVector.size() > 1) && (maVector[0] == maVector[maVector.size() - 1])) + { + maVector.pop_back(); + } + } + + void removeDoublePointsWholeTrack() + { + sal_uInt32 nIndex(0); + + // test as long as there are at least two points and as long as the index + // is smaller or equal second last point + while((maVector.size() > 1) && (nIndex <= maVector.size() - 2)) + { + if(maVector[nIndex] == maVector[nIndex + 1]) + { + // if next is same as index, delete next + maVector.erase(maVector.begin() + (nIndex + 1)); + } + else + { + // if different, step forward + nIndex++; + } + } + } + + void transform(const basegfx::B2DHomMatrix& rMatrix) + { + CoordinateData2DVector::iterator aStart(maVector.begin()); + CoordinateData2DVector::iterator aEnd(maVector.end()); + + for(; aStart != aEnd; aStart++) + { + aStart->transform(rMatrix); + } + } +}; + +////////////////////////////////////////////////////////////////////////////// + +class ControlVectorPair2D +{ + basegfx::B2DVector maPrevVector; + basegfx::B2DVector maNextVector; + +public: + const basegfx::B2DVector& getPrevVector() const + { + return maPrevVector; + } + + void setPrevVector(const basegfx::B2DVector& rValue) + { + if(rValue != maPrevVector) + maPrevVector = rValue; + } + + const basegfx::B2DVector& getNextVector() const + { + return maNextVector; + } + + void setNextVector(const basegfx::B2DVector& rValue) + { + if(rValue != maNextVector) + maNextVector = rValue; + } + + bool operator==(const ControlVectorPair2D& rData) const + { + return (maPrevVector == rData.getPrevVector() && maNextVector == rData.getNextVector()); + } + + void flip() + { + ::std::swap(maPrevVector, maNextVector); + } +}; + +////////////////////////////////////////////////////////////////////////////// + +class ControlVectorArray2D +{ + typedef ::std::vector< ControlVectorPair2D > ControlVectorPair2DVector; + + ControlVectorPair2DVector maVector; + sal_uInt32 mnUsedVectors; + +public: + explicit ControlVectorArray2D(sal_uInt32 nCount) + : maVector(nCount), + mnUsedVectors(0) + {} + + ControlVectorArray2D(const ControlVectorArray2D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount) + : maVector(), + mnUsedVectors(0) + { + ControlVectorPair2DVector::const_iterator aStart(rOriginal.maVector.begin()); + aStart += nIndex; + ControlVectorPair2DVector::const_iterator aEnd(aStart); + aEnd += nCount; + maVector.reserve(nCount); + + for(; aStart != aEnd; aStart++) + { + if(!aStart->getPrevVector().equalZero()) + mnUsedVectors++; + + if(!aStart->getNextVector().equalZero()) + mnUsedVectors++; + + maVector.push_back(*aStart); + } + } + + sal_uInt32 count() const + { + return maVector.size(); + } + + bool operator==(const ControlVectorArray2D& rCandidate) const + { + return (maVector == rCandidate.maVector); + } + + bool isUsed() const + { + return (0 != mnUsedVectors); + } + + const basegfx::B2DVector& getPrevVector(sal_uInt32 nIndex) const + { + return maVector[nIndex].getPrevVector(); + } + + void setPrevVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue) + { + bool bWasUsed(mnUsedVectors && !maVector[nIndex].getPrevVector().equalZero()); + bool bIsUsed(!rValue.equalZero()); + + if(bWasUsed) + { + if(bIsUsed) + { + maVector[nIndex].setPrevVector(rValue); + } + else + { + maVector[nIndex].setPrevVector(basegfx::B2DVector::getEmptyVector()); + mnUsedVectors--; + } + } + else + { + if(bIsUsed) + { + maVector[nIndex].setPrevVector(rValue); + mnUsedVectors++; + } + } + } + + const basegfx::B2DVector& getNextVector(sal_uInt32 nIndex) const + { + return maVector[nIndex].getNextVector(); + } + + void setNextVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue) + { + bool bWasUsed(mnUsedVectors && !maVector[nIndex].getNextVector().equalZero()); + bool bIsUsed(!rValue.equalZero()); + + if(bWasUsed) + { + if(bIsUsed) + { + maVector[nIndex].setNextVector(rValue); + } + else + { + maVector[nIndex].setNextVector(basegfx::B2DVector::getEmptyVector()); + mnUsedVectors--; + } + } + else + { + if(bIsUsed) + { + maVector[nIndex].setNextVector(rValue); + mnUsedVectors++; + } + } + } + + void append(const ControlVectorPair2D& rValue) + { + maVector.push_back(rValue); + + if(!rValue.getPrevVector().equalZero()) + mnUsedVectors += 1; + + if(!rValue.getNextVector().equalZero()) + mnUsedVectors += 1; + } + + void insert(sal_uInt32 nIndex, const ControlVectorPair2D& rValue, sal_uInt32 nCount) + { + if(nCount) + { + // add nCount copies of rValue + ControlVectorPair2DVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + maVector.insert(aIndex, nCount, rValue); + + if(!rValue.getPrevVector().equalZero()) + mnUsedVectors += nCount; + + if(!rValue.getNextVector().equalZero()) + mnUsedVectors += nCount; + } + } + + void insert(sal_uInt32 nIndex, const ControlVectorArray2D& rSource) + { + const sal_uInt32 nCount(rSource.maVector.size()); + + if(nCount) + { + // insert data + ControlVectorPair2DVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + ControlVectorPair2DVector::const_iterator aStart(rSource.maVector.begin()); + ControlVectorPair2DVector::const_iterator aEnd(rSource.maVector.end()); + maVector.insert(aIndex, aStart, aEnd); + + for(; aStart != aEnd; aStart++) + { + if(!aStart->getPrevVector().equalZero()) + mnUsedVectors++; + + if(!aStart->getNextVector().equalZero()) + mnUsedVectors++; + } + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + const ControlVectorPair2DVector::iterator aDeleteStart(maVector.begin() + nIndex); + const ControlVectorPair2DVector::iterator aDeleteEnd(aDeleteStart + nCount); + ControlVectorPair2DVector::const_iterator aStart(aDeleteStart); + + for(; mnUsedVectors && aStart != aDeleteEnd; aStart++) + { + if(!aStart->getPrevVector().equalZero()) + mnUsedVectors--; + + if(mnUsedVectors && !aStart->getNextVector().equalZero()) + mnUsedVectors--; + } + + // remove point data + maVector.erase(aDeleteStart, aDeleteEnd); + } + } + + void flip(bool bIsClosed) + { + if(maVector.size() > 1) + { + // to keep the same point at index 0, just flip all points except the + // first one when closed + const sal_uInt32 nHalfSize(bIsClosed ? (maVector.size() - 1) >> 1 : maVector.size() >> 1); + ControlVectorPair2DVector::iterator aStart(bIsClosed ? maVector.begin() + 1 : maVector.begin()); + ControlVectorPair2DVector::iterator aEnd(maVector.end() - 1); + + for(sal_uInt32 a(0); a < nHalfSize; a++) + { + // swap Prev and Next + aStart->flip(); + aEnd->flip(); + + // swap entries + ::std::swap(*aStart, *aEnd); + + aStart++; + aEnd--; + } + + if(aStart == aEnd) + { + // swap Prev and Next at middle element (if exists) + aStart->flip(); + } + + if(bIsClosed) + { + // swap Prev and Next at start element + maVector.begin()->flip(); + } + } + } +}; + +////////////////////////////////////////////////////////////////////////////// + +class ImplBufferedData +{ +private: + // Possibility to hold the last subdivision + boost::scoped_ptr< basegfx::B2DPolygon > mpDefaultSubdivision; + + // Possibility to hold the last B2DRange calculation + boost::scoped_ptr< basegfx::B2DRange > mpB2DRange; + +public: + ImplBufferedData() + : mpDefaultSubdivision(), + mpB2DRange() + {} + + const basegfx::B2DPolygon& getDefaultAdaptiveSubdivision(const basegfx::B2DPolygon& rSource) const + { + if(!mpDefaultSubdivision) + { + const_cast< ImplBufferedData* >(this)->mpDefaultSubdivision.reset(new basegfx::B2DPolygon(basegfx::tools::adaptiveSubdivideByCount(rSource, 9))); + } + + return *mpDefaultSubdivision; + } + + const basegfx::B2DRange& getB2DRange(const basegfx::B2DPolygon& rSource) const + { + if(!mpB2DRange) + { + basegfx::B2DRange aNewRange; + const sal_uInt32 nPointCount(rSource.count()); + + if(nPointCount) + { + for(sal_uInt32 a(0); a < nPointCount; a++) + { + aNewRange.expand(rSource.getB2DPoint(a)); + } + + if(rSource.areControlPointsUsed()) + { + const sal_uInt32 nEdgeCount(rSource.isClosed() ? nPointCount : nPointCount - 1); + + if(nEdgeCount) + { + basegfx::B2DCubicBezier aEdge; + aEdge.setStartPoint(rSource.getB2DPoint(0)); + + for(sal_uInt32 b(0); b < nEdgeCount; b++) + { + const sal_uInt32 nNextIndex((b + 1) % nPointCount); + aEdge.setControlPointA(rSource.getNextControlPoint(b)); + aEdge.setControlPointB(rSource.getPrevControlPoint(nNextIndex)); + aEdge.setEndPoint(rSource.getB2DPoint(nNextIndex)); + + if(aEdge.isBezier()) + { + const basegfx::B2DRange aBezierRangeWithControlPoints(aEdge.getRange()); + + if(!aNewRange.isInside(aBezierRangeWithControlPoints)) + { + // the range with control points of the current edge is not completely + // inside the current range without control points. Expand current range by + // subdividing the bezier segment. + // Ideal here is a subdivision at the extreme values, so use + // getAllExtremumPositions to get all extremas in one run + ::std::vector< double > aExtremas; + + aExtremas.reserve(4); + aEdge.getAllExtremumPositions(aExtremas); + + const sal_uInt32 nExtremaCount(aExtremas.size()); + + for(sal_uInt32 c(0); c < nExtremaCount; c++) + { + aNewRange.expand(aEdge.interpolatePoint(aExtremas[c])); + } + } + } + + // prepare next edge + aEdge.setStartPoint(aEdge.getEndPoint()); + } + } + } + } + + const_cast< ImplBufferedData* >(this)->mpB2DRange.reset(new basegfx::B2DRange(aNewRange)); + } + + return *mpB2DRange; + } +}; + +////////////////////////////////////////////////////////////////////////////// + +class ImplB2DPolygon +{ +private: + // The point vector. This vector exists always and defines the + // count of members. + CoordinateDataArray2D maPoints; + + // The control point vectors. This vectors are created on demand + // and may be zero. + boost::scoped_ptr< ControlVectorArray2D > mpControlVector; + + // buffered data for e.g. default subdivision and range + boost::scoped_ptr< ImplBufferedData > mpBufferedData; + + // flag which decides if this polygon is opened or closed + bool mbIsClosed; + +public: + const basegfx::B2DPolygon& getDefaultAdaptiveSubdivision(const basegfx::B2DPolygon& rSource) const + { + if(!mpControlVector || !mpControlVector->isUsed()) + { + return rSource; + } + + if(!mpBufferedData) + { + const_cast< ImplB2DPolygon* >(this)->mpBufferedData.reset(new ImplBufferedData); + } + + return mpBufferedData->getDefaultAdaptiveSubdivision(rSource); + } + + const basegfx::B2DRange& getB2DRange(const basegfx::B2DPolygon& rSource) const + { + if(!mpBufferedData) + { + const_cast< ImplB2DPolygon* >(this)->mpBufferedData.reset(new ImplBufferedData); + } + + return mpBufferedData->getB2DRange(rSource); + } + + ImplB2DPolygon() + : maPoints(0), + mpControlVector(), + mpBufferedData(), + mbIsClosed(false) + {} + + ImplB2DPolygon(const ImplB2DPolygon& rToBeCopied) + : maPoints(rToBeCopied.maPoints), + mpControlVector(), + mpBufferedData(), + mbIsClosed(rToBeCopied.mbIsClosed) + { + // complete initialization using copy + if(rToBeCopied.mpControlVector && rToBeCopied.mpControlVector->isUsed()) + { + mpControlVector.reset( new ControlVectorArray2D(*rToBeCopied.mpControlVector) ); + } + } + + ImplB2DPolygon(const ImplB2DPolygon& rToBeCopied, sal_uInt32 nIndex, sal_uInt32 nCount) + : maPoints(rToBeCopied.maPoints, nIndex, nCount), + mpControlVector(), + mpBufferedData(), + mbIsClosed(rToBeCopied.mbIsClosed) + { + // complete initialization using partly copy + if(rToBeCopied.mpControlVector && rToBeCopied.mpControlVector->isUsed()) + { + mpControlVector.reset( new ControlVectorArray2D(*rToBeCopied.mpControlVector, nIndex, nCount) ); + + if(!mpControlVector->isUsed()) + mpControlVector.reset(); + } + } + + ImplB2DPolygon& operator=( const ImplB2DPolygon& rToBeCopied ) + { + maPoints = rToBeCopied.maPoints; + mpControlVector.reset(); + mpBufferedData.reset(); + mbIsClosed = rToBeCopied.mbIsClosed; + + // complete initialization using copy + if(rToBeCopied.mpControlVector && rToBeCopied.mpControlVector->isUsed()) + mpControlVector.reset( new ControlVectorArray2D(*rToBeCopied.mpControlVector) ); + + return *this; + } + + sal_uInt32 count() const + { + return maPoints.count(); + } + + bool isClosed() const + { + return mbIsClosed; + } + + void setClosed(bool bNew) + { + if(bNew != mbIsClosed) + { + mpBufferedData.reset(); + mbIsClosed = bNew; + } + } + + bool operator==(const ImplB2DPolygon& rCandidate) const + { + if(mbIsClosed == rCandidate.mbIsClosed) + { + if(maPoints == rCandidate.maPoints) + { + bool bControlVectorsAreEqual(true); + + if(mpControlVector) + { + if(rCandidate.mpControlVector) + { + bControlVectorsAreEqual = ((*mpControlVector) == (*rCandidate.mpControlVector)); + } + else + { + // candidate has no control vector, so it's assumed all unused. + bControlVectorsAreEqual = !mpControlVector->isUsed(); + } + } + else + { + if(rCandidate.mpControlVector) + { + // we have no control vector, so it's assumed all unused. + bControlVectorsAreEqual = !rCandidate.mpControlVector->isUsed(); + } + } + + if(bControlVectorsAreEqual) + { + return true; + } + } + } + + return false; + } + + const basegfx::B2DPoint& getPoint(sal_uInt32 nIndex) const + { + return maPoints.getCoordinate(nIndex); + } + + void setPoint(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue) + { + mpBufferedData.reset(); + maPoints.setCoordinate(nIndex, rValue); + } + + void reserve(sal_uInt32 nCount) + { + maPoints.reserve(nCount); + } + + void append(const basegfx::B2DPoint& rPoint) + { + mpBufferedData.reset(); // TODO: is this needed? + const CoordinateData2D aCoordinate(rPoint); + maPoints.append(aCoordinate); + + if(mpControlVector) + { + const ControlVectorPair2D aVectorPair; + mpControlVector->append(aVectorPair); + } + } + + void insert(sal_uInt32 nIndex, const basegfx::B2DPoint& rPoint, sal_uInt32 nCount) + { + if(nCount) + { + mpBufferedData.reset(); + CoordinateData2D aCoordinate(rPoint); + maPoints.insert(nIndex, aCoordinate, nCount); + + if(mpControlVector) + { + ControlVectorPair2D aVectorPair; + mpControlVector->insert(nIndex, aVectorPair, nCount); + } + } + } + + const basegfx::B2DVector& getPrevControlVector(sal_uInt32 nIndex) const + { + if(mpControlVector) + { + return mpControlVector->getPrevVector(nIndex); + } + else + { + return basegfx::B2DVector::getEmptyVector(); + } + } + + void setPrevControlVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue) + { + if(!mpControlVector) + { + if(!rValue.equalZero()) + { + mpBufferedData.reset(); + mpControlVector.reset( new ControlVectorArray2D(maPoints.count()) ); + mpControlVector->setPrevVector(nIndex, rValue); + } + } + else + { + mpBufferedData.reset(); + mpControlVector->setPrevVector(nIndex, rValue); + + if(!mpControlVector->isUsed()) + mpControlVector.reset(); + } + } + + const basegfx::B2DVector& getNextControlVector(sal_uInt32 nIndex) const + { + if(mpControlVector) + { + return mpControlVector->getNextVector(nIndex); + } + else + { + return basegfx::B2DVector::getEmptyVector(); + } + } + + void setNextControlVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue) + { + if(!mpControlVector) + { + if(!rValue.equalZero()) + { + mpBufferedData.reset(); + mpControlVector.reset( new ControlVectorArray2D(maPoints.count()) ); + mpControlVector->setNextVector(nIndex, rValue); + } + } + else + { + mpBufferedData.reset(); + mpControlVector->setNextVector(nIndex, rValue); + + if(!mpControlVector->isUsed()) + mpControlVector.reset(); + } + } + + bool areControlPointsUsed() const + { + return (mpControlVector && mpControlVector->isUsed()); + } + + void resetControlVectors(sal_uInt32 nIndex) + { + setPrevControlVector(nIndex, basegfx::B2DVector::getEmptyVector()); + setNextControlVector(nIndex, basegfx::B2DVector::getEmptyVector()); + } + + void resetControlVectors() + { + mpBufferedData.reset(); + mpControlVector.reset(); + } + + void setControlVectors(sal_uInt32 nIndex, const basegfx::B2DVector& rPrev, const basegfx::B2DVector& rNext) + { + setPrevControlVector(nIndex, rPrev); + setNextControlVector(nIndex, rNext); + } + + void appendBezierSegment(const basegfx::B2DVector& rNext, const basegfx::B2DVector& rPrev, const basegfx::B2DPoint& rPoint) + { + mpBufferedData.reset(); + const sal_uInt32 nCount(maPoints.count()); + + if(nCount) + { + setNextControlVector(nCount - 1, rNext); + } + + insert(nCount, rPoint, 1); + setPrevControlVector(nCount, rPrev); + } + + void insert(sal_uInt32 nIndex, const ImplB2DPolygon& rSource) + { + const sal_uInt32 nCount(rSource.maPoints.count()); + + if(nCount) + { + mpBufferedData.reset(); + + if(rSource.mpControlVector && rSource.mpControlVector->isUsed() && !mpControlVector) + { + mpControlVector.reset( new ControlVectorArray2D(maPoints.count()) ); + } + + maPoints.insert(nIndex, rSource.maPoints); + + if(rSource.mpControlVector) + { + mpControlVector->insert(nIndex, *rSource.mpControlVector); + + if(!mpControlVector->isUsed()) + mpControlVector.reset(); + } + else if(mpControlVector) + { + ControlVectorPair2D aVectorPair; + mpControlVector->insert(nIndex, aVectorPair, nCount); + } + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + mpBufferedData.reset(); + maPoints.remove(nIndex, nCount); + + if(mpControlVector) + { + mpControlVector->remove(nIndex, nCount); + + if(!mpControlVector->isUsed()) + mpControlVector.reset(); + } + } + } + + void flip() + { + if(maPoints.count() > 1) + { + mpBufferedData.reset(); + + // flip points + maPoints.flip(mbIsClosed); + + if(mpControlVector) + { + // flip control vector + mpControlVector->flip(mbIsClosed); + } + } + } + + bool hasDoublePoints() const + { + if(mbIsClosed) + { + // check for same start and end point + const sal_uInt32 nIndex(maPoints.count() - 1); + + if(maPoints.getCoordinate(0) == maPoints.getCoordinate(nIndex)) + { + if(mpControlVector) + { + if(mpControlVector->getNextVector(nIndex).equalZero() && mpControlVector->getPrevVector(0).equalZero()) + { + return true; + } + } + else + { + return true; + } + } + } + + // test for range + for(sal_uInt32 a(0); a < maPoints.count() - 1; a++) + { + if(maPoints.getCoordinate(a) == maPoints.getCoordinate(a + 1)) + { + if(mpControlVector) + { + if(mpControlVector->getNextVector(a).equalZero() && mpControlVector->getPrevVector(a + 1).equalZero()) + { + return true; + } + } + else + { + return true; + } + } + } + + return false; + } + + void removeDoublePointsAtBeginEnd() + { + // Only remove DoublePoints at Begin and End when poly is closed + if(mbIsClosed) + { + mpBufferedData.reset(); + + if(mpControlVector) + { + bool bRemove; + + do + { + bRemove = false; + + if(maPoints.count() > 1) + { + const sal_uInt32 nIndex(maPoints.count() - 1); + + if(maPoints.getCoordinate(0) == maPoints.getCoordinate(nIndex)) + { + if(mpControlVector) + { + if(mpControlVector->getNextVector(nIndex).equalZero() && mpControlVector->getPrevVector(0).equalZero()) + { + bRemove = true; + } + } + else + { + bRemove = true; + } + } + } + + if(bRemove) + { + const sal_uInt32 nIndex(maPoints.count() - 1); + + if(mpControlVector && !mpControlVector->getPrevVector(nIndex).equalZero()) + { + mpControlVector->setPrevVector(0, mpControlVector->getPrevVector(nIndex)); + } + + remove(nIndex, 1); + } + } + while(bRemove); + } + else + { + maPoints.removeDoublePointsAtBeginEnd(); + } + } + } + + void removeDoublePointsWholeTrack() + { + mpBufferedData.reset(); + + if(mpControlVector) + { + sal_uInt32 nIndex(0); + + // test as long as there are at least two points and as long as the index + // is smaller or equal second last point + while((maPoints.count() > 1) && (nIndex <= maPoints.count() - 2)) + { + bool bRemove(maPoints.getCoordinate(nIndex) == maPoints.getCoordinate(nIndex + 1)); + + if(bRemove) + { + if(mpControlVector) + { + if(!mpControlVector->getNextVector(nIndex).equalZero() || !mpControlVector->getPrevVector(nIndex + 1).equalZero()) + { + bRemove = false; + } + } + } + + if(bRemove) + { + if(mpControlVector && !mpControlVector->getPrevVector(nIndex).equalZero()) + { + mpControlVector->setPrevVector(nIndex + 1, mpControlVector->getPrevVector(nIndex)); + } + + // if next is same as index and the control vectors are unused, delete index + remove(nIndex, 1); + } + else + { + // if different, step forward + nIndex++; + } + } + } + else + { + maPoints.removeDoublePointsWholeTrack(); + } + } + + void transform(const basegfx::B2DHomMatrix& rMatrix) + { + mpBufferedData.reset(); + + if(mpControlVector) + { + for(sal_uInt32 a(0); a < maPoints.count(); a++) + { + basegfx::B2DPoint aCandidate = maPoints.getCoordinate(a); + + if(mpControlVector->isUsed()) + { + const basegfx::B2DVector& rPrevVector(mpControlVector->getPrevVector(a)); + const basegfx::B2DVector& rNextVector(mpControlVector->getNextVector(a)); + + if(!rPrevVector.equalZero()) + { + basegfx::B2DVector aPrevVector(rMatrix * rPrevVector); + mpControlVector->setPrevVector(a, aPrevVector); + } + + if(!rNextVector.equalZero()) + { + basegfx::B2DVector aNextVector(rMatrix * rNextVector); + mpControlVector->setNextVector(a, aNextVector); + } + } + + aCandidate *= rMatrix; + maPoints.setCoordinate(a, aCandidate); + } + + if(!mpControlVector->isUsed()) + mpControlVector.reset(); + } + else + { + maPoints.transform(rMatrix); + } + } +}; + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace + { + struct DefaultPolygon: public rtl::Static<B2DPolygon::ImplType, DefaultPolygon> {}; + } + + B2DPolygon::B2DPolygon() + : mpPolygon(DefaultPolygon::get()) + {} + + B2DPolygon::B2DPolygon(const B2DPolygon& rPolygon) + : mpPolygon(rPolygon.mpPolygon) + {} + + B2DPolygon::B2DPolygon(const B2DPolygon& rPolygon, sal_uInt32 nIndex, sal_uInt32 nCount) + : mpPolygon(ImplB2DPolygon(*rPolygon.mpPolygon, nIndex, nCount)) + { + // TODO(P2): one extra temporary here (cow_wrapper copies + // given ImplB2DPolygon into its internal impl_t wrapper type) + OSL_ENSURE(nIndex + nCount <= rPolygon.mpPolygon->count(), "B2DPolygon constructor outside range (!)"); + } + + B2DPolygon::~B2DPolygon() + { + } + + B2DPolygon& B2DPolygon::operator=(const B2DPolygon& rPolygon) + { + mpPolygon = rPolygon.mpPolygon; + return *this; + } + + void B2DPolygon::makeUnique() + { + mpPolygon.make_unique(); + } + + bool B2DPolygon::operator==(const B2DPolygon& rPolygon) const + { + if(mpPolygon.same_object(rPolygon.mpPolygon)) + return true; + + return ((*mpPolygon) == (*rPolygon.mpPolygon)); + } + + bool B2DPolygon::operator!=(const B2DPolygon& rPolygon) const + { + return !(*this == rPolygon); + } + + sal_uInt32 B2DPolygon::count() const + { + return mpPolygon->count(); + } + + B2DPoint B2DPolygon::getB2DPoint(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + return mpPolygon->getPoint(nIndex); + } + + void B2DPolygon::setB2DPoint(sal_uInt32 nIndex, const B2DPoint& rValue) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + if(getB2DPoint(nIndex) != rValue) + { + mpPolygon->setPoint(nIndex, rValue); + } + } + + void B2DPolygon::reserve(sal_uInt32 nCount) + { + mpPolygon->reserve(nCount); + } + + void B2DPolygon::insert(sal_uInt32 nIndex, const B2DPoint& rPoint, sal_uInt32 nCount) + { + OSL_ENSURE(nIndex <= mpPolygon->count(), "B2DPolygon Insert outside range (!)"); + + if(nCount) + { + mpPolygon->insert(nIndex, rPoint, nCount); + } + } + + void B2DPolygon::append(const B2DPoint& rPoint, sal_uInt32 nCount) + { + if(nCount) + { + mpPolygon->insert(mpPolygon->count(), rPoint, nCount); + } + } + + void B2DPolygon::append(const B2DPoint& rPoint) + { + mpPolygon->append(rPoint); + } + + B2DPoint B2DPolygon::getPrevControlPoint(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + if(mpPolygon->areControlPointsUsed()) + { + return mpPolygon->getPoint(nIndex) + mpPolygon->getPrevControlVector(nIndex); + } + else + { + return mpPolygon->getPoint(nIndex); + } + } + + B2DPoint B2DPolygon::getNextControlPoint(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + if(mpPolygon->areControlPointsUsed()) + { + return mpPolygon->getPoint(nIndex) + mpPolygon->getNextControlVector(nIndex); + } + else + { + return mpPolygon->getPoint(nIndex); + } + } + + void B2DPolygon::setPrevControlPoint(sal_uInt32 nIndex, const B2DPoint& rValue) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + const basegfx::B2DVector aNewVector(rValue - mpPolygon->getPoint(nIndex)); + + if(mpPolygon->getPrevControlVector(nIndex) != aNewVector) + { + mpPolygon->setPrevControlVector(nIndex, aNewVector); + } + } + + void B2DPolygon::setNextControlPoint(sal_uInt32 nIndex, const B2DPoint& rValue) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + const basegfx::B2DVector aNewVector(rValue - mpPolygon->getPoint(nIndex)); + + if(mpPolygon->getNextControlVector(nIndex) != aNewVector) + { + mpPolygon->setNextControlVector(nIndex, aNewVector); + } + } + + void B2DPolygon::setControlPoints(sal_uInt32 nIndex, const basegfx::B2DPoint& rPrev, const basegfx::B2DPoint& rNext) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + const B2DPoint aPoint(mpPolygon->getPoint(nIndex)); + const basegfx::B2DVector aNewPrev(rPrev - aPoint); + const basegfx::B2DVector aNewNext(rNext - aPoint); + + if(mpPolygon->getPrevControlVector(nIndex) != aNewPrev || mpPolygon->getNextControlVector(nIndex) != aNewNext) + { + mpPolygon->setControlVectors(nIndex, aNewPrev, aNewNext); + } + } + + void B2DPolygon::resetPrevControlPoint(sal_uInt32 nIndex) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + if(mpPolygon->areControlPointsUsed() && !mpPolygon->getPrevControlVector(nIndex).equalZero()) + { + mpPolygon->setPrevControlVector(nIndex, B2DVector::getEmptyVector()); + } + } + + void B2DPolygon::resetNextControlPoint(sal_uInt32 nIndex) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + if(mpPolygon->areControlPointsUsed() && !mpPolygon->getNextControlVector(nIndex).equalZero()) + { + mpPolygon->setNextControlVector(nIndex, B2DVector::getEmptyVector()); + } + } + + void B2DPolygon::resetControlPoints(sal_uInt32 nIndex) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + if(mpPolygon->areControlPointsUsed() && + (!mpPolygon->getPrevControlVector(nIndex).equalZero() || !mpPolygon->getNextControlVector(nIndex).equalZero())) + { + mpPolygon->resetControlVectors(nIndex); + } + } + + void B2DPolygon::resetControlPoints() + { + if(mpPolygon->areControlPointsUsed()) + { + mpPolygon->resetControlVectors(); + } + } + + void B2DPolygon::appendBezierSegment( + const B2DPoint& rNextControlPoint, + const B2DPoint& rPrevControlPoint, + const B2DPoint& rPoint) + { + const B2DVector aNewNextVector(mpPolygon->count() ? B2DVector(rNextControlPoint - mpPolygon->getPoint(mpPolygon->count() - 1)) : B2DVector::getEmptyVector()); + const B2DVector aNewPrevVector(rPrevControlPoint - rPoint); + + if(aNewNextVector.equalZero() && aNewPrevVector.equalZero()) + { + mpPolygon->insert(mpPolygon->count(), rPoint, 1); + } + else + { + mpPolygon->appendBezierSegment(aNewNextVector, aNewPrevVector, rPoint); + } + } + + bool B2DPolygon::areControlPointsUsed() const + { + return mpPolygon->areControlPointsUsed(); + } + + bool B2DPolygon::isPrevControlPointUsed(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + return (mpPolygon->areControlPointsUsed() && !mpPolygon->getPrevControlVector(nIndex).equalZero()); + } + + bool B2DPolygon::isNextControlPointUsed(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + return (mpPolygon->areControlPointsUsed() && !mpPolygon->getNextControlVector(nIndex).equalZero()); + } + + B2VectorContinuity B2DPolygon::getContinuityInPoint(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + if(mpPolygon->areControlPointsUsed()) + { + const B2DVector& rPrev(mpPolygon->getPrevControlVector(nIndex)); + const B2DVector& rNext(mpPolygon->getNextControlVector(nIndex)); + + return getContinuity(rPrev, rNext); + } + else + { + return CONTINUITY_NONE; + } + } + + bool B2DPolygon::isBezierSegment(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + + if(mpPolygon->areControlPointsUsed()) + { + // Check if the edge exists + const bool bNextIndexValidWithoutClose(nIndex + 1 < mpPolygon->count()); + + if(bNextIndexValidWithoutClose || mpPolygon->isClosed()) + { + const sal_uInt32 nNextIndex(bNextIndexValidWithoutClose ? nIndex + 1 : 0); + return (!mpPolygon->getPrevControlVector(nNextIndex).equalZero() + || !mpPolygon->getNextControlVector(nIndex).equalZero()); + } + else + { + // no valid edge -> no bezier segment, even when local next + // vector may be used + return false; + } + } + else + { + // no control points -> no bezier segment + return false; + } + } + + void B2DPolygon::getBezierSegment(sal_uInt32 nIndex, B2DCubicBezier& rTarget) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B2DPolygon access outside range (!)"); + const bool bNextIndexValidWithoutClose(nIndex + 1 < mpPolygon->count()); + + if(bNextIndexValidWithoutClose || mpPolygon->isClosed()) + { + const sal_uInt32 nNextIndex(bNextIndexValidWithoutClose ? nIndex + 1 : 0); + rTarget.setStartPoint(mpPolygon->getPoint(nIndex)); + rTarget.setEndPoint(mpPolygon->getPoint(nNextIndex)); + + if(mpPolygon->areControlPointsUsed()) + { + rTarget.setControlPointA(rTarget.getStartPoint() + mpPolygon->getNextControlVector(nIndex)); + rTarget.setControlPointB(rTarget.getEndPoint() + mpPolygon->getPrevControlVector(nNextIndex)); + } + else + { + // no bezier, reset control poins at rTarget + rTarget.setControlPointA(rTarget.getStartPoint()); + rTarget.setControlPointB(rTarget.getEndPoint()); + } + } + else + { + // no valid edge at all, reset rTarget to current point + const B2DPoint aPoint(mpPolygon->getPoint(nIndex)); + rTarget.setStartPoint(aPoint); + rTarget.setEndPoint(aPoint); + rTarget.setControlPointA(aPoint); + rTarget.setControlPointB(aPoint); + } + } + + B2DPolygon B2DPolygon::getDefaultAdaptiveSubdivision() const + { + return mpPolygon->getDefaultAdaptiveSubdivision(*this); + } + + B2DRange B2DPolygon::getB2DRange() const + { + return mpPolygon->getB2DRange(*this); + } + + void B2DPolygon::insert(sal_uInt32 nIndex, const B2DPolygon& rPoly, sal_uInt32 nIndex2, sal_uInt32 nCount) + { + OSL_ENSURE(nIndex <= mpPolygon->count(), "B2DPolygon Insert outside range (!)"); + + if(rPoly.count()) + { + if(!nCount) + { + nCount = rPoly.count(); + } + + if(0 == nIndex2 && nCount == rPoly.count()) + { + mpPolygon->insert(nIndex, *rPoly.mpPolygon); + } + else + { + OSL_ENSURE(nIndex2 + nCount <= rPoly.mpPolygon->count(), "B2DPolygon Insert outside range (!)"); + ImplB2DPolygon aTempPoly(*rPoly.mpPolygon, nIndex2, nCount); + mpPolygon->insert(nIndex, aTempPoly); + } + } + } + + void B2DPolygon::append(const B2DPolygon& rPoly, sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(rPoly.count()) + { + if(!nCount) + { + nCount = rPoly.count(); + } + + if(0 == nIndex && nCount == rPoly.count()) + { + mpPolygon->insert(mpPolygon->count(), *rPoly.mpPolygon); + } + else + { + OSL_ENSURE(nIndex + nCount <= rPoly.mpPolygon->count(), "B2DPolygon Append outside range (!)"); + ImplB2DPolygon aTempPoly(*rPoly.mpPolygon, nIndex, nCount); + mpPolygon->insert(mpPolygon->count(), aTempPoly); + } + } + } + + void B2DPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + OSL_ENSURE(nIndex + nCount <= mpPolygon->count(), "B2DPolygon Remove outside range (!)"); + + if(nCount) + { + mpPolygon->remove(nIndex, nCount); + } + } + + void B2DPolygon::clear() + { + mpPolygon = DefaultPolygon::get(); + } + + bool B2DPolygon::isClosed() const + { + return mpPolygon->isClosed(); + } + + void B2DPolygon::setClosed(bool bNew) + { + if(isClosed() != bNew) + { + mpPolygon->setClosed(bNew); + } + } + + void B2DPolygon::flip() + { + if(count() > 1) + { + mpPolygon->flip(); + } + } + + bool B2DPolygon::hasDoublePoints() const + { + return (mpPolygon->count() > 1 && mpPolygon->hasDoublePoints()); + } + + void B2DPolygon::removeDoublePoints() + { + if(hasDoublePoints()) + { + mpPolygon->removeDoublePointsAtBeginEnd(); + mpPolygon->removeDoublePointsWholeTrack(); + } + } + + void B2DPolygon::transform(const B2DHomMatrix& rMatrix) + { + if(mpPolygon->count() && !rMatrix.isIdentity()) + { + mpPolygon->transform(rMatrix); + } + } +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/polygon/b2dpolygonclipper.cxx b/basegfx/source/polygon/b2dpolygonclipper.cxx new file mode 100644 index 000000000000..6e5c7701e2ec --- /dev/null +++ b/basegfx/source/polygon/b2dpolygonclipper.cxx @@ -0,0 +1,873 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <osl/diagnose.h> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/polygon/b2dpolygoncutandtouch.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <basegfx/tools/rectcliptools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + B2DPolyPolygon clipPolygonOnParallelAxis(const B2DPolygon& rCandidate, bool bParallelToXAxis, bool bAboveAxis, double fValueOnOtherAxis, bool bStroke) + { + B2DPolyPolygon aRetval; + + if(rCandidate.count()) + { + const B2DRange aCandidateRange(getRange(rCandidate)); + + if(bParallelToXAxis && fTools::moreOrEqual(aCandidateRange.getMinY(), fValueOnOtherAxis)) + { + // completely above and on the clip line. also true for curves. + if(bAboveAxis) + { + // add completely + aRetval.append(rCandidate); + } + } + else if(bParallelToXAxis && fTools::lessOrEqual(aCandidateRange.getMaxY(), fValueOnOtherAxis)) + { + // completely below and on the clip line. also true for curves. + if(!bAboveAxis) + { + // add completely + aRetval.append(rCandidate); + } + } + else if(!bParallelToXAxis && fTools::moreOrEqual(aCandidateRange.getMinX(), fValueOnOtherAxis)) + { + // completely right of and on the clip line. also true for curves. + if(bAboveAxis) + { + // add completely + aRetval.append(rCandidate); + } + } + else if(!bParallelToXAxis && fTools::lessOrEqual(aCandidateRange.getMaxX(), fValueOnOtherAxis)) + { + // completely left of and on the clip line. also true for curves. + if(!bAboveAxis) + { + // add completely + aRetval.append(rCandidate); + } + } + else + { + // add cuts with axis to polygon, including bezier segments + // Build edge to cut with. Make it a little big longer than needed for + // numerical stability. We want to cut against the edge seen as endless + // ray here, but addPointsAtCuts() will limit itself to the + // edge's range ]0.0 .. 1.0[. + const double fSmallExtension((aCandidateRange.getWidth() + aCandidateRange.getHeight()) * (0.5 * 0.1)); + const B2DPoint aStart( + bParallelToXAxis ? aCandidateRange.getMinX() - fSmallExtension : fValueOnOtherAxis, + bParallelToXAxis ? fValueOnOtherAxis : aCandidateRange.getMinY() - fSmallExtension); + const B2DPoint aEnd( + bParallelToXAxis ? aCandidateRange.getMaxX() + fSmallExtension : fValueOnOtherAxis, + bParallelToXAxis ? fValueOnOtherAxis : aCandidateRange.getMaxY() + fSmallExtension); + const B2DPolygon aCandidate(addPointsAtCuts(rCandidate, aStart, aEnd)); + const sal_uInt32 nPointCount(aCandidate.count()); + const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L); + B2DCubicBezier aEdge; + B2DPolygon aRun; + + for(sal_uInt32 a(0L); a < nEdgeCount; a++) + { + aCandidate.getBezierSegment(a, aEdge); + const B2DPoint aTestPoint(aEdge.interpolatePoint(0.5)); + const bool bInside(bParallelToXAxis ? + fTools::moreOrEqual(aTestPoint.getY(), fValueOnOtherAxis) == bAboveAxis : + fTools::moreOrEqual(aTestPoint.getX(), fValueOnOtherAxis) == bAboveAxis); + + if(bInside) + { + if(!aRun.count() || !aRun.getB2DPoint(aRun.count() - 1).equal(aEdge.getStartPoint())) + { + aRun.append(aEdge.getStartPoint()); + } + + if(aEdge.isBezier()) + { + aRun.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint()); + } + else + { + aRun.append(aEdge.getEndPoint()); + } + } + else + { + if(bStroke && aRun.count()) + { + aRetval.append(aRun); + aRun.clear(); + } + } + } + + if(aRun.count()) + { + if(bStroke) + { + // try to merge this last and first polygon; they may have been + // the former polygon's start/end point + if(aRetval.count()) + { + const B2DPolygon aStartPolygon(aRetval.getB2DPolygon(0)); + + if(aStartPolygon.count() && aStartPolygon.getB2DPoint(0).equal(aRun.getB2DPoint(aRun.count() - 1))) + { + // append start polygon to aRun, remove from result set + aRun.append(aStartPolygon); aRun.removeDoublePoints(); + aRetval.remove(0); + } + } + + aRetval.append(aRun); + } + else + { + // set closed flag and correct last point (which is added double now). + closeWithGeometryChange(aRun); + aRetval.append(aRun); + } + } + } + } + + return aRetval; + } + + B2DPolyPolygon clipPolyPolygonOnParallelAxis(const B2DPolyPolygon& rCandidate, bool bParallelToXAxis, bool bAboveAxis, double fValueOnOtherAxis, bool bStroke) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolyPolygon aClippedPolyPolygon(clipPolygonOnParallelAxis(rCandidate.getB2DPolygon(a), bParallelToXAxis, bAboveAxis, fValueOnOtherAxis, bStroke)); + + if(aClippedPolyPolygon.count()) + { + aRetval.append(aClippedPolyPolygon); + } + } + + return aRetval; + } + + B2DPolyPolygon clipPolygonOnRange(const B2DPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke) + { + const sal_uInt32 nCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + if(!nCount) + { + // source is empty + return aRetval; + } + + if(rRange.isEmpty()) + { + if(bInside) + { + // nothing is inside an empty range + return aRetval; + } + else + { + // everything is outside an empty range + return B2DPolyPolygon(rCandidate); + } + } + + const B2DRange aCandidateRange(getRange(rCandidate)); + + if(rRange.isInside(aCandidateRange)) + { + // candidate is completely inside given range + if(bInside) + { + // nothing to do + return B2DPolyPolygon(rCandidate); + } + else + { + // nothing is outside, then + return aRetval; + } + } + + if(!bInside) + { + // cutting off the outer parts of filled polygons at parallell + // lines to the axes is only possible for the inner part, not for + // the outer part which means cutting a hole into the original polygon. + // This is because the inner part is a logical AND-operation of + // the four implied half-planes, but the outer part is not. + // It is possible for strokes, but with creating unnecessary extra + // cuts, so using clipPolygonOnPolyPolygon is better there, too. + // This needs to be done with the topology knowlegde and is unfurtunately + // more expensive, too. + const B2DPolygon aClip(createPolygonFromRect(rRange)); + + return clipPolygonOnPolyPolygon(rCandidate, B2DPolyPolygon(aClip), bInside, bStroke); + } + + // clip against the four axes of the range + // against X-Axis, lower value + aRetval = clipPolygonOnParallelAxis(rCandidate, true, bInside, rRange.getMinY(), bStroke); + + if(aRetval.count()) + { + // against Y-Axis, lower value + if(1L == aRetval.count()) + { + aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0L), false, bInside, rRange.getMinX(), bStroke); + } + else + { + aRetval = clipPolyPolygonOnParallelAxis(aRetval, false, bInside, rRange.getMinX(), bStroke); + } + + if(aRetval.count()) + { + // against X-Axis, higher value + if(1L == aRetval.count()) + { + aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0L), true, !bInside, rRange.getMaxY(), bStroke); + } + else + { + aRetval = clipPolyPolygonOnParallelAxis(aRetval, true, !bInside, rRange.getMaxY(), bStroke); + } + + if(aRetval.count()) + { + // against Y-Axis, higher value + if(1L == aRetval.count()) + { + aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0L), false, !bInside, rRange.getMaxX(), bStroke); + } + else + { + aRetval = clipPolyPolygonOnParallelAxis(aRetval, false, !bInside, rRange.getMaxX(), bStroke); + } + } + } + } + + return aRetval; + } + + B2DPolyPolygon clipPolyPolygonOnRange(const B2DPolyPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + if(!nPolygonCount) + { + // source is empty + return aRetval; + } + + if(rRange.isEmpty()) + { + if(bInside) + { + // nothing is inside an empty range + return aRetval; + } + else + { + // everything is outside an empty range + return rCandidate; + } + } + + if(bInside) + { + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolyPolygon aClippedPolyPolygon(clipPolygonOnRange(rCandidate.getB2DPolygon(a), rRange, bInside, bStroke)); + + if(aClippedPolyPolygon.count()) + { + aRetval.append(aClippedPolyPolygon); + } + } + } + else + { + // for details, see comment in clipPolygonOnRange for the "cutting off + // the outer parts of filled polygons at parallell lines" explanations + const B2DPolygon aClip(createPolygonFromRect(rRange)); + + return clipPolyPolygonOnPolyPolygon(rCandidate, B2DPolyPolygon(aClip), bInside, bStroke); + } + + return aRetval; + } + + B2DPolyPolygon clipPolygonOnEdge(const B2DPolygon& rCandidate, const B2DPoint& rPointA, const B2DPoint& rPointB, bool bAbove, bool bStroke) + { + B2DPolyPolygon aRetval; + + if(rPointA.equal(rPointB)) + { + // edge has no length, return polygon + aRetval.append(rCandidate); + } + else if(rCandidate.count()) + { + const B2DVector aEdge(rPointB - rPointA); + B2DPolygon aCandidate(rCandidate); + + // translate and rotate polygon so that given edge is on x axis + B2DHomMatrix aMatrixTransform(basegfx::tools::createTranslateB2DHomMatrix(-rPointA.getX(), -rPointA.getY())); + aMatrixTransform.rotate(-atan2(aEdge.getY(), aEdge.getX())); + aCandidate.transform(aMatrixTransform); + + // call clip method on X-Axis + aRetval = clipPolygonOnParallelAxis(aCandidate, true, bAbove, 0.0, bStroke); + + if(aRetval.count()) + { + // if there is a result, it needs to be transformed back + aMatrixTransform.invert(); + aRetval.transform(aMatrixTransform); + } + } + + return aRetval; + } + + B2DPolyPolygon clipPolyPolygonOnEdge(const B2DPolyPolygon& rCandidate, const B2DPoint& rPointA, const B2DPoint& rPointB, bool bAbove, bool bStroke) + { + B2DPolyPolygon aRetval; + + if(rPointA.equal(rPointB)) + { + // edge has no length, return polygon + aRetval = rCandidate; + } + else if(rCandidate.count()) + { + const B2DVector aEdge(rPointB - rPointA); + B2DPolyPolygon aCandidate(rCandidate); + + // translate and rotate polygon so that given edge is on x axis + B2DHomMatrix aMatrixTransform(basegfx::tools::createTranslateB2DHomMatrix(-rPointA.getX(), -rPointA.getY())); + aMatrixTransform.rotate(-atan2(aEdge.getY(), aEdge.getX())); + aCandidate.transform(aMatrixTransform); + + // call clip method on X-Axis + aRetval = clipPolyPolygonOnParallelAxis(aCandidate, true, bAbove, 0.0, bStroke); + + if(aRetval.count()) + { + // if there is a result, it needs to be transformed back + aMatrixTransform.invert(); + aRetval.transform(aMatrixTransform); + } + } + + return aRetval; + } + + ////////////////////////////////////////////////////////////////////////////// + + B2DPolyPolygon clipPolyPolygonOnPolyPolygon(const B2DPolyPolygon& rCandidate, const B2DPolyPolygon& rClip, bool bInside, bool bStroke) + { + B2DPolyPolygon aRetval; + + if(rCandidate.count() && rClip.count()) + { + if(bStroke) + { + // line clipping, create line snippets by first adding all cut points and + // then marching along the edges and detecting if they are inside or outside + // the clip polygon + for(sal_uInt32 a(0); a < rCandidate.count(); a++) + { + // add cuts with clip to polygon, including bezier segments + const B2DPolygon aCandidate(addPointsAtCuts(rCandidate.getB2DPolygon(a), rClip)); + const sal_uInt32 nPointCount(aCandidate.count()); + const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L); + B2DCubicBezier aEdge; + B2DPolygon aRun; + + for(sal_uInt32 b(0); b < nEdgeCount; b++) + { + aCandidate.getBezierSegment(b, aEdge); + const B2DPoint aTestPoint(aEdge.interpolatePoint(0.5)); + const bool bIsInside(tools::isInside(rClip, aTestPoint) == bInside); + + if(bIsInside) + { + if(!aRun.count()) + { + aRun.append(aEdge.getStartPoint()); + } + + if(aEdge.isBezier()) + { + aRun.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint()); + } + else + { + aRun.append(aEdge.getEndPoint()); + } + } + else + { + if(aRun.count()) + { + aRetval.append(aRun); + aRun.clear(); + } + } + } + + if(aRun.count()) + { + // try to merge this last and first polygon; they may have been + // the former polygon's start/end point + if(aRetval.count()) + { + const B2DPolygon aStartPolygon(aRetval.getB2DPolygon(0)); + + if(aStartPolygon.count() && aStartPolygon.getB2DPoint(0).equal(aRun.getB2DPoint(aRun.count() - 1))) + { + // append start polygon to aRun, remove from result set + aRun.append(aStartPolygon); aRun.removeDoublePoints(); + aRetval.remove(0); + } + } + + aRetval.append(aRun); + } + } + } + else + { + // area clipping + B2DPolyPolygon aMergePolyPolygonA(rClip); + + // First solve all polygon-self and polygon-polygon intersections. + // Also get rid of some not-needed polygons (neutral, no area -> when + // no intersections, these are tubes). + // Now it is possible to correct the orientations in the cut-free + // polygons to values corresponding to painting the PolyPolygon with + // a XOR-WindingRule. + aMergePolyPolygonA = solveCrossovers(aMergePolyPolygonA); + aMergePolyPolygonA = stripNeutralPolygons(aMergePolyPolygonA); + aMergePolyPolygonA = correctOrientations(aMergePolyPolygonA); + + if(!bInside) + { + // if we want to get the outside of the clip polygon, make + // it a 'Hole' in topological sense + aMergePolyPolygonA.flip(); + } + + B2DPolyPolygon aMergePolyPolygonB(rCandidate); + + // prepare 2nd source polygon in same way + aMergePolyPolygonB = solveCrossovers(aMergePolyPolygonB); + aMergePolyPolygonB = stripNeutralPolygons(aMergePolyPolygonB); + aMergePolyPolygonB = correctOrientations(aMergePolyPolygonB); + + // to clip against each other, concatenate and solve all + // polygon-polygon crossovers. polygon-self do not need to + // be solved again, they were solved in the preparation. + aRetval.append(aMergePolyPolygonA); + aRetval.append(aMergePolyPolygonB); + aRetval = solveCrossovers(aRetval); + + // now remove neutral polygons (closed, but no area). In a last + // step throw away all polygons which have a depth of less than 1 + // which means there was no logical AND at their position. For the + // not-inside solution, the clip was flipped to define it as 'Hole', + // so the removal rule is different here; remove all with a depth + // of less than 0 (aka holes). + aRetval = stripNeutralPolygons(aRetval); + aRetval = stripDispensablePolygons(aRetval, bInside); + } + } + + return aRetval; + } + + ////////////////////////////////////////////////////////////////////////////// + + B2DPolyPolygon clipPolygonOnPolyPolygon(const B2DPolygon& rCandidate, const B2DPolyPolygon& rClip, bool bInside, bool bStroke) + { + B2DPolyPolygon aRetval; + + if(rCandidate.count() && rClip.count()) + { + aRetval = clipPolyPolygonOnPolyPolygon(B2DPolyPolygon(rCandidate), rClip, bInside, bStroke); + } + + return aRetval; + } + + ////////////////////////////////////////////////////////////////////////////// + + /* + * let a plane be defined as + * + * v.n+d=0 + * + * and a ray be defined as + * + * a+(b-a)*t=0 + * + * substitute and rearranging yields + * + * t = -(a.n+d)/(n.(b-a)) + * + * if the denominator is zero, the line is either + * contained in the plane or parallel to the plane. + * in either case, there is no intersection. + * if numerator and denominator are both zero, the + * ray is contained in the plane. + * + */ + struct scissor_plane { + double nx,ny; // plane normal + double d; // [-] minimum distance from origin + sal_uInt32 clipmask; // clipping mask, e.g. 1000 1000 + }; + + /* + * + * polygon clipping rules (straight out of Foley and Van Dam) + * =========================================================== + * current |next |emit + * ____________________________________ + * inside |inside |next + * inside |outside |intersect with clip plane + * outside |outside |nothing + * outside |inside |intersect with clip plane follwed by next + * + */ + sal_uInt32 scissorLineSegment( ::basegfx::B2DPoint *in_vertex, // input buffer + sal_uInt32 in_count, // number of verts in input buffer + ::basegfx::B2DPoint *out_vertex, // output buffer + scissor_plane *pPlane, // scissoring plane + const ::basegfx::B2DRectangle &rR ) // clipping rectangle + { + ::basegfx::B2DPoint *curr; + ::basegfx::B2DPoint *next; + + sal_uInt32 out_count=0; + + // process all the verts + for(sal_uInt32 i=0; i<in_count; i++) { + + // vertices are relative to the coordinate + // system defined by the rectangle. + curr = &in_vertex[i]; + next = &in_vertex[(i+1)%in_count]; + + // perform clipping judgement & mask against current plane. + sal_uInt32 clip = pPlane->clipmask & ((getCohenSutherlandClipFlags(*curr,rR)<<4)|getCohenSutherlandClipFlags(*next,rR)); + + if(clip==0) { // both verts are inside + out_vertex[out_count++] = *next; + } + else if((clip&0x0f) && (clip&0xf0)) { // both verts are outside + } + else if((clip&0x0f) && (clip&0xf0)==0) { // curr is inside, next is outside + + // direction vector from 'current' to 'next', *not* normalized + // to bring 't' into the [0<=x<=1] intervall. + ::basegfx::B2DPoint dir((*next)-(*curr)); + + double denominator = ( pPlane->nx*dir.getX() + + pPlane->ny*dir.getY() ); + double numerator = ( pPlane->nx*curr->getX() + + pPlane->ny*curr->getY() + + pPlane->d ); + double t = -numerator/denominator; + + // calculate the actual point of intersection + ::basegfx::B2DPoint intersection( curr->getX()+t*dir.getX(), + curr->getY()+t*dir.getY() ); + + out_vertex[out_count++] = intersection; + } + else if((clip&0x0f)==0 && (clip&0xf0)) { // curr is outside, next is inside + + // direction vector from 'current' to 'next', *not* normalized + // to bring 't' into the [0<=x<=1] intervall. + ::basegfx::B2DPoint dir((*next)-(*curr)); + + double denominator = ( pPlane->nx*dir.getX() + + pPlane->ny*dir.getY() ); + double numerator = ( pPlane->nx*curr->getX() + + pPlane->ny*curr->getY() + + pPlane->d ); + double t = -numerator/denominator; + + // calculate the actual point of intersection + ::basegfx::B2DPoint intersection( curr->getX()+t*dir.getX(), + curr->getY()+t*dir.getY() ); + + out_vertex[out_count++] = intersection; + out_vertex[out_count++] = *next; + } + } + + return out_count; + } + + B2DPolygon clipTriangleListOnRange( const B2DPolygon& rCandidate, + const B2DRange& rRange ) + { + B2DPolygon aResult; + + if( !(rCandidate.count()%3) ) + { + const int scissor_plane_count = 4; + + scissor_plane sp[scissor_plane_count]; + + sp[0].nx = +1.0; + sp[0].ny = +0.0; + sp[0].d = -(rRange.getMinX()); + sp[0].clipmask = (RectClipFlags::LEFT << 4) | RectClipFlags::LEFT; // 0001 0001 + sp[1].nx = -1.0; + sp[1].ny = +0.0; + sp[1].d = +(rRange.getMaxX()); + sp[1].clipmask = (RectClipFlags::RIGHT << 4) | RectClipFlags::RIGHT; // 0010 0010 + sp[2].nx = +0.0; + sp[2].ny = +1.0; + sp[2].d = -(rRange.getMinY()); + sp[2].clipmask = (RectClipFlags::TOP << 4) | RectClipFlags::TOP; // 0100 0100 + sp[3].nx = +0.0; + sp[3].ny = -1.0; + sp[3].d = +(rRange.getMaxY()); + sp[3].clipmask = (RectClipFlags::BOTTOM << 4) | RectClipFlags::BOTTOM; // 1000 1000 + + // retrieve the number of vertices of the triangulated polygon + const sal_uInt32 nVertexCount = rCandidate.count(); + + if(nVertexCount) + { + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + // + // Upper bound for the maximal number of vertices when intersecting an + // axis-aligned rectangle with a triangle in E2 + // + // The rectangle and the triangle are in general position, and have 4 and 3 + // vertices, respectively. + // + // Lemma: Since the rectangle is a convex polygon ( see + // http://mathworld.wolfram.com/ConvexPolygon.html for a definition), and + // has no holes, it follows that any straight line will intersect the + // rectangle's border line at utmost two times (with the usual + // tie-breaking rule, if the intersection exactly hits an already existing + // rectangle vertex, that this intersection is only attributed to one of + // the adjoining edges). Thus, having a rectangle intersected with + // a half-plane (one side of a straight line denotes 'inside', the + // other 'outside') will at utmost add _one_ vertex to the resulting + // intersection polygon (adding two intersection vertices, and removing at + // least one rectangle vertex): + // + // * + // +--+-----------------+ + // | * | + // |* | + // + | + // *| | + // * | | + // +--------------------+ + // + // Proof: If the straight line intersects the rectangle two + // times, it does so for distinct edges, i.e. the intersection has + // minimally one of the rectangle's vertices on either side of the straight + // line (but maybe more). Thus, the intersection with a half-plane has + // minimally _one_ rectangle vertex removed from the resulting clip + // polygon, and therefore, a clip against a half-plane has the net effect + // of adding at utmost _one_ vertex to the resulting clip polygon. + // + // Theorem: The intersection of a rectangle and a triangle results in a + // polygon with at utmost 7 vertices. + // + // Proof: The inside of the triangle can be described as the consecutive + // intersection with three half-planes. Together with the lemma above, this + // results in at utmost 3 additional vertices added to the already existing 4 + // rectangle vertices. + // + // This upper bound is attained with the following example configuration: + // + // * + // *** + // ** * + // ** * + // ** * + // ** * + // ** * + // ** * + // ** * + // ** * + // ** * + // ----*2--------3 * + // | ** |* + // 1* 4 + // **| *| + // ** | * | + // **| * | + // 7* * | + // --*6-----5----- + // ** * + // ** + // + // As we need to scissor all triangles against the + // output rectangle we employ an output buffer for the + // resulting vertices. the question is how large this + // buffer needs to be compared to the number of + // incoming vertices. this buffer needs to hold at + // most the number of original vertices times '7'. see + // figure above for an example. scissoring triangles + // with the cohen-sutherland line clipping algorithm + // as implemented here will result in a triangle fan + // which will be rendered as separate triangles to + // avoid pipeline stalls for each scissored + // triangle. creating separate triangles from a + // triangle fan produces (n-2)*3 vertices where n is + // the number of vertices of the original triangle + // fan. for the maximum number of 7 vertices of + // resulting triangle fans we therefore need 15 times + // the number of original vertices. + // + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + + //const size_t nBufferSize = sizeof(vertex)*(nVertexCount*16); + //vertex *pVertices = (vertex*)alloca(nBufferSize); + //sal_uInt32 nNumOutput = 0; + + // we need to clip this triangle against the output rectangle + // to ensure that the resulting texture coordinates are in + // the valid range from [0<=st<=1]. under normal circustances + // we could use the BORDERCOLOR renderstate but some cards + // seem to ignore this feature. + ::basegfx::B2DPoint stack[3]; + unsigned int clipflag = 0; + + for(sal_uInt32 nIndex=0; nIndex<nVertexCount; ++nIndex) + { + // rotate stack + stack[0] = stack[1]; + stack[1] = stack[2]; + stack[2] = rCandidate.getB2DPoint(nIndex); + + // clipping judgement + clipflag |= !(rRange.isInside(stack[2])); + + if(nIndex > 1) + { + // consume vertices until a single seperate triangle has been visited. + if(!((nIndex+1)%3)) + { + // if any of the last three vertices was outside + // we need to scissor against the destination rectangle + if(clipflag & 7) + { + ::basegfx::B2DPoint buf0[16]; + ::basegfx::B2DPoint buf1[16]; + + sal_uInt32 vertex_count = 3; + + // clip against all 4 planes passing the result of + // each plane as the input to the next using a double buffer + vertex_count = scissorLineSegment(stack,vertex_count,buf1,&sp[0],rRange); + vertex_count = scissorLineSegment(buf1,vertex_count,buf0,&sp[1],rRange); + vertex_count = scissorLineSegment(buf0,vertex_count,buf1,&sp[2],rRange); + vertex_count = scissorLineSegment(buf1,vertex_count,buf0,&sp[3],rRange); + + if(vertex_count >= 3) + { + // convert triangle fan back to triangle list. + ::basegfx::B2DPoint v0(buf0[0]); + ::basegfx::B2DPoint v1(buf0[1]); + for(sal_uInt32 i=2; i<vertex_count; ++i) + { + ::basegfx::B2DPoint v2(buf0[i]); + aResult.append(v0); + aResult.append(v1); + aResult.append(v2); + v1 = v2; + } + } + } + else + { + // the last triangle has not been altered, simply copy to result + for(sal_uInt32 i=0; i<3; ++i) + aResult.append(stack[i]); + } + } + } + + clipflag <<= 1; + } + } + } + + return aResult; + } + + ////////////////////////////////////////////////////////////////////////////// + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +// eof diff --git a/basegfx/source/polygon/b2dpolygoncutandtouch.cxx b/basegfx/source/polygon/b2dpolygoncutandtouch.cxx new file mode 100644 index 000000000000..11955ceb22f9 --- /dev/null +++ b/basegfx/source/polygon/b2dpolygoncutandtouch.cxx @@ -0,0 +1,1299 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <basegfx/polygon/b2dpolygoncutandtouch.hxx> +#include <osl/diagnose.h> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> + +#include <vector> +#include <algorithm> + +////////////////////////////////////////////////////////////////////////////// +// defines + +#define SUBDIVIDE_FOR_CUT_TEST_COUNT (50) + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace + { + //////////////////////////////////////////////////////////////////////////////// + + class temporaryPoint + { + B2DPoint maPoint; // the new point + sal_uInt32 mnIndex; // index after which to insert + double mfCut; // parametric cut description [0.0 .. 1.0] + + public: + temporaryPoint(const B2DPoint& rNewPoint, sal_uInt32 nIndex, double fCut) + : maPoint(rNewPoint), + mnIndex(nIndex), + mfCut(fCut) + { + } + + bool operator<(const temporaryPoint& rComp) const + { + if(mnIndex == rComp.mnIndex) + { + return (mfCut < rComp.mfCut); + } + + return (mnIndex < rComp.mnIndex); + } + + const B2DPoint& getPoint() const { return maPoint; } + sal_uInt32 getIndex() const { return mnIndex; } + double getCut() const { return mfCut; } + }; + + //////////////////////////////////////////////////////////////////////////////// + + typedef ::std::vector< temporaryPoint > temporaryPointVector; + + //////////////////////////////////////////////////////////////////////////////// + + class temporaryPolygonData + { + B2DPolygon maPolygon; + B2DRange maRange; + temporaryPointVector maPoints; + + public: + const B2DPolygon& getPolygon() const { return maPolygon; } + void setPolygon(const B2DPolygon& rNew) { maPolygon = rNew; maRange = tools::getRange(maPolygon); } + const B2DRange& getRange() const { return maRange; } + temporaryPointVector& getTemporaryPointVector() { return maPoints; } + }; + + //////////////////////////////////////////////////////////////////////////////// + + B2DPolygon mergeTemporaryPointsAndPolygon(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints) + { + // #i76891# mergeTemporaryPointsAndPolygon redesigned to be able to correctly handle + // single edges with/without control points + // #i101491# added counter for non-changing element count + const sal_uInt32 nTempPointCount(rTempPoints.size()); + + if(nTempPointCount) + { + B2DPolygon aRetval; + const sal_uInt32 nCount(rCandidate.count()); + + if(nCount) + { + // sort temp points to assure increasing fCut values and increasing indices + ::std::sort(rTempPoints.begin(), rTempPoints.end()); + + // prepare loop + B2DCubicBezier aEdge; + sal_uInt32 nNewInd(0L); + + // add start point + aRetval.append(rCandidate.getB2DPoint(0)); + + for(sal_uInt32 a(0L); a < nCount; a++) + { + // get edge + rCandidate.getBezierSegment(a, aEdge); + + if(aEdge.isBezier()) + { + // control vectors involved for this edge + double fLeftStart(0.0); + + // now add all points targeted to be at this index + while(nNewInd < nTempPointCount && rTempPoints[nNewInd].getIndex() == a) + { + const temporaryPoint& rTempPoint = rTempPoints[nNewInd++]; + + // split curve segment. Splits need to come sorted and need to be < 1.0. Also, + // since original segment is consumed from left to right, the cut values need + // to be scaled to the remaining part + B2DCubicBezier aLeftPart; + const double fRelativeSplitPoint((rTempPoint.getCut() - fLeftStart) / (1.0 - fLeftStart)); + aEdge.split(fRelativeSplitPoint, &aLeftPart, &aEdge); + fLeftStart = rTempPoint.getCut(); + + // add left bow + aRetval.appendBezierSegment(aLeftPart.getControlPointA(), aLeftPart.getControlPointB(), rTempPoint.getPoint()); + } + + // add remaining bow + aRetval.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint()); + } + else + { + // add all points targeted to be at this index + while(nNewInd < nTempPointCount && rTempPoints[nNewInd].getIndex() == a) + { + const temporaryPoint& rTempPoint = rTempPoints[nNewInd++]; + const B2DPoint aNewPoint(rTempPoint.getPoint()); + + // do not add points double + if(!aRetval.getB2DPoint(aRetval.count() - 1L).equal(aNewPoint)) + { + aRetval.append(aNewPoint); + } + } + + // add edge end point + aRetval.append(aEdge.getEndPoint()); + } + } + } + + if(rCandidate.isClosed()) + { + // set closed flag and correct last point (which is added double now). + tools::closeWithGeometryChange(aRetval); + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + //////////////////////////////////////////////////////////////////////////////// + + void adaptAndTransferCutsWithBezierSegment( + const temporaryPointVector& rPointVector, const B2DPolygon& rPolygon, + sal_uInt32 nInd, temporaryPointVector& rTempPoints) + { + // assuming that the subdivision to create rPolygon used aequidistant pieces + // (as in adaptiveSubdivideByCount) it is now possible to calculate back the + // cut positions in the polygon to relative cut positions on the original bezier + // segment. + const sal_uInt32 nTempPointCount(rPointVector.size()); + const sal_uInt32 nEdgeCount(rPolygon.count() ? rPolygon.count() - 1L : 0L); + + if(nTempPointCount && nEdgeCount) + { + for(sal_uInt32 a(0L); a < nTempPointCount; a++) + { + const temporaryPoint& rTempPoint = rPointVector[a]; + const double fCutPosInPolygon((double)rTempPoint.getIndex() + rTempPoint.getCut()); + const double fRelativeCutPos(fCutPosInPolygon / (double)nEdgeCount); + rTempPoints.push_back(temporaryPoint(rTempPoint.getPoint(), nInd, fRelativeCutPos)); + } + } + } + + //////////////////////////////////////////////////////////////////////////////// + + } // end of anonymous namespace +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace + { + //////////////////////////////////////////////////////////////////////////////// + // predefines for calls to this methods before method implementation + + void findCuts(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints); + void findTouches(const B2DPolygon& rEdgePolygon, const B2DPolygon& rPointPolygon, temporaryPointVector& rTempPoints); + void findCuts(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB); + + //////////////////////////////////////////////////////////////////////////////// + + void findEdgeCutsTwoEdges( + const B2DPoint& rCurrA, const B2DPoint& rNextA, + const B2DPoint& rCurrB, const B2DPoint& rNextB, + sal_uInt32 nIndA, sal_uInt32 nIndB, + temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB) + { + // no null length edges + if(!(rCurrA.equal(rNextA) || rCurrB.equal(rNextB))) + { + // no common start/end points, this can be no cuts + if(!(rCurrB.equal(rCurrA) || rCurrB.equal(rNextA) || rNextB.equal(rCurrA) || rNextB.equal(rNextA))) + { + const B2DVector aVecA(rNextA - rCurrA); + const B2DVector aVecB(rNextB - rCurrB); + double fCut(aVecA.cross(aVecB)); + + if(!fTools::equalZero(fCut)) + { + const double fZero(0.0); + const double fOne(1.0); + fCut = (aVecB.getY() * (rCurrB.getX() - rCurrA.getX()) + aVecB.getX() * (rCurrA.getY() - rCurrB.getY())) / fCut; + + if(fTools::more(fCut, fZero) && fTools::less(fCut, fOne)) + { + // it's a candidate, but also need to test parameter value of cut on line 2 + double fCut2; + + // choose the more precise version + if(fabs(aVecB.getX()) > fabs(aVecB.getY())) + { + fCut2 = (rCurrA.getX() + (fCut * aVecA.getX()) - rCurrB.getX()) / aVecB.getX(); + } + else + { + fCut2 = (rCurrA.getY() + (fCut * aVecA.getY()) - rCurrB.getY()) / aVecB.getY(); + } + + if(fTools::more(fCut2, fZero) && fTools::less(fCut2, fOne)) + { + // cut is in range, add point. Two edges can have only one cut, but + // add a cut point to each list. The lists may be the same for + // self intersections. + const B2DPoint aCutPoint(interpolate(rCurrA, rNextA, fCut)); + rTempPointsA.push_back(temporaryPoint(aCutPoint, nIndA, fCut)); + rTempPointsB.push_back(temporaryPoint(aCutPoint, nIndB, fCut2)); + } + } + } + } + } + } + + //////////////////////////////////////////////////////////////////////////////// + + void findCutsAndTouchesAndCommonForBezier(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB) + { + // #i76891# + // This new method is necessary since in findEdgeCutsBezierAndEdge and in findEdgeCutsTwoBeziers + // it is not sufficient to use findCuts() recursively. This will indeed find the cuts between the + // segments of the two temporarily adaptive subdivided bezier segments, but not the touches or + // equal points of them. + // It would be possible to find the toches using findTouches(), but at last with commpn points + // the adding of cut points (temporary points) would fail. But for these temporarily adaptive + // subdivided bezier segments, common points may be not very likely, but the bug shows that it + // happens. + // Touch points are a little bit more likely than common points. All in all it is best to use + // a specialized method here which can profit from knowing that it is working on a special + // family of B2DPolygons: no curve segments included and not closed. + OSL_ENSURE(!rCandidateA.areControlPointsUsed() && !rCandidateB.areControlPointsUsed(), "findCutsAndTouchesAndCommonForBezier only works with subdivided polygons (!)"); + OSL_ENSURE(!rCandidateA.isClosed() && !rCandidateB.isClosed(), "findCutsAndTouchesAndCommonForBezier only works with opened polygons (!)"); + const sal_uInt32 nPointCountA(rCandidateA.count()); + const sal_uInt32 nPointCountB(rCandidateB.count()); + + if(nPointCountA > 1 && nPointCountB > 1) + { + const sal_uInt32 nEdgeCountA(nPointCountA - 1); + const sal_uInt32 nEdgeCountB(nPointCountB - 1); + B2DPoint aCurrA(rCandidateA.getB2DPoint(0L)); + + for(sal_uInt32 a(0L); a < nEdgeCountA; a++) + { + const B2DPoint aNextA(rCandidateA.getB2DPoint(a + 1L)); + const B2DRange aRangeA(aCurrA, aNextA); + B2DPoint aCurrB(rCandidateB.getB2DPoint(0L)); + + for(sal_uInt32 b(0L); b < nEdgeCountB; b++) + { + const B2DPoint aNextB(rCandidateB.getB2DPoint(b + 1L)); + const B2DRange aRangeB(aCurrB, aNextB); + + if(aRangeA.overlaps(aRangeB)) + { + // no null length edges + if(!(aCurrA.equal(aNextA) || aCurrB.equal(aNextB))) + { + const B2DVector aVecA(aNextA - aCurrA); + const B2DVector aVecB(aNextB - aCurrB); + double fCutA(aVecA.cross(aVecB)); + + if(!fTools::equalZero(fCutA)) + { + const double fZero(0.0); + const double fOne(1.0); + fCutA = (aVecB.getY() * (aCurrB.getX() - aCurrA.getX()) + aVecB.getX() * (aCurrA.getY() - aCurrB.getY())) / fCutA; + + // use range [0.0 .. 1.0[, thus in the loop, all direct aCurrA cuts will be registered + // as 0.0 cut. The 1.0 cut will be registered in the next loop step + if(fTools::moreOrEqual(fCutA, fZero) && fTools::less(fCutA, fOne)) + { + // it's a candidate, but also need to test parameter value of cut on line 2 + double fCutB; + + // choose the more precise version + if(fabs(aVecB.getX()) > fabs(aVecB.getY())) + { + fCutB = (aCurrA.getX() + (fCutA * aVecA.getX()) - aCurrB.getX()) / aVecB.getX(); + } + else + { + fCutB = (aCurrA.getY() + (fCutA * aVecA.getY()) - aCurrB.getY()) / aVecB.getY(); + } + + // use range [0.0 .. 1.0[, thus in the loop, all direct aCurrA cuts will be registered + // as 0.0 cut. The 1.0 cut will be registered in the next loop step + if(fTools::moreOrEqual(fCutB, fZero) && fTools::less(fCutB, fOne)) + { + // cut is in both ranges. Add points for A and B + if(fTools::equalZero(fCutA)) + { + // ignore for start point in first edge; this is handled + // by outer methods and would just produce a double point + if(a) + { + rTempPointsA.push_back(temporaryPoint(aCurrA, a, 0.0)); + } + } + else + { + const B2DPoint aCutPoint(interpolate(aCurrA, aNextA, fCutA)); + rTempPointsA.push_back(temporaryPoint(aCutPoint, a, fCutA)); + } + + if(fTools::equalZero(fCutB)) + { + // ignore for start point in first edge; this is handled + // by outer methods and would just produce a double point + if(b) + { + rTempPointsB.push_back(temporaryPoint(aCurrB, b, 0.0)); + } + } + else + { + const B2DPoint aCutPoint(interpolate(aCurrB, aNextB, fCutB)); + rTempPointsB.push_back(temporaryPoint(aCutPoint, b, fCutB)); + } + } + } + } + } + } + + // prepare next step + aCurrB = aNextB; + } + + // prepare next step + aCurrA = aNextA; + } + } + } + + //////////////////////////////////////////////////////////////////////////////// + + void findEdgeCutsBezierAndEdge( + const B2DCubicBezier& rCubicA, + const B2DPoint& rCurrB, const B2DPoint& rNextB, + sal_uInt32 nIndA, sal_uInt32 nIndB, + temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB) + { + // find all cuts between given bezier segment and edge. Add an entry to the tempPoints + // for each common point with the cut value describing the relative position on given + // bezier segment and edge. + B2DPolygon aTempPolygonA; + B2DPolygon aTempPolygonEdge; + temporaryPointVector aTempPointVectorA; + temporaryPointVector aTempPointVectorEdge; + + // create subdivided polygons and find cuts between them + // Keep adaptiveSubdivideByCount due to needed quality + aTempPolygonA.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8); + aTempPolygonA.append(rCubicA.getStartPoint()); + rCubicA.adaptiveSubdivideByCount(aTempPolygonA, SUBDIVIDE_FOR_CUT_TEST_COUNT); + aTempPolygonEdge.append(rCurrB); + aTempPolygonEdge.append(rNextB); + + // #i76891# using findCuts recursively is not sufficient here + findCutsAndTouchesAndCommonForBezier(aTempPolygonA, aTempPolygonEdge, aTempPointVectorA, aTempPointVectorEdge); + + if(aTempPointVectorA.size()) + { + // adapt tempVector entries to segment + adaptAndTransferCutsWithBezierSegment(aTempPointVectorA, aTempPolygonA, nIndA, rTempPointsA); + } + + // append remapped tempVector entries for edge to tempPoints for edge + for(sal_uInt32 a(0L); a < aTempPointVectorEdge.size(); a++) + { + const temporaryPoint& rTempPoint = aTempPointVectorEdge[a]; + rTempPointsB.push_back(temporaryPoint(rTempPoint.getPoint(), nIndB, rTempPoint.getCut())); + } + } + + //////////////////////////////////////////////////////////////////////////////// + + void findEdgeCutsTwoBeziers( + const B2DCubicBezier& rCubicA, + const B2DCubicBezier& rCubicB, + sal_uInt32 nIndA, sal_uInt32 nIndB, + temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB) + { + // find all cuts between the two given bezier segments. Add an entry to the tempPoints + // for each common point with the cut value describing the relative position on given + // bezier segments. + B2DPolygon aTempPolygonA; + B2DPolygon aTempPolygonB; + temporaryPointVector aTempPointVectorA; + temporaryPointVector aTempPointVectorB; + + // create subdivided polygons and find cuts between them + // Keep adaptiveSubdivideByCount due to needed quality + aTempPolygonA.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8); + aTempPolygonA.append(rCubicA.getStartPoint()); + rCubicA.adaptiveSubdivideByCount(aTempPolygonA, SUBDIVIDE_FOR_CUT_TEST_COUNT); + aTempPolygonB.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8); + aTempPolygonB.append(rCubicB.getStartPoint()); + rCubicB.adaptiveSubdivideByCount(aTempPolygonB, SUBDIVIDE_FOR_CUT_TEST_COUNT); + + // #i76891# using findCuts recursively is not sufficient here + findCutsAndTouchesAndCommonForBezier(aTempPolygonA, aTempPolygonB, aTempPointVectorA, aTempPointVectorB); + + if(aTempPointVectorA.size()) + { + // adapt tempVector entries to segment + adaptAndTransferCutsWithBezierSegment(aTempPointVectorA, aTempPolygonA, nIndA, rTempPointsA); + } + + if(aTempPointVectorB.size()) + { + // adapt tempVector entries to segment + adaptAndTransferCutsWithBezierSegment(aTempPointVectorB, aTempPolygonB, nIndB, rTempPointsB); + } + } + + //////////////////////////////////////////////////////////////////////////////// + + void findEdgeCutsOneBezier( + const B2DCubicBezier& rCubicA, + sal_uInt32 nInd, temporaryPointVector& rTempPoints) + { + // avoid expensive part of this method if possible + // TODO: use hasAnyExtremum() method instead when it becomes available + double fDummy; + const bool bHasAnyExtremum = rCubicA.getMinimumExtremumPosition( fDummy ); + if( !bHasAnyExtremum ) + return; + + // find all self-intersections on the given bezier segment. Add an entry to the tempPoints + // for each self intersection point with the cut value describing the relative position on given + // bezier segment. + B2DPolygon aTempPolygon; + temporaryPointVector aTempPointVector; + + // create subdivided polygon and find cuts on it + // Keep adaptiveSubdivideByCount due to needed quality + aTempPolygon.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8); + aTempPolygon.append(rCubicA.getStartPoint()); + rCubicA.adaptiveSubdivideByCount(aTempPolygon, SUBDIVIDE_FOR_CUT_TEST_COUNT); + findCuts(aTempPolygon, aTempPointVector); + + if(aTempPointVector.size()) + { + // adapt tempVector entries to segment + adaptAndTransferCutsWithBezierSegment(aTempPointVector, aTempPolygon, nInd, rTempPoints); + } + } + + //////////////////////////////////////////////////////////////////////////////// + + void findCuts(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints) + { + // find out if there are edges with intersections (self-cuts). If yes, add + // entries to rTempPoints accordingly + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount) + { + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); + + if(nEdgeCount) + { + const bool bCurvesInvolved(rCandidate.areControlPointsUsed()); + + if(bCurvesInvolved) + { + B2DCubicBezier aCubicA; + B2DCubicBezier aCubicB; + + for(sal_uInt32 a(0L); a < nEdgeCount - 1L; a++) + { + rCandidate.getBezierSegment(a, aCubicA); + aCubicA.testAndSolveTrivialBezier(); + const bool bEdgeAIsCurve(aCubicA.isBezier()); + const B2DRange aRangeA(aCubicA.getRange()); + + if(bEdgeAIsCurve) + { + // curved segments may have self-intersections, do not forget those (!) + findEdgeCutsOneBezier(aCubicA, a, rTempPoints); + } + + for(sal_uInt32 b(a + 1L); b < nEdgeCount; b++) + { + rCandidate.getBezierSegment(b, aCubicB); + aCubicB.testAndSolveTrivialBezier(); + const bool bEdgeBIsCurve(aCubicB.isBezier()); + const B2DRange aRangeB(aCubicB.getRange()); + + // only overlapping segments need to be tested + // consecutive segments touch of course + bool bOverlap = false; + if( b > a+1) + bOverlap = aRangeA.overlaps(aRangeB); + else + bOverlap = aRangeA.overlapsMore(aRangeB); + if( bOverlap) + { + if(bEdgeAIsCurve && bEdgeBIsCurve) + { + // test for bezier-bezier cuts + findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, rTempPoints, rTempPoints); + } + else if(bEdgeAIsCurve) + { + // test for bezier-edge cuts + findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, rTempPoints, rTempPoints); + } + else if(bEdgeBIsCurve) + { + // test for bezier-edge cuts + findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, rTempPoints, rTempPoints); + } + else + { + // test for simple edge-edge cuts + findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(), + a, b, rTempPoints, rTempPoints); + } + } + } + } + } + else + { + B2DPoint aCurrA(rCandidate.getB2DPoint(0L)); + + for(sal_uInt32 a(0L); a < nEdgeCount - 1L; a++) + { + const B2DPoint aNextA(rCandidate.getB2DPoint(a + 1L == nPointCount ? 0L : a + 1L)); + const B2DRange aRangeA(aCurrA, aNextA); + B2DPoint aCurrB(rCandidate.getB2DPoint(a + 1L)); + + for(sal_uInt32 b(a + 1L); b < nEdgeCount; b++) + { + const B2DPoint aNextB(rCandidate.getB2DPoint(b + 1L == nPointCount ? 0L : b + 1L)); + const B2DRange aRangeB(aCurrB, aNextB); + + // consecutive segments touch of course + bool bOverlap = false; + if( b > a+1) + bOverlap = aRangeA.overlaps(aRangeB); + else + bOverlap = aRangeA.overlapsMore(aRangeB); + if( bOverlap) + { + findEdgeCutsTwoEdges(aCurrA, aNextA, aCurrB, aNextB, a, b, rTempPoints, rTempPoints); + } + + // prepare next step + aCurrB = aNextB; + } + + // prepare next step + aCurrA = aNextA; + } + } + } + } + } + + //////////////////////////////////////////////////////////////////////////////// + + } // end of anonymous namespace +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace + { + //////////////////////////////////////////////////////////////////////////////// + + void findTouchesOnEdge( + const B2DPoint& rCurr, const B2DPoint& rNext, const B2DPolygon& rPointPolygon, + sal_uInt32 nInd, temporaryPointVector& rTempPoints) + { + // find out if points from rPointPolygon are positioned on given edge. If Yes, add + // points there to represent touches (which may be enter or leave nodes later). + const sal_uInt32 nPointCount(rPointPolygon.count()); + + if(nPointCount) + { + const B2DRange aRange(rCurr, rNext); + const B2DVector aEdgeVector(rNext - rCurr); + B2DVector aNormalizedEdgeVector(aEdgeVector); + aNormalizedEdgeVector.normalize(); + bool bTestUsingX(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY())); + + for(sal_uInt32 a(0L); a < nPointCount; a++) + { + const B2DPoint aTestPoint(rPointPolygon.getB2DPoint(a)); + + if(aRange.isInside(aTestPoint)) + { + if(!aTestPoint.equal(rCurr) && !aTestPoint.equal(rNext)) + { + const B2DVector aTestVector(aTestPoint - rCurr); + + if(areParallel(aNormalizedEdgeVector, aTestVector)) + { + const double fCut((bTestUsingX) + ? aTestVector.getX() / aEdgeVector.getX() + : aTestVector.getY() / aEdgeVector.getY()); + const double fZero(0.0); + const double fOne(1.0); + + if(fTools::more(fCut, fZero) && fTools::less(fCut, fOne)) + { + rTempPoints.push_back(temporaryPoint(aTestPoint, nInd, fCut)); + } + } + } + } + } + } + } + + //////////////////////////////////////////////////////////////////////////////// + + void findTouchesOnCurve( + const B2DCubicBezier& rCubicA, const B2DPolygon& rPointPolygon, + sal_uInt32 nInd, temporaryPointVector& rTempPoints) + { + // find all points from rPointPolygon which touch the given bezier segment. Add an entry + // for each touch to the given pointVector. The cut for that entry is the relative position on + // the given bezier segment. + B2DPolygon aTempPolygon; + temporaryPointVector aTempPointVector; + + // create subdivided polygon and find cuts on it + // Keep adaptiveSubdivideByCount due to needed quality + aTempPolygon.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8); + aTempPolygon.append(rCubicA.getStartPoint()); + rCubicA.adaptiveSubdivideByCount(aTempPolygon, SUBDIVIDE_FOR_CUT_TEST_COUNT); + findTouches(aTempPolygon, rPointPolygon, aTempPointVector); + + if(aTempPointVector.size()) + { + // adapt tempVector entries to segment + adaptAndTransferCutsWithBezierSegment(aTempPointVector, aTempPolygon, nInd, rTempPoints); + } + } + + //////////////////////////////////////////////////////////////////////////////// + + void findTouches(const B2DPolygon& rEdgePolygon, const B2DPolygon& rPointPolygon, temporaryPointVector& rTempPoints) + { + // find out if points from rPointPolygon touch edges from rEdgePolygon. If yes, + // add entries to rTempPoints + const sal_uInt32 nPointCount(rPointPolygon.count()); + const sal_uInt32 nEdgePointCount(rEdgePolygon.count()); + + if(nPointCount && nEdgePointCount) + { + const sal_uInt32 nEdgeCount(rEdgePolygon.isClosed() ? nEdgePointCount : nEdgePointCount - 1L); + B2DPoint aCurr(rEdgePolygon.getB2DPoint(0)); + + for(sal_uInt32 a(0L); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nEdgePointCount); + const B2DPoint aNext(rEdgePolygon.getB2DPoint(nNextIndex)); + + if(!aCurr.equal(aNext)) + { + bool bHandleAsSimpleEdge(true); + + if(rEdgePolygon.areControlPointsUsed()) + { + const B2DPoint aNextControlPoint(rEdgePolygon.getNextControlPoint(a)); + const B2DPoint aPrevControlPoint(rEdgePolygon.getPrevControlPoint(nNextIndex)); + const bool bEdgeIsCurve(!aNextControlPoint.equal(aCurr) || !aPrevControlPoint.equal(aNext)); + + if(bEdgeIsCurve) + { + bHandleAsSimpleEdge = false; + const B2DCubicBezier aCubicA(aCurr, aNextControlPoint, aPrevControlPoint, aNext); + findTouchesOnCurve(aCubicA, rPointPolygon, a, rTempPoints); + } + } + + if(bHandleAsSimpleEdge) + { + findTouchesOnEdge(aCurr, aNext, rPointPolygon, a, rTempPoints); + } + } + + // next step + aCurr = aNext; + } + } + } + + //////////////////////////////////////////////////////////////////////////////// + + } // end of anonymous namespace +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace + { + //////////////////////////////////////////////////////////////////////////////// + + void findCuts(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB) + { + // find out if edges from both polygons cut. If so, add entries to rTempPoints which + // should be added to the polygons accordingly + const sal_uInt32 nPointCountA(rCandidateA.count()); + const sal_uInt32 nPointCountB(rCandidateB.count()); + + if(nPointCountA && nPointCountB) + { + const sal_uInt32 nEdgeCountA(rCandidateA.isClosed() ? nPointCountA : nPointCountA - 1L); + const sal_uInt32 nEdgeCountB(rCandidateB.isClosed() ? nPointCountB : nPointCountB - 1L); + + if(nEdgeCountA && nEdgeCountB) + { + const bool bCurvesInvolved(rCandidateA.areControlPointsUsed() || rCandidateB.areControlPointsUsed()); + + if(bCurvesInvolved) + { + B2DCubicBezier aCubicA; + B2DCubicBezier aCubicB; + + for(sal_uInt32 a(0L); a < nEdgeCountA; a++) + { + rCandidateA.getBezierSegment(a, aCubicA); + aCubicA.testAndSolveTrivialBezier(); + const bool bEdgeAIsCurve(aCubicA.isBezier()); + const B2DRange aRangeA(aCubicA.getRange()); + + for(sal_uInt32 b(0L); b < nEdgeCountB; b++) + { + rCandidateB.getBezierSegment(b, aCubicB); + aCubicB.testAndSolveTrivialBezier(); + const bool bEdgeBIsCurve(aCubicB.isBezier()); + const B2DRange aRangeB(aCubicB.getRange()); + + // consecutive segments touch of course + bool bOverlap = false; + if( b > a+1) + bOverlap = aRangeA.overlaps(aRangeB); + else + bOverlap = aRangeA.overlapsMore(aRangeB); + if( bOverlap) + { + if(bEdgeAIsCurve && bEdgeBIsCurve) + { + // test for bezier-bezier cuts + findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, rTempPointsA, rTempPointsB); + } + else if(bEdgeAIsCurve) + { + // test for bezier-edge cuts + findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, rTempPointsA, rTempPointsB); + } + else if(bEdgeBIsCurve) + { + // test for bezier-edge cuts + findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, rTempPointsB, rTempPointsA); + } + else + { + // test for simple edge-edge cuts + findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(), + a, b, rTempPointsA, rTempPointsB); + } + } + } + } + } + else + { + B2DPoint aCurrA(rCandidateA.getB2DPoint(0L)); + + for(sal_uInt32 a(0L); a < nEdgeCountA; a++) + { + const B2DPoint aNextA(rCandidateA.getB2DPoint(a + 1L == nPointCountA ? 0L : a + 1L)); + const B2DRange aRangeA(aCurrA, aNextA); + B2DPoint aCurrB(rCandidateB.getB2DPoint(0L)); + + for(sal_uInt32 b(0L); b < nEdgeCountB; b++) + { + const B2DPoint aNextB(rCandidateB.getB2DPoint(b + 1L == nPointCountB ? 0L : b + 1L)); + const B2DRange aRangeB(aCurrB, aNextB); + + // consecutive segments touch of course + bool bOverlap = false; + if( b > a+1) + bOverlap = aRangeA.overlaps(aRangeB); + else + bOverlap = aRangeA.overlapsMore(aRangeB); + if( bOverlap) + { + // test for simple edge-edge cuts + findEdgeCutsTwoEdges(aCurrA, aNextA, aCurrB, aNextB, a, b, rTempPointsA, rTempPointsB); + } + + // prepare next step + aCurrB = aNextB; + } + + // prepare next step + aCurrA = aNextA; + } + } + } + } + } + + //////////////////////////////////////////////////////////////////////////////// + + } // end of anonymous namespace +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + //////////////////////////////////////////////////////////////////////////////// + + B2DPolygon addPointsAtCutsAndTouches(const B2DPolygon& rCandidate) + { + if(rCandidate.count()) + { + temporaryPointVector aTempPoints; + + findTouches(rCandidate, rCandidate, aTempPoints); + findCuts(rCandidate, aTempPoints); + + return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints); + } + else + { + return rCandidate; + } + } + + //////////////////////////////////////////////////////////////////////////////// + + B2DPolyPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rCandidate, bool bSelfIntersections) + { + const sal_uInt32 nCount(rCandidate.count()); + + if(nCount) + { + B2DPolyPolygon aRetval; + + if(1L == nCount) + { + if(bSelfIntersections) + { + // remove self intersections + aRetval.append(addPointsAtCutsAndTouches(rCandidate.getB2DPolygon(0L))); + } + else + { + // copy source + aRetval = rCandidate; + } + } + else + { + // first solve self cuts and self touches for all contained single polygons + temporaryPolygonData *pTempData = new temporaryPolygonData[nCount]; + sal_uInt32 a, b; + + for(a = 0L; a < nCount; a++) + { + if(bSelfIntersections) + { + // use polygons with solved self intersections + pTempData[a].setPolygon(addPointsAtCutsAndTouches(rCandidate.getB2DPolygon(a))); + } + else + { + // copy given polygons + pTempData[a].setPolygon(rCandidate.getB2DPolygon(a)); + } + } + + // now cuts and touches between the polygons + for(a = 0L; a < nCount; a++) + { + for(b = 0L; b < nCount; b++) + { + if(a != b) + { + // look for touches, compare each edge polygon to all other points + if(pTempData[a].getRange().overlaps(pTempData[b].getRange())) + { + findTouches(pTempData[a].getPolygon(), pTempData[b].getPolygon(), pTempData[a].getTemporaryPointVector()); + } + } + + if(a < b) + { + // look for cuts, compare each edge polygon to following ones + if(pTempData[a].getRange().overlaps(pTempData[b].getRange())) + { + findCuts(pTempData[a].getPolygon(), pTempData[b].getPolygon(), pTempData[a].getTemporaryPointVector(), pTempData[b].getTemporaryPointVector()); + } + } + } + } + + // consolidate the result + for(a = 0L; a < nCount; a++) + { + aRetval.append(mergeTemporaryPointsAndPolygon(pTempData[a].getPolygon(), pTempData[a].getTemporaryPointVector())); + } + + delete[] pTempData; + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + //////////////////////////////////////////////////////////////////////////////// + + B2DPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rMask, const B2DPolygon& rCandidate) + { + if(rCandidate.count()) + { + temporaryPointVector aTempPoints; + temporaryPointVector aTempPointsUnused; + + for(sal_uInt32 a(0L); a < rMask.count(); a++) + { + const B2DPolygon aPartMask(rMask.getB2DPolygon(a)); + + findTouches(rCandidate, aPartMask, aTempPoints); + findCuts(rCandidate, aPartMask, aTempPoints, aTempPointsUnused); + } + + return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints); + } + else + { + return rCandidate; + } + } + + //////////////////////////////////////////////////////////////////////////////// + + B2DPolyPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rMask, const B2DPolyPolygon& rCandidate) + { + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(addPointsAtCutsAndTouches(rMask, rCandidate.getB2DPolygon(a))); + } + + return aRetval; + } + + //////////////////////////////////////////////////////////////////////////////// + + B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate, const B2DPoint& rStart, const B2DPoint& rEnd) + { + const sal_uInt32 nCount(rCandidate.count()); + + if(nCount && !rStart.equal(rEnd)) + { + const B2DRange aPolygonRange(rCandidate.getB2DRange()); + const B2DRange aEdgeRange(rStart, rEnd); + + if(aPolygonRange.overlaps(aEdgeRange)) + { + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nCount : nCount - 1); + temporaryPointVector aTempPoints; + temporaryPointVector aUnusedTempPoints; + B2DCubicBezier aCubic; + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + rCandidate.getBezierSegment(a, aCubic); + B2DRange aCubicRange(aCubic.getStartPoint(), aCubic.getEndPoint()); + + if(aCubic.isBezier()) + { + aCubicRange.expand(aCubic.getControlPointA()); + aCubicRange.expand(aCubic.getControlPointB()); + + if(aCubicRange.overlaps(aEdgeRange)) + { + findEdgeCutsBezierAndEdge(aCubic, rStart, rEnd, a, 0, aTempPoints, aUnusedTempPoints); + } + } + else + { + if(aCubicRange.overlaps(aEdgeRange)) + { + findEdgeCutsTwoEdges(aCubic.getStartPoint(), aCubic.getEndPoint(), rStart, rEnd, a, 0, aTempPoints, aUnusedTempPoints); + } + } + } + + return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints); + } + } + + return rCandidate; + } + + B2DPolyPolygon addPointsAtCuts(const B2DPolyPolygon& rCandidate, const B2DPoint& rStart, const B2DPoint& rEnd) + { + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0); a < rCandidate.count(); a++) + { + aRetval.append(addPointsAtCuts(rCandidate.getB2DPolygon(a), rStart, rEnd)); + } + + return aRetval; + } + + //////////////////////////////////////////////////////////////////////////////// + + B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate, const B2DPolyPolygon& rPolyMask) + { + const sal_uInt32 nCountA(rCandidate.count()); + const sal_uInt32 nCountM(rPolyMask.count()); + + if(nCountA && nCountM) + { + const B2DRange aRangeA(rCandidate.getB2DRange()); + const B2DRange aRangeM(rPolyMask.getB2DRange()); + + if(aRangeA.overlaps(aRangeM)) + { + const sal_uInt32 nEdgeCountA(rCandidate.isClosed() ? nCountA : nCountA - 1); + temporaryPointVector aTempPointsA; + temporaryPointVector aUnusedTempPointsB; + + for(sal_uInt32 m(0); m < nCountM; m++) + { + const B2DPolygon aMask(rPolyMask.getB2DPolygon(m)); + const sal_uInt32 nCountB(aMask.count()); + + if(nCountB) + { + B2DCubicBezier aCubicA; + B2DCubicBezier aCubicB; + + for(sal_uInt32 a(0); a < nEdgeCountA; a++) + { + rCandidate.getBezierSegment(a, aCubicA); + const bool bCubicAIsCurve(aCubicA.isBezier()); + B2DRange aCubicRangeA(aCubicA.getStartPoint(), aCubicA.getEndPoint()); + + if(bCubicAIsCurve) + { + aCubicRangeA.expand(aCubicA.getControlPointA()); + aCubicRangeA.expand(aCubicA.getControlPointB()); + } + + for(sal_uInt32 b(0); b < nCountB; b++) + { + aMask.getBezierSegment(b, aCubicB); + const bool bCubicBIsCurve(aCubicB.isBezier()); + B2DRange aCubicRangeB(aCubicB.getStartPoint(), aCubicB.getEndPoint()); + + if(bCubicBIsCurve) + { + aCubicRangeB.expand(aCubicB.getControlPointA()); + aCubicRangeB.expand(aCubicB.getControlPointB()); + } + + if(aCubicRangeA.overlaps(aCubicRangeB)) + { + if(bCubicAIsCurve && bCubicBIsCurve) + { + findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, aTempPointsA, aUnusedTempPointsB); + } + else if(bCubicAIsCurve) + { + findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, aTempPointsA, aUnusedTempPointsB); + } + else if(bCubicBIsCurve) + { + findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, aUnusedTempPointsB, aTempPointsA); + } + else + { + findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, aTempPointsA, aUnusedTempPointsB); + } + } + } + } + } + } + + return mergeTemporaryPointsAndPolygon(rCandidate, aTempPointsA); + } + } + + return rCandidate; + } + + B2DPolyPolygon addPointsAtCuts(const B2DPolyPolygon& rCandidate, const B2DPolyPolygon& rMask) + { + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0); a < rCandidate.count(); a++) + { + aRetval.append(addPointsAtCuts(rCandidate.getB2DPolygon(a), rMask)); + } + + return aRetval; + } + + 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..e54a5e2707c9 --- /dev/null +++ b/basegfx/source/polygon/b2dpolygontools.cxx @@ -0,0 +1,3612 @@ +/************************************************************************* + * + * 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 ) + { + return false; + } + + // number of 90 degree turns the polygon has taken + int nNumTurns(0); + + int nVerticalEdgeType=0; + int nHorizontalEdgeType=0; + bool bNullVertex(true); + bool bCWPolygon(false); // when true, polygon is CW + // oriented, when false, CCW + bool bOrientationSet(false); // when false, polygon + // orientation has not yet + // been determined. + + // scan all _edges_ (which involves coming back to point 0 + // for the last edge - thus the modulo operation below) + const sal_Int32 nCount( rPoly.count() ); + for( sal_Int32 i=0; i<nCount; ++i ) + { + const B2DPoint& rPoint0( rPoly.getB2DPoint(i % nCount) ); + const B2DPoint& rPoint1( rPoly.getB2DPoint((i+1) % nCount) ); + + // is 0 for zero direction vector, 1 for south edge and -1 + // for north edge (standard screen coordinate system) + int nCurrVerticalEdgeType( lcl_sgn( rPoint1.getY() - rPoint0.getY() ) ); + + // is 0 for zero direction vector, 1 for east edge and -1 + // for west edge (standard screen coordinate system) + int nCurrHorizontalEdgeType( lcl_sgn(rPoint1.getX() - rPoint0.getX()) ); + + if( nCurrVerticalEdgeType && nCurrHorizontalEdgeType ) + return false; // oblique edge - for sure no rect + + const bool bCurrNullVertex( !nCurrVerticalEdgeType && !nCurrHorizontalEdgeType ); + + // current vertex is equal to previous - just skip, + // until we have a real edge + if( bCurrNullVertex ) + continue; + + // if previous edge has two identical points, because + // no previous edge direction was available, simply + // take this first non-null edge as the start + // direction. That's what will happen here, if + // bNullVertex is false + if( !bNullVertex ) + { + // 2D cross product - is 1 for CW and -1 for CCW turns + const int nCrossProduct( nHorizontalEdgeType*nCurrVerticalEdgeType - + nVerticalEdgeType*nCurrHorizontalEdgeType ); + + if( !nCrossProduct ) + continue; // no change in orientation - + // collinear edges - just go on + + // if polygon orientation is not set, we'll + // determine it now + if( !bOrientationSet ) + { + bCWPolygon = nCrossProduct == 1; + bOrientationSet = true; + } + else + { + // if current turn orientation is not equal + // initial orientation, this is not a + // rectangle (as rectangles have consistent + // orientation). + if( (nCrossProduct == 1) != bCWPolygon ) + return false; + } + + ++nNumTurns; + + // More than four 90 degree turns are an + // indication that this must not be a rectangle. + if( nNumTurns > 4 ) + return false; + } + + // store current state for the next turn + nVerticalEdgeType = nCurrVerticalEdgeType; + nHorizontalEdgeType = nCurrHorizontalEdgeType; + bNullVertex = false; // won't reach this line, + // if bCurrNullVertex is + // true - see above + } + + return true; + } + + B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon& rCandidate, double fZCoordinate) + { + if(rCandidate.areControlPointsUsed()) + { + // call myself recursively with subdivided input + const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate)); + return createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate); + } + else + { + B3DPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + B2DPoint aPoint(rCandidate.getB2DPoint(a)); + aRetval.append(B3DPoint(aPoint.getX(), aPoint.getY(), fZCoordinate)); + } + + // copy closed state + aRetval.setClosed(rCandidate.isClosed()); + + return aRetval; + } + } + + B2DPolygon createB2DPolygonFromB3DPolygon(const B3DPolygon& rCandidate, const B3DHomMatrix& rMat) + { + B2DPolygon aRetval; + const sal_uInt32 nCount(rCandidate.count()); + const bool bIsIdentity(rMat.isIdentity()); + + for(sal_uInt32 a(0L); a < nCount; a++) + { + B3DPoint aCandidate(rCandidate.getB3DPoint(a)); + + if(!bIsIdentity) + { + aCandidate *= rMat; + } + + aRetval.append(B2DPoint(aCandidate.getX(), aCandidate.getY())); + } + + // copy closed state + aRetval.setClosed(rCandidate.isClosed()); + + return aRetval; + } + + double getDistancePointToEndlessRay(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut) + { + if(rPointA.equal(rPointB)) + { + rCut = 0.0; + const B2DVector aVector(rTestPoint - rPointA); + return aVector.getLength(); + } + else + { + // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint + const B2DVector aVector1(rPointB - rPointA); + const B2DVector aVector2(rTestPoint - rPointA); + const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY())); + const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY())); + + rCut = fDividend / fDivisor; + + const B2DPoint aCutPoint(rPointA + rCut * aVector1); + const B2DVector aVector(rTestPoint - aCutPoint); + return aVector.getLength(); + } + } + + double getSmallestDistancePointToEdge(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut) + { + if(rPointA.equal(rPointB)) + { + rCut = 0.0; + const B2DVector aVector(rTestPoint - rPointA); + return aVector.getLength(); + } + else + { + // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint + const B2DVector aVector1(rPointB - rPointA); + const B2DVector aVector2(rTestPoint - rPointA); + const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY())); + const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY())); + const double fCut(fDividend / fDivisor); + + if(fCut < 0.0) + { + // not in line range, get distance to PointA + rCut = 0.0; + return aVector2.getLength(); + } + else if(fCut > 1.0) + { + // not in line range, get distance to PointB + rCut = 1.0; + const B2DVector aVector(rTestPoint - rPointB); + return aVector.getLength(); + } + else + { + // in line range + const B2DPoint aCutPoint(rPointA + fCut * aVector1); + const B2DVector aVector(rTestPoint - aCutPoint); + rCut = fCut; + return aVector.getLength(); + } + } + } + + double getSmallestDistancePointToPolygon(const B2DPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rEdgeIndex, double& rCut) + { + double fRetval(DBL_MAX); + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount > 1L) + { + const double fZero(0.0); + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); + B2DCubicBezier aBezier; + aBezier.setStartPoint(rCandidate.getB2DPoint(0)); + + for(sal_uInt32 a(0L); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); + double fEdgeDist; + double fNewCut; + bool bEdgeIsCurve(false); + + if(rCandidate.areControlPointsUsed()) + { + aBezier.setControlPointA(rCandidate.getNextControlPoint(a)); + aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); + aBezier.testAndSolveTrivialBezier(); + bEdgeIsCurve = aBezier.isBezier(); + } + + if(bEdgeIsCurve) + { + fEdgeDist = aBezier.getSmallestDistancePointToBezierSegment(rTestPoint, fNewCut); + } + else + { + fEdgeDist = getSmallestDistancePointToEdge(aBezier.getStartPoint(), aBezier.getEndPoint(), rTestPoint, fNewCut); + } + + if(DBL_MAX == fRetval || fEdgeDist < fRetval) + { + fRetval = fEdgeDist; + rEdgeIndex = a; + rCut = fNewCut; + + if(fTools::equal(fRetval, fZero)) + { + // already found zero distance, cannot get better. Ensure numerical zero value and end loop. + fRetval = 0.0; + break; + } + } + + // prepare next step + aBezier.setStartPoint(aBezier.getEndPoint()); + } + + if(1.0 == rCut) + { + // correct rEdgeIndex when not last point + if(rCandidate.isClosed()) + { + rEdgeIndex = getIndexOfSuccessor(rEdgeIndex, rCandidate); + rCut = 0.0; + } + else + { + if(rEdgeIndex != nEdgeCount - 1L) + { + rEdgeIndex++; + rCut = 0.0; + } + } + } + } + + return fRetval; + } + + B2DPoint distort(const B2DPoint& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight) + { + if(fTools::equalZero(rOriginal.getWidth()) || fTools::equalZero(rOriginal.getHeight())) + { + return rCandidate; + } + else + { + const double fRelativeX((rCandidate.getX() - rOriginal.getMinX()) / rOriginal.getWidth()); + const double fRelativeY((rCandidate.getY() - rOriginal.getMinY()) / rOriginal.getHeight()); + const double fOneMinusRelativeX(1.0 - fRelativeX); + const double fOneMinusRelativeY(1.0 - fRelativeY); + const double fNewX((fOneMinusRelativeY) * ((fOneMinusRelativeX) * rTopLeft.getX() + fRelativeX * rTopRight.getX()) + + fRelativeY * ((fOneMinusRelativeX) * rBottomLeft.getX() + fRelativeX * rBottomRight.getX())); + const double fNewY((fOneMinusRelativeX) * ((fOneMinusRelativeY) * rTopLeft.getY() + fRelativeY * rBottomLeft.getY()) + + fRelativeX * ((fOneMinusRelativeY) * rTopRight.getY() + fRelativeY * rBottomRight.getY())); + + return B2DPoint(fNewX, fNewY); + } + } + + B2DPolygon distort(const B2DPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount && 0.0 != rOriginal.getWidth() && 0.0 != rOriginal.getHeight()) + { + B2DPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPointCount; a++) + { + aRetval.append(distort(rCandidate.getB2DPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight)); + + if(rCandidate.areControlPointsUsed()) + { + if(!rCandidate.getPrevControlPoint(a).equalZero()) + { + aRetval.setPrevControlPoint(a, distort(rCandidate.getPrevControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight)); + } + + if(!rCandidate.getNextControlPoint(a).equalZero()) + { + aRetval.setNextControlPoint(a, distort(rCandidate.getNextControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight)); + } + } + } + + aRetval.setClosed(rCandidate.isClosed()); + return aRetval; + } + else + { + return rCandidate; + } + } + + B2DPolygon rotateAroundPoint(const B2DPolygon& rCandidate, const B2DPoint& rCenter, double fAngle) + { + const sal_uInt32 nPointCount(rCandidate.count()); + B2DPolygon aRetval(rCandidate); + + if(nPointCount) + { + const B2DHomMatrix aMatrix(basegfx::tools::createRotateAroundPoint(rCenter, fAngle)); + + aRetval.transform(aMatrix); + } + + return aRetval; + } + + B2DPolygon expandToCurve(const B2DPolygon& rCandidate) + { + B2DPolygon aRetval(rCandidate); + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + expandToCurveInPoint(aRetval, a); + } + + return aRetval; + } + + bool expandToCurveInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex) + { + OSL_ENSURE(nIndex < rCandidate.count(), "expandToCurveInPoint: Access to polygon out of range (!)"); + bool bRetval(false); + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount) + { + // predecessor + if(!rCandidate.isPrevControlPointUsed(nIndex)) + { + if(!rCandidate.isClosed() && 0 == nIndex) + { + // do not create previous vector for start point of open polygon + } + else + { + const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount); + rCandidate.setPrevControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0)); + bRetval = true; + } + } + + // successor + if(!rCandidate.isNextControlPointUsed(nIndex)) + { + if(!rCandidate.isClosed() && nIndex + 1 == nPointCount) + { + // do not create next vector for end point of open polygon + } + else + { + const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); + rCandidate.setNextControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0)); + bRetval = true; + } + } + } + + return bRetval; + } + + B2DPolygon setContinuity(const B2DPolygon& rCandidate, B2VectorContinuity eContinuity) + { + B2DPolygon aRetval(rCandidate); + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + setContinuityInPoint(aRetval, a, eContinuity); + } + + return aRetval; + } + + bool setContinuityInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity) + { + OSL_ENSURE(nIndex < rCandidate.count(), "setContinuityInPoint: Access to polygon out of range (!)"); + bool bRetval(false); + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount) + { + const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(nIndex)); + + switch(eContinuity) + { + case CONTINUITY_NONE : + { + if(rCandidate.isPrevControlPointUsed(nIndex)) + { + if(!rCandidate.isClosed() && 0 == nIndex) + { + // remove existing previous vector for start point of open polygon + rCandidate.resetPrevControlPoint(nIndex); + } + else + { + const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount); + rCandidate.setPrevControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0)); + } + + bRetval = true; + } + + if(rCandidate.isNextControlPointUsed(nIndex)) + { + if(!rCandidate.isClosed() && nIndex == nPointCount + 1) + { + // remove next vector for end point of open polygon + rCandidate.resetNextControlPoint(nIndex); + } + else + { + const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); + rCandidate.setNextControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0)); + } + + bRetval = true; + } + + break; + } + case CONTINUITY_C1 : + { + if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex)) + { + // lengths both exist since both are used + B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint); + B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint); + const double fLenPrev(aVectorPrev.getLength()); + const double fLenNext(aVectorNext.getLength()); + aVectorPrev.normalize(); + aVectorNext.normalize(); + const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext)); + + if(ORIENTATION_NEUTRAL == aOrientation && aVectorPrev.scalar(aVectorNext) < 0.0) + { + // parallel and opposite direction; check length + if(fTools::equal(fLenPrev, fLenNext)) + { + // this would be even C2, but we want C1. Use the lengths of the corresponding edges. + const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount); + const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); + const double fLenPrevEdge(B2DVector(rCandidate.getB2DPoint(nPrevIndex) - aCurrentPoint).getLength() * (1.0 / 3.0)); + const double fLenNextEdge(B2DVector(rCandidate.getB2DPoint(nNextIndex) - aCurrentPoint).getLength() * (1.0 / 3.0)); + + rCandidate.setControlPoints(nIndex, + aCurrentPoint + (aVectorPrev * fLenPrevEdge), + aCurrentPoint + (aVectorNext * fLenNextEdge)); + bRetval = true; + } + } + else + { + // not parallel or same direction, set vectors and length + const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext)); + + if(ORIENTATION_POSITIVE == aOrientation) + { + rCandidate.setControlPoints(nIndex, + aCurrentPoint - (aNormalizedPerpendicular * fLenPrev), + aCurrentPoint + (aNormalizedPerpendicular * fLenNext)); + } + else + { + rCandidate.setControlPoints(nIndex, + aCurrentPoint + (aNormalizedPerpendicular * fLenPrev), + aCurrentPoint - (aNormalizedPerpendicular * fLenNext)); + } + + bRetval = true; + } + } + break; + } + case CONTINUITY_C2 : + { + if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex)) + { + // lengths both exist since both are used + B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint); + B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint); + const double fCommonLength((aVectorPrev.getLength() + aVectorNext.getLength()) / 2.0); + aVectorPrev.normalize(); + aVectorNext.normalize(); + const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext)); + + if(ORIENTATION_NEUTRAL == aOrientation && aVectorPrev.scalar(aVectorNext) < 0.0) + { + // parallel and opposite direction; set length. Use one direction for better numerical correctness + const B2DVector aScaledDirection(aVectorPrev * fCommonLength); + + rCandidate.setControlPoints(nIndex, + aCurrentPoint + aScaledDirection, + aCurrentPoint - aScaledDirection); + } + else + { + // not parallel or same direction, set vectors and length + const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext)); + const B2DVector aPerpendicular(aNormalizedPerpendicular * fCommonLength); + + if(ORIENTATION_POSITIVE == aOrientation) + { + rCandidate.setControlPoints(nIndex, + aCurrentPoint - aPerpendicular, + aCurrentPoint + aPerpendicular); + } + else + { + rCandidate.setControlPoints(nIndex, + aCurrentPoint + aPerpendicular, + aCurrentPoint - aPerpendicular); + } + } + + bRetval = true; + } + break; + } + } + } + + return bRetval; + } + + B2DPolygon growInNormalDirection(const B2DPolygon& rCandidate, double fValue) + { + if(0.0 != fValue) + { + if(rCandidate.areControlPointsUsed()) + { + // call myself recursively with subdivided input + const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate)); + return growInNormalDirection(aCandidate, fValue); + } + else + { + B2DPolygon aRetval; + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount) + { + B2DPoint aPrev(rCandidate.getB2DPoint(nPointCount - 1L)); + B2DPoint aCurrent(rCandidate.getB2DPoint(0L)); + + for(sal_uInt32 a(0L); a < nPointCount; a++) + { + const B2DPoint aNext(rCandidate.getB2DPoint(a + 1L == nPointCount ? 0L : a + 1L)); + const B2DVector aBack(aPrev - aCurrent); + const B2DVector aForw(aNext - aCurrent); + const B2DVector aPerpBack(getNormalizedPerpendicular(aBack)); + const B2DVector aPerpForw(getNormalizedPerpendicular(aForw)); + B2DVector aDirection(aPerpBack - aPerpForw); + aDirection.normalize(); + aDirection *= fValue; + aRetval.append(aCurrent + aDirection); + + // prepare next step + aPrev = aCurrent; + aCurrent = aNext; + } + } + + // copy closed state + aRetval.setClosed(rCandidate.isClosed()); + + return aRetval; + } + } + else + { + return rCandidate; + } + } + + B2DPolygon reSegmentPolygon(const B2DPolygon& rCandidate, sal_uInt32 nSegments) + { + B2DPolygon aRetval; + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount && nSegments) + { + // get current segment count + const sal_uInt32 nSegmentCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); + + if(nSegmentCount == nSegments) + { + aRetval = rCandidate; + } + else + { + const double fLength(getLength(rCandidate)); + const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nSegments : nSegments + 1L); + + for(sal_uInt32 a(0L); a < nLoopCount; a++) + { + const double fRelativePos((double)a / (double)nSegments); // 0.0 .. 1.0 + const B2DPoint aNewPoint(getPositionRelative(rCandidate, fRelativePos, fLength)); + aRetval.append(aNewPoint); + } + + // copy closed flag + aRetval.setClosed(rCandidate.isClosed()); + } + } + + return aRetval; + } + + B2DPolygon reSegmentPolygonEdges(const B2DPolygon& rCandidate, sal_uInt32 nSubEdges, bool bHandleCurvedEdges, bool bHandleStraightEdges) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount < 2 || nSubEdges < 2 || (!bHandleCurvedEdges && !bHandleStraightEdges)) + { + // nothing to do: + // - less than two points -> no edge at all + // - less than two nSubEdges -> no resegment necessary + // - neither bHandleCurvedEdges nor bHandleStraightEdges -> nothing to do + return rCandidate; + } + else + { + B2DPolygon aRetval; + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); + B2DCubicBezier aCurrentEdge; + + // prepare first edge and add start point to target + aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0)); + aRetval.append(aCurrentEdge.getStartPoint()); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + // fill edge + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a)); + aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); + aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); + + if(aCurrentEdge.isBezier()) + { + if(bHandleCurvedEdges) + { + for(sal_uInt32 b(nSubEdges); b > 1; b--) + { + const double fSplitPoint(1.0 / b); + B2DCubicBezier aLeftPart; + + aCurrentEdge.split(fSplitPoint, &aLeftPart, &aCurrentEdge); + aRetval.appendBezierSegment(aLeftPart.getControlPointA(), aLeftPart.getControlPointB(), aLeftPart.getEndPoint()); + } + } + + // copy remaining segment to target + aRetval.appendBezierSegment(aCurrentEdge.getControlPointA(), aCurrentEdge.getControlPointB(), aCurrentEdge.getEndPoint()); + } + else + { + if(bHandleStraightEdges) + { + for(sal_uInt32 b(nSubEdges); b > 1; b--) + { + const double fSplitPoint(1.0 / b); + const B2DPoint aSplitPoint(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fSplitPoint)); + + aRetval.append(aSplitPoint); + aCurrentEdge.setStartPoint(aSplitPoint); + } + } + + // copy remaining segment to target + aRetval.append(aCurrentEdge.getEndPoint()); + } + + // prepare next step + aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint()); + } + + // copy closed flag and return + aRetval.setClosed(rCandidate.isClosed()); + return aRetval; + } + } + + B2DPolygon interpolate(const B2DPolygon& rOld1, const B2DPolygon& rOld2, double t) + { + OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolygon interpolate: Different geometry (!)"); + + if(fTools::lessOrEqual(t, 0.0) || rOld1 == rOld2) + { + return rOld1; + } + else if(fTools::moreOrEqual(t, 1.0)) + { + return rOld2; + } + else + { + B2DPolygon aRetval; + const bool bInterpolateVectors(rOld1.areControlPointsUsed() || rOld2.areControlPointsUsed()); + aRetval.setClosed(rOld1.isClosed() && rOld2.isClosed()); + + for(sal_uInt32 a(0L); a < rOld1.count(); a++) + { + aRetval.append(interpolate(rOld1.getB2DPoint(a), rOld2.getB2DPoint(a), t)); + + if(bInterpolateVectors) + { + aRetval.setPrevControlPoint(a, interpolate(rOld1.getPrevControlPoint(a), rOld2.getPrevControlPoint(a), t)); + aRetval.setNextControlPoint(a, interpolate(rOld1.getNextControlPoint(a), rOld2.getNextControlPoint(a), t)); + } + } + + return aRetval; + } + } + + bool isPolyPolygonEqualRectangle( const B2DPolyPolygon& rPolyPoly, + const B2DRange& rRect ) + { + // exclude some cheap cases first + if( rPolyPoly.count() != 1 ) + return false; + + // fill array with rectangle vertices + const B2DPoint aPoints[] = + { + B2DPoint(rRect.getMinX(),rRect.getMinY()), + B2DPoint(rRect.getMaxX(),rRect.getMinY()), + B2DPoint(rRect.getMaxX(),rRect.getMaxY()), + B2DPoint(rRect.getMinX(),rRect.getMaxY()) + }; + + const B2DPolygon& rPoly( rPolyPoly.getB2DPolygon(0) ); + const sal_uInt32 nCount( rPoly.count() ); + const double epsilon = ::std::numeric_limits<double>::epsilon(); + + for(unsigned int j=0; j<4; ++j) + { + const B2DPoint &p1 = aPoints[j]; + const B2DPoint &p2 = aPoints[(j+1)%4]; + bool bPointOnBoundary = false; + for( sal_uInt32 i=0; i<nCount; ++i ) + { + const B2DPoint p(rPoly.getB2DPoint(i)); + + // 1 | x0 y0 1 | + // A = - | x1 y1 1 | + // 2 | x2 y2 1 | + double fDoubleArea = p2.getX()*p.getY() - + p2.getY()*p.getX() - + p1.getX()*p.getY() + + p1.getY()*p.getX() + + p1.getX()*p2.getY() - + p1.getY()*p2.getX(); + + if(fDoubleArea < epsilon) + { + bPointOnBoundary=true; + break; + } + } + if(!(bPointOnBoundary)) + return false; + } + + return true; + } + + + // create simplified version of the original polygon by + // replacing segments with spikes/loops and self intersections + // by several trivial sub-segments + B2DPolygon createSimplifiedPolygon( const B2DPolygon& rCandidate ) + { + const sal_uInt32 nCount(rCandidate.count()); + + if(nCount && rCandidate.areControlPointsUsed()) + { + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nCount : nCount - 1); + B2DPolygon aRetval; + B2DCubicBezier aSegment; + + aSegment.setStartPoint(rCandidate.getB2DPoint(0)); + aRetval.append(aSegment.getStartPoint()); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + // fill edge + const sal_uInt32 nNextIndex((a + 1) % nCount); + aSegment.setControlPointA(rCandidate.getNextControlPoint(a)); + aSegment.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); + aSegment.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); + + if(aSegment.isBezier()) + { + double fExtremumPos(0.0); + sal_uInt32 nExtremumCounter(4); + + while(nExtremumCounter-- && aSegment.isBezier() && aSegment.getMinimumExtremumPosition(fExtremumPos)) + { + // split off left, now extremum-free part and append + B2DCubicBezier aLeft; + + aSegment.split(fExtremumPos, &aLeft, &aSegment); + aLeft.testAndSolveTrivialBezier(); + aSegment.testAndSolveTrivialBezier(); + + if(aLeft.isBezier()) + { + aRetval.appendBezierSegment(aLeft.getControlPointA(), aLeft.getControlPointB(), aLeft.getEndPoint()); + } + else + { + aRetval.append(aLeft.getEndPoint()); + } + } + + // append (evtl. reduced) rest of Segment + if(aSegment.isBezier()) + { + aRetval.appendBezierSegment(aSegment.getControlPointA(), aSegment.getControlPointB(), aSegment.getEndPoint()); + } + else + { + aRetval.append(aSegment.getEndPoint()); + } + } + else + { + // simple edge, append end point + aRetval.append(aSegment.getEndPoint()); + } + + // prepare next edge + aSegment.setStartPoint(aSegment.getEndPoint()); + } + + // copy closed flag and check for double points + aRetval.setClosed(rCandidate.isClosed()); + aRetval.removeDoublePoints(); + + return aRetval; + } + else + { + return rCandidate; + } + } + + // #i76891# + B2DPolygon simplifyCurveSegments(const B2DPolygon& rCandidate) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount && rCandidate.areControlPointsUsed()) + { + // prepare loop + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); + B2DPolygon aRetval; + B2DCubicBezier aBezier; + aBezier.setStartPoint(rCandidate.getB2DPoint(0)); + + // try to avoid costly reallocations + aRetval.reserve( nEdgeCount+1); + + // add start point + aRetval.append(aBezier.getStartPoint()); + + for(sal_uInt32 a(0L); a < nEdgeCount; a++) + { + // get values for edge + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex)); + aBezier.setControlPointA(rCandidate.getNextControlPoint(a)); + aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex)); + aBezier.testAndSolveTrivialBezier(); + + // still bezier? + if(aBezier.isBezier()) + { + // add edge with control vectors + aRetval.appendBezierSegment(aBezier.getControlPointA(), aBezier.getControlPointB(), aBezier.getEndPoint()); + } + else + { + // add edge + aRetval.append(aBezier.getEndPoint()); + } + + // next point + aBezier.setStartPoint(aBezier.getEndPoint()); + } + + if(rCandidate.isClosed()) + { + // set closed flag, rescue control point and correct last double point + closeWithGeometryChange(aRetval); + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + // makes the given indexed point the new polygon start point. To do that, the points in the + // polygon will be rotated. This is only valid for closed polygons, for non-closed ones + // an assertion will be triggered + B2DPolygon makeStartPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndexOfNewStatPoint) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount > 2 && nIndexOfNewStatPoint != 0 && nIndexOfNewStatPoint < nPointCount) + { + OSL_ENSURE(rCandidate.isClosed(), "makeStartPoint: only valid for closed polygons (!)"); + B2DPolygon aRetval; + + for(sal_uInt32 a(0); a < nPointCount; a++) + { + const sal_uInt32 nSourceIndex((a + nIndexOfNewStatPoint) % nPointCount); + aRetval.append(rCandidate.getB2DPoint(nSourceIndex)); + + if(rCandidate.areControlPointsUsed()) + { + aRetval.setPrevControlPoint(a, rCandidate.getPrevControlPoint(nSourceIndex)); + aRetval.setNextControlPoint(a, rCandidate.getNextControlPoint(nSourceIndex)); + } + } + + return aRetval; + } + + return rCandidate; + } + + B2DPolygon createEdgesOfGivenLength(const B2DPolygon& rCandidate, double fLength, double fStart, double fEnd) + { + B2DPolygon aRetval; + + if(fLength < 0.0) + { + fLength = 0.0; + } + + if(!fTools::equalZero(fLength)) + { + if(fStart < 0.0) + { + fStart = 0.0; + } + + if(fEnd < 0.0) + { + fEnd = 0.0; + } + + if(fEnd < fStart) + { + fEnd = fStart; + } + + // iterate and consume pieces with fLength. First subdivide to reduce input to line segments + const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate); + const sal_uInt32 nPointCount(aCandidate.count()); + + if(nPointCount > 1) + { + const bool bEndActive(!fTools::equalZero(fEnd)); + const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1); + B2DPoint aCurrent(aCandidate.getB2DPoint(0)); + double fPositionInEdge(fStart); + double fAbsolutePosition(fStart); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex)); + const B2DVector aEdge(aNext - aCurrent); + double fEdgeLength(aEdge.getLength()); + + if(!fTools::equalZero(fEdgeLength)) + { + while(fTools::less(fPositionInEdge, fEdgeLength)) + { + // move position on edge forward as long as on edge + const double fScalar(fPositionInEdge / fEdgeLength); + aRetval.append(aCurrent + (aEdge * fScalar)); + fPositionInEdge += fLength; + + if(bEndActive) + { + fAbsolutePosition += fLength; + + if(fTools::more(fAbsolutePosition, fEnd)) + { + break; + } + } + } + + // substract length of current edge + fPositionInEdge -= fEdgeLength; + } + + if(bEndActive && fTools::more(fAbsolutePosition, fEnd)) + { + break; + } + + // prepare next step + aCurrent = aNext; + } + + // keep closed state + aRetval.setClosed(aCandidate.isClosed()); + } + else + { + // source polygon has only one point, return unchanged + aRetval = aCandidate; + } + } + + return aRetval; + } + + B2DPolygon createWaveline(const B2DPolygon& rCandidate, double fWaveWidth, double fWaveHeight) + { + B2DPolygon aRetval; + + if(fWaveWidth < 0.0) + { + fWaveWidth = 0.0; + } + + if(fWaveHeight < 0.0) + { + fWaveHeight = 0.0; + } + + const bool bHasWidth(!fTools::equalZero(fWaveWidth)); + const bool bHasHeight(!fTools::equalZero(fWaveHeight)); + + if(bHasWidth) + { + if(bHasHeight) + { + // width and height, create waveline. First subdivide to reduce input to line segments + // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it + // may be added here again using the original last point from rCandidate. It may + // also be the case that rCandidate was closed. To simplify things it is handled here + // as if it was opened. + // Result from createEdgesOfGivenLength contains no curved segments, handle as straight + // edges. + const B2DPolygon aEqualLenghEdges(createEdgesOfGivenLength(rCandidate, fWaveWidth)); + const sal_uInt32 nPointCount(aEqualLenghEdges.count()); + + if(nPointCount > 1) + { + // iterate over straight edges, add start point + B2DPoint aCurrent(aEqualLenghEdges.getB2DPoint(0)); + aRetval.append(aCurrent); + + for(sal_uInt32 a(0); a < nPointCount - 1; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B2DPoint aNext(aEqualLenghEdges.getB2DPoint(nNextIndex)); + const B2DVector aEdge(aNext - aCurrent); + const B2DVector aPerpendicular(getNormalizedPerpendicular(aEdge)); + const B2DVector aControlOffset((aEdge * 0.467308) - (aPerpendicular * fWaveHeight)); + + // add curve segment + aRetval.appendBezierSegment( + aCurrent + aControlOffset, + aNext - aControlOffset, + aNext); + + // prepare next step + aCurrent = aNext; + } + } + } + else + { + // width but no height -> return original polygon + aRetval = rCandidate; + } + } + else + { + // no width -> no waveline, stay empty and return + } + + return aRetval; + } + + ////////////////////////////////////////////////////////////////////// + // comparators with tolerance for 2D Polygons + + bool equal(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, const double& rfSmallValue) + { + const sal_uInt32 nPointCount(rCandidateA.count()); + + if(nPointCount != rCandidateB.count()) + return false; + + const bool bClosed(rCandidateA.isClosed()); + + if(bClosed != rCandidateB.isClosed()) + return false; + + const bool bAreControlPointsUsed(rCandidateA.areControlPointsUsed()); + + if(bAreControlPointsUsed != rCandidateB.areControlPointsUsed()) + return false; + + for(sal_uInt32 a(0); a < nPointCount; a++) + { + const B2DPoint aPoint(rCandidateA.getB2DPoint(a)); + + if(!aPoint.equal(rCandidateB.getB2DPoint(a), rfSmallValue)) + return false; + + if(bAreControlPointsUsed) + { + const basegfx::B2DPoint aPrev(rCandidateA.getPrevControlPoint(a)); + + if(!aPrev.equal(rCandidateB.getPrevControlPoint(a), rfSmallValue)) + return false; + + const basegfx::B2DPoint aNext(rCandidateA.getNextControlPoint(a)); + + if(!aNext.equal(rCandidateB.getNextControlPoint(a), rfSmallValue)) + return false; + } + } + + return true; + } + + bool equal(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB) + { + const double fSmallValue(fTools::getSmallValue()); + + return equal(rCandidateA, rCandidateB, fSmallValue); + } + + // snap points of horizontal or vertical edges to discrete values + B2DPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon& rCandidate) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount > 1) + { + // Start by copying the source polygon to get a writeable copy. The closed state is + // copied by aRetval's initialisation, too, so no need to copy it in this method + B2DPolygon aRetval(rCandidate); + + // prepare geometry data. Get rounded from original + B2ITuple aPrevTuple(basegfx::fround(rCandidate.getB2DPoint(nPointCount - 1))); + B2DPoint aCurrPoint(rCandidate.getB2DPoint(0)); + B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + + // loop over all points. This will also snap the implicit closing edge + // even when not closed, but that's no problem here + for(sal_uInt32 a(0); a < nPointCount; a++) + { + // get next point. Get rounded from original + const bool bLastRun(a + 1 == nPointCount); + const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1); + const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex)); + const B2ITuple aNextTuple(basegfx::fround(aNextPoint)); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if(bSnapX || bSnapY) + { + const B2DPoint aSnappedPoint( + bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + aRetval.setB2DPoint(a, aSnappedPoint); + } + + // prepare next point + if(!bLastRun) + { + aPrevTuple = aCurrTuple; + aCurrPoint = aNextPoint; + aCurrTuple = aNextTuple; + } + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/polygon/b2dpolygontriangulator.cxx b/basegfx/source/polygon/b2dpolygontriangulator.cxx new file mode 100644 index 000000000000..83fcc036c996 --- /dev/null +++ b/basegfx/source/polygon/b2dpolygontriangulator.cxx @@ -0,0 +1,466 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <basegfx/polygon/b2dpolygontriangulator.hxx> +#include <osl/diagnose.h> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <algorithm> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace + { + class EdgeEntry + { + EdgeEntry* mpNext; + B2DPoint maStart; + B2DPoint maEnd; + double mfAtan2; + + public: + EdgeEntry(const B2DPoint& rStart, const B2DPoint& rEnd) + : mpNext(0L), + maStart(rStart), + maEnd(rEnd), + mfAtan2(0.0) + { + // make sure edge goes down. If horizontal, let it go to the right (left-handed). + bool bSwap(false); + + if(::basegfx::fTools::equal(maStart.getY(), maEnd.getY())) + { + if(maStart.getX() > maEnd.getX()) + { + bSwap = true; + } + } + else if(maStart.getY() > maEnd.getY()) + { + bSwap = true; + } + + if(bSwap) + { + maStart = rEnd; + maEnd = rStart; + } + + mfAtan2 = atan2(maEnd.getY() - maStart.getY(), maEnd.getX() - maStart.getX()); + } + + ~EdgeEntry() + { + } + + bool operator<(const EdgeEntry& rComp) const + { + if(::basegfx::fTools::equal(maStart.getY(), rComp.maStart.getY())) + { + if(::basegfx::fTools::equal(maStart.getX(), rComp.maStart.getX())) + { + // same in x and y -> same start point. Sort emitting vectors from left to right. + return (mfAtan2 > rComp.mfAtan2); + } + + return (maStart.getX() < rComp.maStart.getX()); + } + + return (maStart.getY() < rComp.maStart.getY()); + } + + bool operator==(const EdgeEntry& rComp) const + { + return (maStart.equal(rComp.maStart) && maEnd.equal(rComp.maEnd)); + } + + bool operator!=(const EdgeEntry& rComp) const + { + return !(*this == rComp); + } + + const B2DPoint& getStart() const { return maStart; } + const B2DPoint& getEnd() const { return maEnd; } + + EdgeEntry* getNext() const { return mpNext; } + void setNext(EdgeEntry* pNext) { mpNext = pNext; } + }; + + ////////////////////////////////////////////////////////////////////////////// + + typedef ::std::vector< EdgeEntry > EdgeEntries; + typedef ::std::vector< EdgeEntry* > EdgeEntryPointers; + + ////////////////////////////////////////////////////////////////////////////// + + class Triangulator + { + EdgeEntry* mpList; + EdgeEntries maStartEntries; + EdgeEntryPointers maNewEdgeEntries; + B2DPolygon maResult; + + void handleClosingEdge(const B2DPoint& rStart, const B2DPoint& rEnd); + bool CheckPointInTriangle(EdgeEntry* pEdgeA, EdgeEntry* pEdgeB, const B2DPoint& rTestPoint); + void createTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC); + + public: + Triangulator(const B2DPolyPolygon& rCandidate); + ~Triangulator(); + + const B2DPolygon getResult() const { return maResult; } + }; + + void Triangulator::handleClosingEdge(const B2DPoint& rStart, const B2DPoint& rEnd) + { + // create an entry, else the comparison might use the wrong edges + EdgeEntry aNew(rStart, rEnd); + EdgeEntry* pCurr = mpList; + EdgeEntry* pPrev = 0L; + + while(pCurr + && pCurr->getStart().getY() <= aNew.getStart().getY() + && *pCurr != aNew) + { + pPrev = pCurr; + pCurr = pCurr->getNext(); + } + + if(pCurr && *pCurr == aNew) + { + // found closing edge, remove + if(pPrev) + { + pPrev->setNext(pCurr->getNext()); + } + else + { + mpList = pCurr->getNext(); + } + } + else + { + // insert closing edge + EdgeEntry* pNew = new EdgeEntry(aNew); + maNewEdgeEntries.push_back(pNew); + pCurr = mpList; + pPrev = 0L; + + while(pCurr && *pCurr < *pNew) + { + pPrev = pCurr; + pCurr = pCurr->getNext(); + } + + if(pPrev) + { + pNew->setNext(pPrev->getNext()); + pPrev->setNext(pNew); + } + else + { + pNew->setNext(mpList); + mpList = pNew; + } + } + } + + bool Triangulator::CheckPointInTriangle(EdgeEntry* pEdgeA, EdgeEntry* pEdgeB, const B2DPoint& rTestPoint) + { + // inside triangle or on edge? + if(tools::isPointInTriangle(pEdgeA->getStart(), pEdgeA->getEnd(), pEdgeB->getEnd(), rTestPoint, true)) + { + // but not on point + if(!rTestPoint.equal(pEdgeA->getEnd()) && !rTestPoint.equal(pEdgeB->getEnd())) + { + // found point in triangle -> split triangle inserting two edges + EdgeEntry* pStart = new EdgeEntry(pEdgeA->getStart(), rTestPoint); + EdgeEntry* pEnd = new EdgeEntry(*pStart); + maNewEdgeEntries.push_back(pStart); + maNewEdgeEntries.push_back(pEnd); + + pStart->setNext(pEnd); + pEnd->setNext(pEdgeA->getNext()); + pEdgeA->setNext(pStart); + + return false; + } + } + + return true; + } + + void Triangulator::createTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC) + { + maResult.append(rA); + maResult.append(rB); + maResult.append(rC); + } + + // consume as long as there are edges + Triangulator::Triangulator(const B2DPolyPolygon& rCandidate) + : mpList(0L) + { + // add all available edges to the single linked local list which will be sorted + // by Y,X,atan2 when adding nodes + if(rCandidate.count()) + { + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + const B2DPolygon aPolygonCandidate(rCandidate.getB2DPolygon(a)); + const sal_uInt32 nCount(aPolygonCandidate.count()); + + if(nCount > 2L) + { + B2DPoint aPrevPnt(aPolygonCandidate.getB2DPoint(nCount - 1L)); + + for(sal_uInt32 b(0L); b < nCount; b++) + { + B2DPoint aNextPnt(aPolygonCandidate.getB2DPoint(b)); + + if( !aPrevPnt.equal(aNextPnt) ) + { + maStartEntries.push_back(EdgeEntry(aPrevPnt, aNextPnt)); + } + + aPrevPnt = aNextPnt; + } + } + } + + if(maStartEntries.size()) + { + // sort initial list + ::std::sort(maStartEntries.begin(), maStartEntries.end()); + + // insert to own simply linked list + EdgeEntries::iterator aPos(maStartEntries.begin()); + mpList = &(*aPos++); + EdgeEntry* pLast = mpList; + + while(aPos != maStartEntries.end()) + { + EdgeEntry* pEntry = &(*aPos++); + pLast->setNext(pEntry); + pLast = pEntry; + } + } + } + + while(mpList) + { + if(mpList->getNext() && mpList->getNext()->getStart().equal(mpList->getStart())) + { + // next candidate. There are two edges and start point is equal. + // Length is not zero. + EdgeEntry* pEdgeA = mpList; + EdgeEntry* pEdgeB = pEdgeA->getNext(); + + if( pEdgeA->getEnd().equal(pEdgeB->getEnd()) ) + { + // start and end equal -> neutral triangle, delete both + mpList = pEdgeB->getNext(); + } + else + { + const B2DVector aLeft(pEdgeA->getEnd() - pEdgeA->getStart()); + const B2DVector aRight(pEdgeB->getEnd() - pEdgeA->getStart()); + + if(ORIENTATION_NEUTRAL == getOrientation(aLeft, aRight)) + { + // edges are parallel and have different length -> neutral triangle, + // delete both edges and handle closing edge + mpList = pEdgeB->getNext(); + handleClosingEdge(pEdgeA->getEnd(), pEdgeB->getEnd()); + } + else + { + // not parallel, look for points inside + B2DRange aRange(pEdgeA->getStart(), pEdgeA->getEnd()); + aRange.expand(pEdgeB->getEnd()); + EdgeEntry* pTestEdge = pEdgeB->getNext(); + bool bNoPointInTriangle(true); + + // look for start point in triangle + while(bNoPointInTriangle && pTestEdge) + { + if(aRange.getMaxY() < pTestEdge->getStart().getY()) + { + // edge is below test range and edges are sorted -> stop looking + break; + } + else + { + // do not look for edges with same start point, they are sorted and cannot end inside. + if(!pTestEdge->getStart().equal(pEdgeA->getStart())) + { + if(aRange.isInside(pTestEdge->getStart())) + { + bNoPointInTriangle = CheckPointInTriangle(pEdgeA, pEdgeB, pTestEdge->getStart()); + } + } + } + + // next candidate + pTestEdge = pTestEdge->getNext(); + } + + if(bNoPointInTriangle) + { + // look for end point in triange + pTestEdge = pEdgeB->getNext(); + + while(bNoPointInTriangle && pTestEdge) + { + if(aRange.getMaxY() < pTestEdge->getStart().getY()) + { + // edge is below test range and edges are sorted -> stop looking + break; + } + else + { + // do not look for edges with same end point, they are sorted and cannot end inside. + if(!pTestEdge->getEnd().equal(pEdgeA->getStart())) + { + if(aRange.isInside(pTestEdge->getEnd())) + { + bNoPointInTriangle = CheckPointInTriangle(pEdgeA, pEdgeB, pTestEdge->getEnd()); + } + } + } + + // next candidate + pTestEdge = pTestEdge->getNext(); + } + } + + if(bNoPointInTriangle) + { + // create triangle, remove edges, handle closing edge + mpList = pEdgeB->getNext(); + createTriangle(pEdgeA->getStart(), pEdgeB->getEnd(), pEdgeA->getEnd()); + handleClosingEdge(pEdgeA->getEnd(), pEdgeB->getEnd()); + } + } + } + } + else + { + // only one entry at start point, delete it + mpList = mpList->getNext(); + } + } + } + + Triangulator::~Triangulator() + { + EdgeEntryPointers::iterator aIter(maNewEdgeEntries.begin()); + + while(aIter != maNewEdgeEntries.end()) + { + delete (*aIter++); + } + } + + } // end of anonymous namespace +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace triangulator + { + B2DPolygon triangulate(const B2DPolygon& rCandidate) + { + B2DPolygon aRetval; + + // subdivide locally (triangulate does not work with beziers), remove double and neutral points + B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? tools::adaptiveSubdivideByAngle(rCandidate) : rCandidate); + aCandidate.removeDoublePoints(); + aCandidate = tools::removeNeutralPoints(aCandidate); + + if(2L == aCandidate.count()) + { + // candidate IS a triangle, just append + aRetval.append(aCandidate); + } + else if(aCandidate.count() > 2L) + { + if(tools::isConvex(aCandidate)) + { + // polygon is convex, just use a triangle fan + tools::addTriangleFan(aCandidate, aRetval); + } + else + { + // polygon is concave. + const B2DPolyPolygon aCandPolyPoly(aCandidate); + Triangulator aTriangulator(aCandPolyPoly); + aRetval = aTriangulator.getResult(); + } + } + + return aRetval; + } + + B2DPolygon triangulate(const B2DPolyPolygon& rCandidate) + { + B2DPolygon aRetval; + + // subdivide locally (triangulate does not work with beziers) + B2DPolyPolygon aCandidate(rCandidate.areControlPointsUsed() ? tools::adaptiveSubdivideByAngle(rCandidate) : rCandidate); + + if(1L == aCandidate.count()) + { + // single polygon -> single polygon triangulation + const B2DPolygon aSinglePolygon(aCandidate.getB2DPolygon(0L)); + aRetval = triangulate(aSinglePolygon); + } + else + { + Triangulator aTriangulator(aCandidate); + aRetval = aTriangulator.getResult(); + } + + return aRetval; + } + } // end of namespace triangulator +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/polygon/b2dpolypolygon.cxx b/basegfx/source/polygon/b2dpolypolygon.cxx new file mode 100644 index 000000000000..767d2b25ced5 --- /dev/null +++ b/basegfx/source/polygon/b2dpolypolygon.cxx @@ -0,0 +1,380 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <osl/diagnose.h> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <rtl/instance.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> + +#include <functional> +#include <vector> +#include <algorithm> + +////////////////////////////////////////////////////////////////////////////// + +class ImplB2DPolyPolygon +{ + typedef ::std::vector< basegfx::B2DPolygon > PolygonVector; + + PolygonVector maPolygons; + +public: + ImplB2DPolyPolygon() : maPolygons() + { + } + + ImplB2DPolyPolygon(const basegfx::B2DPolygon& rToBeCopied) : + maPolygons(1,rToBeCopied) + { + } + + bool operator==(const ImplB2DPolyPolygon& rPolygonList) const + { + // same polygon count? + if(maPolygons.size() != rPolygonList.maPolygons.size()) + return false; + + // compare polygon content + if(!(maPolygons == rPolygonList.maPolygons)) + return false; + + return true; + } + + const basegfx::B2DPolygon& getB2DPolygon(sal_uInt32 nIndex) const + { + return maPolygons[nIndex]; + } + + void setB2DPolygon(sal_uInt32 nIndex, const basegfx::B2DPolygon& rPolygon) + { + maPolygons[nIndex] = rPolygon; + } + + void insert(sal_uInt32 nIndex, const basegfx::B2DPolygon& rPolygon, sal_uInt32 nCount) + { + if(nCount) + { + // add nCount copies of rPolygon + PolygonVector::iterator aIndex(maPolygons.begin()); + aIndex += nIndex; + maPolygons.insert(aIndex, nCount, rPolygon); + } + } + + void insert(sal_uInt32 nIndex, const basegfx::B2DPolyPolygon& rPolyPolygon) + { + const sal_uInt32 nCount = rPolyPolygon.count(); + + if(nCount) + { + // add nCount polygons from rPolyPolygon + maPolygons.reserve(maPolygons.size() + nCount); + PolygonVector::iterator aIndex(maPolygons.begin()); + aIndex += nIndex; + + for(sal_uInt32 a(0L); a < nCount; a++) + { + aIndex = maPolygons.insert(aIndex, rPolyPolygon.getB2DPolygon(a)); + aIndex++; + } + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + // remove polygon data + PolygonVector::iterator aStart(maPolygons.begin()); + aStart += nIndex; + const PolygonVector::iterator aEnd(aStart + nCount); + + maPolygons.erase(aStart, aEnd); + } + } + + sal_uInt32 count() const + { + return maPolygons.size(); + } + + void setClosed(bool bNew) + { + for(sal_uInt32 a(0L); a < maPolygons.size(); a++) + { + maPolygons[a].setClosed(bNew); + } + } + + void flip() + { + std::for_each( maPolygons.begin(), + maPolygons.end(), + std::mem_fun_ref( &basegfx::B2DPolygon::flip )); + } + + void removeDoublePoints() + { + std::for_each( maPolygons.begin(), + maPolygons.end(), + std::mem_fun_ref( &basegfx::B2DPolygon::removeDoublePoints )); + } + + void transform(const basegfx::B2DHomMatrix& rMatrix) + { + for(sal_uInt32 a(0L); a < maPolygons.size(); a++) + { + maPolygons[a].transform(rMatrix); + } + } + + void makeUnique() + { + std::for_each( maPolygons.begin(), + maPolygons.end(), + std::mem_fun_ref( &basegfx::B2DPolygon::makeUnique )); + } +}; + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace { struct DefaultPolyPolygon: public rtl::Static<B2DPolyPolygon::ImplType, + DefaultPolyPolygon> {}; } + + B2DPolyPolygon::B2DPolyPolygon() : + mpPolyPolygon(DefaultPolyPolygon::get()) + { + } + + B2DPolyPolygon::B2DPolyPolygon(const B2DPolyPolygon& rPolyPolygon) : + mpPolyPolygon(rPolyPolygon.mpPolyPolygon) + { + } + + B2DPolyPolygon::B2DPolyPolygon(const B2DPolygon& rPolygon) : + mpPolyPolygon( ImplB2DPolyPolygon(rPolygon) ) + { + } + + B2DPolyPolygon::~B2DPolyPolygon() + { + } + + B2DPolyPolygon& B2DPolyPolygon::operator=(const B2DPolyPolygon& rPolyPolygon) + { + mpPolyPolygon = rPolyPolygon.mpPolyPolygon; + return *this; + } + + void B2DPolyPolygon::makeUnique() + { + mpPolyPolygon.make_unique(); + mpPolyPolygon->makeUnique(); + } + + bool B2DPolyPolygon::operator==(const B2DPolyPolygon& rPolyPolygon) const + { + if(mpPolyPolygon.same_object(rPolyPolygon.mpPolyPolygon)) + return true; + + return ((*mpPolyPolygon) == (*rPolyPolygon.mpPolyPolygon)); + } + + bool B2DPolyPolygon::operator!=(const B2DPolyPolygon& rPolyPolygon) const + { + return !((*this) == rPolyPolygon); + } + + sal_uInt32 B2DPolyPolygon::count() const + { + return mpPolyPolygon->count(); + } + + B2DPolygon B2DPolyPolygon::getB2DPolygon(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolyPolygon->count(), "B2DPolyPolygon access outside range (!)"); + + return mpPolyPolygon->getB2DPolygon(nIndex); + } + + void B2DPolyPolygon::setB2DPolygon(sal_uInt32 nIndex, const B2DPolygon& rPolygon) + { + OSL_ENSURE(nIndex < mpPolyPolygon->count(), "B2DPolyPolygon access outside range (!)"); + + if(getB2DPolygon(nIndex) != rPolygon) + mpPolyPolygon->setB2DPolygon(nIndex, rPolygon); + } + + bool B2DPolyPolygon::areControlPointsUsed() const + { + for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++) + { + const B2DPolygon& rPolygon = mpPolyPolygon->getB2DPolygon(a); + + if(rPolygon.areControlPointsUsed()) + { + return true; + } + } + + return false; + } + + void B2DPolyPolygon::insert(sal_uInt32 nIndex, const B2DPolygon& rPolygon, sal_uInt32 nCount) + { + OSL_ENSURE(nIndex <= mpPolyPolygon->count(), "B2DPolyPolygon Insert outside range (!)"); + + if(nCount) + mpPolyPolygon->insert(nIndex, rPolygon, nCount); + } + + void B2DPolyPolygon::append(const B2DPolygon& rPolygon, sal_uInt32 nCount) + { + if(nCount) + mpPolyPolygon->insert(mpPolyPolygon->count(), rPolygon, nCount); + } + + B2DPolyPolygon B2DPolyPolygon::getDefaultAdaptiveSubdivision() const + { + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++) + { + aRetval.append(mpPolyPolygon->getB2DPolygon(a).getDefaultAdaptiveSubdivision()); + } + + return aRetval; + } + + B2DRange B2DPolyPolygon::getB2DRange() const + { + B2DRange aRetval; + + for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++) + { + aRetval.expand(mpPolyPolygon->getB2DPolygon(a).getB2DRange()); + } + + return aRetval; + } + + void B2DPolyPolygon::insert(sal_uInt32 nIndex, const B2DPolyPolygon& rPolyPolygon) + { + OSL_ENSURE(nIndex <= mpPolyPolygon->count(), "B2DPolyPolygon Insert outside range (!)"); + + if(rPolyPolygon.count()) + mpPolyPolygon->insert(nIndex, rPolyPolygon); + } + + void B2DPolyPolygon::append(const B2DPolyPolygon& rPolyPolygon) + { + if(rPolyPolygon.count()) + mpPolyPolygon->insert(mpPolyPolygon->count(), rPolyPolygon); + } + + void B2DPolyPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + OSL_ENSURE(nIndex + nCount <= mpPolyPolygon->count(), "B2DPolyPolygon Remove outside range (!)"); + + if(nCount) + mpPolyPolygon->remove(nIndex, nCount); + } + + void B2DPolyPolygon::clear() + { + mpPolyPolygon = DefaultPolyPolygon::get(); + } + + bool B2DPolyPolygon::isClosed() const + { + bool bRetval(true); + + // PolyPOlygon is closed when all contained Polygons are closed or + // no Polygon exists. + for(sal_uInt32 a(0L); bRetval && a < mpPolyPolygon->count(); a++) + { + if(!(mpPolyPolygon->getB2DPolygon(a)).isClosed()) + { + bRetval = false; + } + } + + return bRetval; + } + + void B2DPolyPolygon::setClosed(bool bNew) + { + if(bNew != isClosed()) + mpPolyPolygon->setClosed(bNew); + } + + void B2DPolyPolygon::flip() + { + if(mpPolyPolygon->count()) + { + mpPolyPolygon->flip(); + } + } + + bool B2DPolyPolygon::hasDoublePoints() const + { + bool bRetval(false); + + for(sal_uInt32 a(0L); !bRetval && a < mpPolyPolygon->count(); a++) + { + if((mpPolyPolygon->getB2DPolygon(a)).hasDoublePoints()) + { + bRetval = true; + } + } + + return bRetval; + } + + void B2DPolyPolygon::removeDoublePoints() + { + if(hasDoublePoints()) + mpPolyPolygon->removeDoublePoints(); + } + + void B2DPolyPolygon::transform(const B2DHomMatrix& rMatrix) + { + if(mpPolyPolygon->count() && !rMatrix.isIdentity()) + { + mpPolyPolygon->transform(rMatrix); + } + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/polygon/b2dpolypolygoncutter.cxx b/basegfx/source/polygon/b2dpolypolygoncutter.cxx new file mode 100644 index 000000000000..4f9cf3a75f72 --- /dev/null +++ b/basegfx/source/polygon/b2dpolypolygoncutter.cxx @@ -0,0 +1,1014 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <osl/diagnose.h> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygoncutandtouch.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <vector> +#include <algorithm> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace + { + ////////////////////////////////////////////////////////////////////////////// + + struct StripHelper + { + B2DRange maRange; + sal_Int32 mnDepth; + B2VectorOrientation meOrinetation; + }; + + ////////////////////////////////////////////////////////////////////////////// + + struct PN + { + public: + B2DPoint maPoint; + sal_uInt32 mnI; + sal_uInt32 mnIP; + sal_uInt32 mnIN; + }; + + ////////////////////////////////////////////////////////////////////////////// + + struct VN + { + public: + B2DVector maPrev; + B2DVector maNext; + + // to have the correct curve segments in the crossover checks, + // it is necessary to keep the original next vectors, too. Else, + // it may happen to use a already switched next vector which + // would interpolate the wrong comparison point + B2DVector maOriginalNext; + }; + + ////////////////////////////////////////////////////////////////////////////// + + struct SN + { + public: + PN* mpPN; + + bool operator<(const SN& rComp) const + { + if(fTools::equal(mpPN->maPoint.getX(), rComp.mpPN->maPoint.getX())) + { + if(fTools::equal(mpPN->maPoint.getY(), rComp.mpPN->maPoint.getY())) + { + return (mpPN->mnI < rComp.mpPN->mnI); + } + else + { + return fTools::less(mpPN->maPoint.getY(), rComp.mpPN->maPoint.getY()); + } + } + else + { + return fTools::less(mpPN->maPoint.getX(), rComp.mpPN->maPoint.getX()); + } + } + }; + + ////////////////////////////////////////////////////////////////////////////// + + typedef ::std::vector< PN > PNV; + typedef ::std::vector< VN > VNV; + typedef ::std::vector< SN > SNV; + + ////////////////////////////////////////////////////////////////////////////// + + class solver + { + private: + const B2DPolyPolygon maOriginal; + PNV maPNV; + VNV maVNV; + SNV maSNV; + + unsigned mbIsCurve : 1; + unsigned mbChanged : 1; + + void impAddPolygon(const sal_uInt32 aPos, const B2DPolygon& rGeometry) + { + const sal_uInt32 nCount(rGeometry.count()); + PN aNewPN; + VN aNewVN; + SN aNewSN; + + for(sal_uInt32 a(0); a < nCount; a++) + { + const B2DPoint aPoint(rGeometry.getB2DPoint(a)); + aNewPN.maPoint = aPoint; + aNewPN.mnI = aPos + a; + aNewPN.mnIP = aPos + ((a != 0) ? a - 1 : nCount - 1); + aNewPN.mnIN = aPos + ((a + 1 == nCount) ? 0 : a + 1); + maPNV.push_back(aNewPN); + + if(mbIsCurve) + { + aNewVN.maPrev = rGeometry.getPrevControlPoint(a) - aPoint; + aNewVN.maNext = rGeometry.getNextControlPoint(a) - aPoint; + aNewVN.maOriginalNext = aNewVN.maNext; + maVNV.push_back(aNewVN); + } + + aNewSN.mpPN = &maPNV[maPNV.size() - 1]; + maSNV.push_back(aNewSN); + } + } + + bool impLeftOfEdges(const B2DVector& rVecA, const B2DVector& rVecB, const B2DVector& rTest) + { + // tests if rTest is left of both directed line segments along the line -rVecA, rVecB. Test is + // with border. + if(rVecA.cross(rVecB) > 0.0) + { + // b is left turn seen from a, test if Test is left of both and so inside (left is seeen as inside) + const bool bBoolA(fTools::moreOrEqual(rVecA.cross(rTest), 0.0)); + const bool bBoolB(fTools::lessOrEqual(rVecB.cross(rTest), 0.0)); + + return (bBoolA && bBoolB); + } + else + { + // b is right turn seen from a, test if Test is right of both and so outside (left is seeen as inside) + const bool bBoolA(fTools::lessOrEqual(rVecA.cross(rTest), 0.0)); + const bool bBoolB(fTools::moreOrEqual(rVecB.cross(rTest), 0.0)); + + return (!(bBoolA && bBoolB)); + } + } + + void impSwitchNext(PN& rPNa, PN& rPNb) + { + ::std::swap(rPNa.mnIN, rPNb.mnIN); + + if(mbIsCurve) + { + VN& rVNa = maVNV[rPNa.mnI]; + VN& rVNb = maVNV[rPNb.mnI]; + + ::std::swap(rVNa.maNext, rVNb.maNext); + } + + if(!mbChanged) + { + mbChanged = true; + } + } + + B2DCubicBezier createSegment(const PN& rPN, bool bPrev) const + { + const B2DPoint& rStart(rPN.maPoint); + const B2DPoint& rEnd(maPNV[bPrev ? rPN.mnIP : rPN.mnIN].maPoint); + const B2DVector& rCPA(bPrev ? maVNV[rPN.mnI].maPrev : maVNV[rPN.mnI].maNext); + // Use maOriginalNext, not maNext to create the original (yet unchanged) + // curve segment. Otherwise, this segment would NOT ne correct. + const B2DVector& rCPB(bPrev ? maVNV[maPNV[rPN.mnIP].mnI].maOriginalNext : maVNV[maPNV[rPN.mnIN].mnI].maPrev); + + return B2DCubicBezier(rStart, rStart + rCPA, rEnd + rCPB, rEnd); + } + + void impHandleCommon(PN& rPNa, PN& rPNb) + { + if(mbIsCurve) + { + const B2DCubicBezier aNextA(createSegment(rPNa, false)); + const B2DCubicBezier aPrevA(createSegment(rPNa, true)); + + if(aNextA.equal(aPrevA)) + { + // deadend on A (identical edge) + return; + } + + const B2DCubicBezier aNextB(createSegment(rPNb, false)); + const B2DCubicBezier aPrevB(createSegment(rPNb, true)); + + if(aNextB.equal(aPrevB)) + { + // deadend on B (identical edge) + return; + } + + if(aPrevA.equal(aPrevB)) + { + // common edge in same direction + if(aNextA.equal(aNextB)) + { + // common edge in same direction continues + return; + } + else + { + // common edge in same direction leave + // action is done on enter + return; + } + } + else if(aPrevA.equal(aNextB)) + { + // common edge in opposite direction + if(aNextA.equal(aPrevB)) + { + // common edge in opposite direction continues + return; + } + else + { + // common edge in opposite direction leave + impSwitchNext(rPNa, rPNb); + } + } + else if(aNextA.equal(aNextB)) + { + // common edge in same direction enter + // search leave edge + PN* pPNa2 = &maPNV[rPNa.mnIN]; + PN* pPNb2 = &maPNV[rPNb.mnIN]; + bool bOnEdge(true); + + do + { + const B2DCubicBezier aNextA2(createSegment(*pPNa2, false)); + const B2DCubicBezier aNextB2(createSegment(*pPNb2, false)); + + if(aNextA2.equal(aNextB2)) + { + pPNa2 = &maPNV[pPNa2->mnIN]; + pPNb2 = &maPNV[pPNb2->mnIN]; + } + else + { + bOnEdge = false; + } + } + while(bOnEdge && pPNa2 != &rPNa && pPNa2 != &rPNa); + + if(bOnEdge) + { + // loop over two identical polygon paths + return; + } + else + { + // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges + // at enter/leave. Check for crossover. + const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint()); + const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint()); + const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint()); + const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB)); + + const B2DCubicBezier aNextA2(createSegment(*pPNa2, false)); + const B2DCubicBezier aPrevA2(createSegment(*pPNa2, true)); + const B2DCubicBezier aNextB2(createSegment(*pPNb2, false)); + const B2DVector aPrevCA2(aPrevA2.interpolatePoint(0.5) - aPrevA2.getStartPoint()); + const B2DVector aNextCA2(aNextA2.interpolatePoint(0.5) - aNextA2.getStartPoint()); + const B2DVector aNextCB2(aNextB2.interpolatePoint(0.5) - aNextB2.getStartPoint()); + const bool bLeave(impLeftOfEdges(aPrevCA2, aNextCA2, aNextCB2)); + + if(bEnter != bLeave) + { + // crossover + impSwitchNext(rPNa, rPNb); + } + } + } + else if(aNextA.equal(aPrevB)) + { + // common edge in opposite direction enter + impSwitchNext(rPNa, rPNb); + } + else + { + // no common edges, check for crossover + const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint()); + const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint()); + const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint()); + const B2DVector aNextCB(aNextB.interpolatePoint(0.5) - aNextB.getStartPoint()); + + const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB)); + const bool bLeave(impLeftOfEdges(aPrevCA, aNextCA, aNextCB)); + + if(bEnter != bLeave) + { + // crossover + impSwitchNext(rPNa, rPNb); + } + } + } + else + { + const B2DPoint& rNextA(maPNV[rPNa.mnIN].maPoint); + const B2DPoint& rPrevA(maPNV[rPNa.mnIP].maPoint); + + if(rNextA.equal(rPrevA)) + { + // deadend on A + return; + } + + const B2DPoint& rNextB(maPNV[rPNb.mnIN].maPoint); + const B2DPoint& rPrevB(maPNV[rPNb.mnIP].maPoint); + + if(rNextB.equal(rPrevB)) + { + // deadend on B + return; + } + + if(rPrevA.equal(rPrevB)) + { + // common edge in same direction + if(rNextA.equal(rNextB)) + { + // common edge in same direction continues + return; + } + else + { + // common edge in same direction leave + // action is done on enter + return; + } + } + else if(rPrevA.equal(rNextB)) + { + // common edge in opposite direction + if(rNextA.equal(rPrevB)) + { + // common edge in opposite direction continues + return; + } + else + { + // common edge in opposite direction leave + impSwitchNext(rPNa, rPNb); + } + } + else if(rNextA.equal(rNextB)) + { + // common edge in same direction enter + // search leave edge + PN* pPNa2 = &maPNV[rPNa.mnIN]; + PN* pPNb2 = &maPNV[rPNb.mnIN]; + bool bOnEdge(true); + + do + { + const B2DPoint& rNextA2(maPNV[pPNa2->mnIN].maPoint); + const B2DPoint& rNextB2(maPNV[pPNb2->mnIN].maPoint); + + if(rNextA2.equal(rNextB2)) + { + pPNa2 = &maPNV[pPNa2->mnIN]; + pPNb2 = &maPNV[pPNb2->mnIN]; + } + else + { + bOnEdge = false; + } + } + while(bOnEdge && pPNa2 != &rPNa && pPNa2 != &rPNa); + + if(bOnEdge) + { + // loop over two identical polygon paths + return; + } + else + { + // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges + // at enter/leave. Check for crossover. + const B2DPoint& aPointE(rPNa.maPoint); + const B2DVector aPrevAE(rPrevA - aPointE); + const B2DVector aNextAE(rNextA - aPointE); + const B2DVector aPrevBE(rPrevB - aPointE); + + const B2DPoint& aPointL(pPNa2->maPoint); + const B2DVector aPrevAL(maPNV[pPNa2->mnIP].maPoint - aPointL); + const B2DVector aNextAL(maPNV[pPNa2->mnIN].maPoint - aPointL); + const B2DVector aNextBL(maPNV[pPNb2->mnIN].maPoint - aPointL); + + const bool bEnter(impLeftOfEdges(aPrevAE, aNextAE, aPrevBE)); + const bool bLeave(impLeftOfEdges(aPrevAL, aNextAL, aNextBL)); + + if(bEnter != bLeave) + { + // crossover; switch start or end + impSwitchNext(rPNa, rPNb); + } + } + } + else if(rNextA.equal(rPrevB)) + { + // common edge in opposite direction enter + impSwitchNext(rPNa, rPNb); + } + else + { + // no common edges, check for crossover + const B2DPoint& aPoint(rPNa.maPoint); + const B2DVector aPrevA(rPrevA - aPoint); + const B2DVector aNextA(rNextA - aPoint); + const B2DVector aPrevB(rPrevB - aPoint); + const B2DVector aNextB(rNextB - aPoint); + + const bool bEnter(impLeftOfEdges(aPrevA, aNextA, aPrevB)); + const bool bLeave(impLeftOfEdges(aPrevA, aNextA, aNextB)); + + if(bEnter != bLeave) + { + // crossover + impSwitchNext(rPNa, rPNb); + } + } + } + } + + void impSolve() + { + // sort by point to identify common nodes + ::std::sort(maSNV.begin(), maSNV.end()); + + // handle common nodes + const sal_uInt32 nNodeCount(maSNV.size()); + + for(sal_uInt32 a(0); a < nNodeCount - 1; a++) + { + // test a before using it, not after. Also use nPointCount instead of aSortNodes.size() + PN& rPNb = *(maSNV[a].mpPN); + + for(sal_uInt32 b(a + 1); b < nNodeCount && rPNb.maPoint.equal(maSNV[b].mpPN->maPoint); b++) + { + impHandleCommon(rPNb, *maSNV[b].mpPN); + } + } + } + + public: + solver(const B2DPolygon& rOriginal) + : maOriginal(B2DPolyPolygon(rOriginal)), + mbIsCurve(false), + mbChanged(false) + { + const sal_uInt32 nOriginalCount(rOriginal.count()); + + if(nOriginalCount) + { + B2DPolygon aGeometry(tools::addPointsAtCutsAndTouches(rOriginal)); + aGeometry.removeDoublePoints(); + aGeometry = tools::simplifyCurveSegments(aGeometry); + mbIsCurve = aGeometry.areControlPointsUsed(); + + const sal_uInt32 nPointCount(aGeometry.count()); + + // If it's not a pezier polygon, at least four points are needed to create + // a self-intersection. If it's a bezier polygon, the minimum point number + // is two, since with a single point You get a curve, but no self-intersection + if(nPointCount > 3 || (nPointCount > 1 && mbIsCurve)) + { + // reserve space in point, control and sort vector. + maSNV.reserve(nPointCount); + maPNV.reserve(nPointCount); + maVNV.reserve(mbIsCurve ? nPointCount : 0); + + // fill data + impAddPolygon(0, aGeometry); + + // solve common nodes + impSolve(); + } + } + } + + solver(const B2DPolyPolygon& rOriginal) + : maOriginal(rOriginal), + mbIsCurve(false), + mbChanged(false) + { + sal_uInt32 nOriginalCount(maOriginal.count()); + + if(nOriginalCount) + { + B2DPolyPolygon aGeometry(tools::addPointsAtCutsAndTouches(maOriginal, true)); + aGeometry.removeDoublePoints(); + aGeometry = tools::simplifyCurveSegments(aGeometry); + mbIsCurve = aGeometry.areControlPointsUsed(); + nOriginalCount = aGeometry.count(); + + if(nOriginalCount) + { + sal_uInt32 nPointCount(0); + sal_uInt32 a(0); + + // count points + for(a = 0; a < nOriginalCount; a++) + { + const B2DPolygon aCandidate(aGeometry.getB2DPolygon(a)); + const sal_uInt32 nCandCount(aCandidate.count()); + + // If it's not a bezier curve, at least three points would be needed to have a + // topological relevant (not empty) polygon. Since its not known here if trivial + // edges (dead ends) will be kept or sorted out, add non-bezier polygons with + // more than one point. + // For bezier curves, the minimum for defining an area is also one. + if(nCandCount) + { + nPointCount += nCandCount; + } + } + + if(nPointCount) + { + // reserve space in point, control and sort vector. + maSNV.reserve(nPointCount); + maPNV.reserve(nPointCount); + maVNV.reserve(mbIsCurve ? nPointCount : 0); + + // fill data + sal_uInt32 nInsertIndex(0); + + for(a = 0; a < nOriginalCount; a++) + { + const B2DPolygon aCandidate(aGeometry.getB2DPolygon(a)); + const sal_uInt32 nCandCount(aCandidate.count()); + + // use same condition as above, the data vector is + // pre-allocated + if(nCandCount) + { + impAddPolygon(nInsertIndex, aCandidate); + nInsertIndex += nCandCount; + } + } + + // solve common nodes + impSolve(); + } + } + } + } + + B2DPolyPolygon getB2DPolyPolygon() + { + if(mbChanged) + { + B2DPolyPolygon aRetval; + const sal_uInt32 nCount(maPNV.size()); + sal_uInt32 nCountdown(nCount); + + for(sal_uInt32 a(0); nCountdown && a < nCount; a++) + { + PN& rPN = maPNV[a]; + + if(SAL_MAX_UINT32 != rPN.mnI) + { + // unused node, start new part polygon + B2DPolygon aNewPart; + PN* pPNCurr = &rPN; + + do + { + const B2DPoint& rPoint = pPNCurr->maPoint; + aNewPart.append(rPoint); + + if(mbIsCurve) + { + const VN& rVNCurr = maVNV[pPNCurr->mnI]; + + if(!rVNCurr.maPrev.equalZero()) + { + aNewPart.setPrevControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maPrev); + } + + if(!rVNCurr.maNext.equalZero()) + { + aNewPart.setNextControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maNext); + } + } + + pPNCurr->mnI = SAL_MAX_UINT32; + nCountdown--; + pPNCurr = &(maPNV[pPNCurr->mnIN]); + } + while(pPNCurr != &rPN && SAL_MAX_UINT32 != pPNCurr->mnI); + + // close and add + aNewPart.setClosed(true); + aRetval.append(aNewPart); + } + } + + return aRetval; + } + else + { + // no change, return original + return maOriginal; + } + } + }; + + ////////////////////////////////////////////////////////////////////////////// + + } // end of anonymous namespace +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + ////////////////////////////////////////////////////////////////////////////// + + B2DPolyPolygon solveCrossovers(const B2DPolyPolygon& rCandidate) + { + if(rCandidate.count() > 1L) + { + solver aSolver(rCandidate); + return aSolver.getB2DPolyPolygon(); + } + else + { + return rCandidate; + } + } + + ////////////////////////////////////////////////////////////////////////////// + + B2DPolyPolygon solveCrossovers(const B2DPolygon& rCandidate) + { + solver aSolver(rCandidate); + return aSolver.getB2DPolyPolygon(); + } + + ////////////////////////////////////////////////////////////////////////////// + + B2DPolyPolygon stripNeutralPolygons(const B2DPolyPolygon& rCandidate) + { + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + if(ORIENTATION_NEUTRAL != tools::getOrientation(aCandidate)) + { + aRetval.append(aCandidate); + } + } + + return aRetval; + } + + ////////////////////////////////////////////////////////////////////////////// + + B2DPolyPolygon stripDispensablePolygons(const B2DPolyPolygon& rCandidate, bool bKeepAboveZero) + { + const sal_uInt32 nCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + if(nCount) + { + if(nCount == 1L) + { + if(!bKeepAboveZero && ORIENTATION_POSITIVE == tools::getOrientation(rCandidate.getB2DPolygon(0L))) + { + aRetval = rCandidate; + } + } + else + { + sal_uInt32 a, b; + ::std::vector< StripHelper > aHelpers; + aHelpers.resize(nCount); + + for(a = 0L; a < nCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + StripHelper* pNewHelper = &(aHelpers[a]); + pNewHelper->maRange = tools::getRange(aCandidate); + pNewHelper->meOrinetation = tools::getOrientation(aCandidate); + pNewHelper->mnDepth = (ORIENTATION_NEGATIVE == pNewHelper->meOrinetation ? -1L : 0L); + } + + for(a = 0L; a < nCount - 1L; a++) + { + const B2DPolygon aCandA(rCandidate.getB2DPolygon(a)); + StripHelper& rHelperA = aHelpers[a]; + + for(b = a + 1L; b < nCount; b++) + { + const B2DPolygon aCandB(rCandidate.getB2DPolygon(b)); + StripHelper& rHelperB = aHelpers[b]; + const bool bAInB(rHelperB.maRange.isInside(rHelperA.maRange) && tools::isInside(aCandB, aCandA, true)); + const bool bBInA(rHelperA.maRange.isInside(rHelperB.maRange) && tools::isInside(aCandA, aCandB, true)); + + if(bAInB && bBInA) + { + // congruent + if(rHelperA.meOrinetation == rHelperB.meOrinetation) + { + // two polys or two holes. Lower one of them to get one of them out of the way. + // Since each will be contained in the other one, both will be increased, too. + // So, for lowering, increase only one of them + rHelperA.mnDepth++; + } + else + { + // poly and hole. They neutralize, so get rid of both. Move securely below zero. + rHelperA.mnDepth = -((sal_Int32)nCount); + rHelperB.mnDepth = -((sal_Int32)nCount); + } + } + else + { + if(bAInB) + { + if(ORIENTATION_NEGATIVE == rHelperB.meOrinetation) + { + rHelperA.mnDepth--; + } + else + { + rHelperA.mnDepth++; + } + } + else if(bBInA) + { + if(ORIENTATION_NEGATIVE == rHelperA.meOrinetation) + { + rHelperB.mnDepth--; + } + else + { + rHelperB.mnDepth++; + } + } + } + } + } + + for(a = 0L; a < nCount; a++) + { + const StripHelper& rHelper = aHelpers[a]; + bool bAcceptEntry(bKeepAboveZero ? 1L <= rHelper.mnDepth : 0L == rHelper.mnDepth); + + if(bAcceptEntry) + { + aRetval.append(rCandidate.getB2DPolygon(a)); + } + } + } + } + + return aRetval; + } + + ////////////////////////////////////////////////////////////////////////////// + + B2DPolyPolygon prepareForPolygonOperation(const B2DPolygon& rCandidate) + { + solver aSolver(rCandidate); + B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon())); + + return correctOrientations(aRetval); + } + + B2DPolyPolygon prepareForPolygonOperation(const B2DPolyPolygon& rCandidate) + { + solver aSolver(rCandidate); + B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon())); + + return correctOrientations(aRetval); + } + + B2DPolyPolygon solvePolygonOperationOr(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB) + { + if(!rCandidateA.count()) + { + return rCandidateB; + } + else if(!rCandidateB.count()) + { + return rCandidateA; + } + else + { + // concatenate polygons, solve crossovers and throw away all sub-polygons + // which have a depth other than 0. + B2DPolyPolygon aRetval(rCandidateA); + + aRetval.append(rCandidateB); + aRetval = solveCrossovers(aRetval); + aRetval = stripNeutralPolygons(aRetval); + + return stripDispensablePolygons(aRetval, false); + } + } + + B2DPolyPolygon solvePolygonOperationXor(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB) + { + if(!rCandidateA.count()) + { + return rCandidateB; + } + else if(!rCandidateB.count()) + { + return rCandidateA; + } + else + { + // XOR is pretty simple: By definition it is the simple concatenation of + // the single polygons since we imply XOR fill rule. Make it intersection-free + // and correct orientations + B2DPolyPolygon aRetval(rCandidateA); + + aRetval.append(rCandidateB); + aRetval = solveCrossovers(aRetval); + aRetval = stripNeutralPolygons(aRetval); + + return correctOrientations(aRetval); + } + } + + B2DPolyPolygon solvePolygonOperationAnd(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB) + { + if(!rCandidateA.count()) + { + return B2DPolyPolygon(); + } + else if(!rCandidateB.count()) + { + return B2DPolyPolygon(); + } + else + { + // concatenate polygons, solve crossovers and throw away all sub-polygons + // with a depth of < 1. This means to keep all polygons where at least two + // polygons do overlap. + B2DPolyPolygon aRetval(rCandidateA); + + aRetval.append(rCandidateB); + aRetval = solveCrossovers(aRetval); + aRetval = stripNeutralPolygons(aRetval); + + return stripDispensablePolygons(aRetval, true); + } + } + + B2DPolyPolygon solvePolygonOperationDiff(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB) + { + if(!rCandidateA.count()) + { + return B2DPolyPolygon(); + } + else if(!rCandidateB.count()) + { + return rCandidateA; + } + else + { + // Make B topologically to holes and append to A + B2DPolyPolygon aRetval(rCandidateB); + + aRetval.flip(); + aRetval.append(rCandidateA); + + // solve crossovers and throw away all sub-polygons which have a + // depth other than 0. + aRetval = basegfx::tools::solveCrossovers(aRetval); + aRetval = basegfx::tools::stripNeutralPolygons(aRetval); + + return basegfx::tools::stripDispensablePolygons(aRetval, false); + } + } + + B2DPolyPolygon mergeToSinglePolyPolygon(const std::vector< basegfx::B2DPolyPolygon >& rInput) + { + std::vector< basegfx::B2DPolyPolygon > aInput(rInput); + + // first step: prepareForPolygonOperation and simple merge of non-overlapping + // PolyPolygons for speedup; this is possible for the wanted OR-operation + if(aInput.size()) + { + std::vector< basegfx::B2DPolyPolygon > aResult; + aResult.reserve(aInput.size()); + + for(sal_uInt32 a(0); a < aInput.size(); a++) + { + const basegfx::B2DPolyPolygon aCandidate(prepareForPolygonOperation(aInput[a])); + + if(aResult.size()) + { + const B2DRange aCandidateRange(aCandidate.getB2DRange()); + bool bCouldMergeSimple(false); + + for(sal_uInt32 b(0); !bCouldMergeSimple && b < aResult.size(); b++) + { + basegfx::B2DPolyPolygon aTarget(aResult[b]); + const B2DRange aTargetRange(aTarget.getB2DRange()); + + if(!aCandidateRange.overlaps(aTargetRange)) + { + aTarget.append(aCandidate); + aResult[b] = aTarget; + bCouldMergeSimple = true; + } + } + + if(!bCouldMergeSimple) + { + aResult.push_back(aCandidate); + } + } + else + { + aResult.push_back(aCandidate); + } + } + + aInput = aResult; + } + + // second step: melt pairwise to a single PolyPolygon + while(aInput.size() > 1) + { + std::vector< basegfx::B2DPolyPolygon > aResult; + aResult.reserve((aInput.size() / 2) + 1); + + for(sal_uInt32 a(0); a < aInput.size(); a += 2) + { + if(a + 1 < aInput.size()) + { + // a pair for processing + aResult.push_back(solvePolygonOperationOr(aInput[a], aInput[a + 1])); + } + else + { + // last single PolyPolygon; copy to target to not lose it + aResult.push_back(aInput[a]); + } + } + + aInput = aResult; + } + + // third step: get result + if(1 == aInput.size()) + { + return aInput[0]; + } + + return B2DPolyPolygon(); + } + + ////////////////////////////////////////////////////////////////////////////// + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/polygon/b2dpolypolygonrasterconverter.cxx b/basegfx/source/polygon/b2dpolypolygonrasterconverter.cxx new file mode 100644 index 000000000000..b795c04e158e --- /dev/null +++ b/basegfx/source/polygon/b2dpolypolygonrasterconverter.cxx @@ -0,0 +1,702 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" + +#include <basegfx/polygon/b2dpolypolygonrasterconverter.hxx> + +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> + +#include <boost/mem_fn.hpp> + +#include <algorithm> + +namespace basegfx +{ + class radixSort { + + //! public interface + public: + + //! default constructor + radixSort( void ); + + //! destructor + ~radixSort( void ); + + bool sort( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride ); + + inline sal_uInt32 *indices( void ) const { return m_indices1; } + + //! private attributes + private: + + // current size of index list + sal_uInt32 m_current_size; + + // last known size of index list + sal_uInt32 m_previous_size; + + // index lists + sal_uInt32 *m_indices1; + sal_uInt32 *m_indices2; + + sal_uInt32 m_counter[256*4]; + sal_uInt32 m_offset[256]; + + //! private methods + private: + + bool resize( sal_uInt32 nNumElements ); + inline void reset_indices( void ); + bool prepareCounters( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride ); + }; + + inline radixSort::radixSort( void ) { + + m_indices1 = NULL; + m_indices2 = NULL; + m_current_size = 0; + m_previous_size = 0; + + reset_indices(); + } + + inline radixSort::~radixSort( void ) { + + delete [] m_indices2; + delete [] m_indices1; + } + + bool radixSort::resize( sal_uInt32 nNumElements ) { + + if(nNumElements==m_previous_size) + return true; + + if(nNumElements > m_current_size) { + + // release index lists + if(m_indices2) + delete [] m_indices2; + if(m_indices1) + delete [] m_indices1; + + // allocate new index lists + m_indices1 = new sal_uInt32[nNumElements]; + m_indices2 = new sal_uInt32[nNumElements]; + + // check for out of memory situation + if(!m_indices1 || !m_indices2) { + delete [] m_indices1; + delete [] m_indices2; + m_indices1 = NULL; + m_indices2 = NULL; + m_current_size = 0; + return false; + } + + m_current_size = nNumElements; + } + + m_previous_size = nNumElements; + + // initialize indices + reset_indices(); + + return true; + } + + inline void radixSort::reset_indices( void ) { + + for(sal_uInt32 i=0;i<m_current_size;i++) + m_indices1[i] = i; + } + + bool radixSort::prepareCounters( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride ) { + + // clear counters + sal_uInt32 *ptr = m_counter; + for(int i=0; i<64; ++i) + { + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + } + + // prepare pointers to relevant memory addresses + sal_uInt8 *p = (sal_uInt8*)pInput; + sal_uInt8 *pe = p+(nNumElements*dwStride); + sal_uInt32 *h0= &m_counter[0]; + sal_uInt32 *h1= &m_counter[256]; + sal_uInt32 *h2= &m_counter[512]; + sal_uInt32 *h3= &m_counter[768]; + + sal_uInt32 *Indices = m_indices1; + float previous_value = *(float *)(((sal_uInt8 *)pInput)+(m_indices1[0]*dwStride)); + bool bSorted = true; + while(p!=pe) { + float value = *(float *)(((sal_uInt8 *)pInput)+((*Indices++)*dwStride)); + if(value<previous_value) { + bSorted = false; + break; + } + previous_value = value; + h0[*p++]++; + h1[*p++]++; + h2[*p++]++; + h3[*p++]++; + p += dwStride-4; + } + if(bSorted) + return true; + while(p!=pe) { + h0[*p++]++; + h1[*p++]++; + h2[*p++]++; + h3[*p++]++; + p += dwStride-4; + } + return false; + } + + bool radixSort::sort( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride ) { + + if(!(pInput)) + return false; + if(!(nNumElements)) + return false; + if(!(resize(nNumElements))) + return false; + + // prepare radix counters, return if already sorted + if(prepareCounters(pInput,nNumElements,dwStride)) + return true; + + // count number of negative values + sal_uInt32 num_negatives = 0; + sal_uInt32 *h3= &m_counter[768]; + for(sal_uInt32 i=128;i<256;i++) + num_negatives += h3[i]; + + // perform passes, one for each byte + for(sal_uInt32 j=0;j<4;j++) { + + // ignore this pass if all values have the same byte + bool bRun = true; + sal_uInt32 *current_counter = &m_counter[j<<8]; + sal_uInt8 unique_value = *(((sal_uInt8*)pInput)+j); + if(current_counter[unique_value]==nNumElements) + bRun=false; + + // does the incoming byte contain the sign bit? + sal_uInt32 i; + if(j!=3) { + if(bRun) { + m_offset[0] = 0; + for(i=1;i<256;i++) + m_offset[i] = m_offset[i-1] + current_counter[i-1]; + sal_uInt8 *InputBytes = (sal_uInt8 *)pInput; + sal_uInt32 *Indices = m_indices1; + sal_uInt32 *IndicesEnd = &m_indices1[nNumElements]; + InputBytes += j; + while(Indices!=IndicesEnd) { + sal_uInt32 id = *Indices++; + m_indices2[m_offset[InputBytes[id*dwStride]]++] = id; + } + sal_uInt32 *Tmp = m_indices1; + m_indices1 = m_indices2; + m_indices2 = Tmp; + } + } + else { + if(bRun) { + m_offset[0] = num_negatives; + for(i=1;i<128;i++) + m_offset[i] = m_offset[i-1] + current_counter[i-1]; + m_offset[255] = 0; + for(i=0;i<127;i++) + m_offset[254-i] = m_offset[255-i] + current_counter[255-i]; + for(i=128;i<256;i++) + m_offset[i] += current_counter[i]; + for(i=0;i<nNumElements;i++) { + sal_uInt32 Radix = (*(sal_uInt32 *)(((sal_uInt8 *)pInput)+(m_indices1[i]*dwStride)))>>24; + if(Radix<128) m_indices2[m_offset[Radix]++] = m_indices1[i]; + else m_indices2[--m_offset[Radix]] = m_indices1[i]; + } + sal_uInt32 *Tmp = m_indices1; + m_indices1 = m_indices2; + m_indices2 = Tmp; + } + else { + if(unique_value>=128) { + for(i=0;i<nNumElements;i++) + m_indices2[i] = m_indices1[nNumElements-i-1]; + sal_uInt32 *Tmp = m_indices1; + m_indices1 = m_indices2; + m_indices2 = Tmp; + } + } + } + } + + return true; + } + + //************************************************************ + // Internal vertex storage of B2DPolyPolygonRasterConverter + //************************************************************ + + inline B2DPolyPolygonRasterConverter::Vertex::Vertex() : + aP1(), + aP2(), + bDownwards( true ) + { + } + + inline B2DPolyPolygonRasterConverter::Vertex::Vertex( const B2DPoint& rP1, const B2DPoint& rP2, bool bDown ) : + aP1( rP1 ), + aP2( rP2 ), + bDownwards( bDown ) + { + } + + + //************************************************************ + // Helper class for holding horizontal line segments during raster + // conversion + //************************************************************ + + namespace + { + class ImplLineNode + { + public: + sal_Int32 mnYCounter; + float mfXPos; + float mfXDelta; + bool mbDownwards; + + public: + /**rP1 and rP2 must not have equal y values, when rounded + to integer! + */ + ImplLineNode(const B2DPoint& rP1, const B2DPoint& rP2, bool bDown) : + mnYCounter( fround(rP2.getY()) - fround(rP1.getY()) ), + mfXPos( (float)(rP1.getX()) ), + mfXDelta((float) ((rP2.getX() - rP1.getX()) / mnYCounter) ), + mbDownwards( bDown ) + { + } + + /// get current x position + const float& getXPos() const + { + return mfXPos; + } + + /// returns true, if line ends on this Y value + float nextLine() + { + if(mnYCounter>=0) + { + // go one step in Y + mfXPos += mfXDelta; + --mnYCounter; + return mfXDelta; + } + + return 0.0f; + } + + bool isEnded() + { + return mnYCounter<=0; + } + + bool isDownwards() + { + return mbDownwards; + } + }; + } + + typedef ::std::vector<ImplLineNode> VectorOfLineNodes; + + + //************************************************************ + // Base2D PolyPolygon Raster Converter (Rasterizer) + //************************************************************ + + namespace + { + struct VertexComparator + { + bool operator()( const B2DPolyPolygonRasterConverter::Vertex& rLHS, + const B2DPolyPolygonRasterConverter::Vertex& rRHS ) + { + return rLHS.aP1.getX() < rRHS.aP1.getX(); + } + }; + } + + void B2DPolyPolygonRasterConverter::init() + { + if(!maPolyPolyRectangle.isEmpty()) + { + const sal_Int32 nMinY( fround(maPolyPolyRectangle.getMinY()) ); + const sal_Int32 nScanlines(fround(maPolyPolyRectangle.getMaxY()) - nMinY); + + maScanlines.resize( nScanlines+1 ); + + // add all polygons + for( sal_uInt32 i(0), nCount(maPolyPolygon.count()); + i < nCount; + ++i ) + { + // add all vertices + const B2DPolygon& rPoly( maPolyPolygon.getB2DPolygon(i) ); + for( sal_uInt32 k(0), nVertices(rPoly.count()); + k<nVertices; + ++k ) + { + const B2DPoint& rP1( rPoly.getB2DPoint(k) ); + const B2DPoint& rP2( rPoly.getB2DPoint( (k + 1) % nVertices ) ); + + const sal_Int32 nVertexYP1( fround(rP1.getY()) ); + const sal_Int32 nVertexYP2( fround(rP2.getY()) ); + + // insert only vertices which are not strictly + // horizontal. Note that the ImplLineNode relies on + // this. + if(nVertexYP1 != nVertexYP2) + { + if( nVertexYP2 < nVertexYP1 ) + { + const sal_Int32 nStartScanline(nVertexYP2 - nMinY); + + // swap edges + maScanlines[ nStartScanline ].push_back( Vertex(rP2, rP1, false) ); + } + else + { + const sal_Int32 nStartScanline(nVertexYP1 - nMinY); + + maScanlines[ nStartScanline ].push_back( Vertex(rP1, rP2, true) ); + } + } + } + } + + // now sort all scanlines, with increasing x coordinates + VectorOfVertexVectors::iterator aIter( maScanlines.begin() ); + VectorOfVertexVectors::iterator aEnd( maScanlines.end() ); + while( aIter != aEnd ) + { + ::std::sort( aIter->begin(), + aIter->end(), + VertexComparator() ); + ++aIter; + } + } + } + + B2DPolyPolygonRasterConverter::B2DPolyPolygonRasterConverter( const B2DPolyPolygon& rPolyPoly ) : + maPolyPolygon( rPolyPoly ), + maPolyPolyRectangle( tools::getRange( rPolyPoly ) ), + maScanlines() + { + init(); + } + + namespace + { + B2DRectangle getCombinedBounds( const B2DPolyPolygon& rPolyPolyRaster, + const B2DRectangle& rRasterArea ) + { + B2DRectangle aRect( tools::getRange( rPolyPolyRaster ) ); + aRect.expand( rRasterArea ); + + return aRect; + } + } + + B2DPolyPolygonRasterConverter::B2DPolyPolygonRasterConverter( const B2DPolyPolygon& rPolyPolyRaster, + const B2DRectangle& rRasterArea ) : + maPolyPolygon( rPolyPolyRaster ), + maPolyPolyRectangle( + getCombinedBounds( rPolyPolyRaster, + rRasterArea ) ), + maScanlines() + { + init(); + } + + B2DPolyPolygonRasterConverter::~B2DPolyPolygonRasterConverter() + { + } + + namespace + { + class LineNodeGenerator + { + public: + LineNodeGenerator( VectorOfLineNodes& rActiveVertices ) : + mrActiveVertices( rActiveVertices ) + { + } + + void operator()( const B2DPolyPolygonRasterConverter::Vertex& rVertex ) + { + mrActiveVertices.push_back( ImplLineNode(rVertex.aP1, + rVertex.aP2, + rVertex.bDownwards) ); + } + + private: + VectorOfLineNodes& mrActiveVertices; + }; + + struct LineNodeComparator + { + bool operator()( const ImplLineNode& rLHS, const ImplLineNode& rRHS ) + { + return rLHS.getXPos() < rRHS.getXPos(); + } + }; + } + + void B2DPolyPolygonRasterConverter::rasterConvert( FillRule eFillRule ) + { + if( maScanlines.empty() ) + return; // no scanlines at all -> bail out + + const sal_Int32 nMinY( fround(maPolyPolyRectangle.getMinY()) ); + const sal_Int32 nScanlines(fround(maPolyPolyRectangle.getMaxY()) - nMinY); + + // Vector of currently active vertices. A vertex is active, if + // it crosses or touches the current scanline. + VectorOfLineNodes aActiveVertices; + + // mickey's optimized version... + radixSort rs; + std::size_t nb(0); + std::size_t nb_previous(0); + bool bSort(false); + + // process each scanline + for( sal_Int32 y(0); y <= nScanlines; ++y ) + { + // add vertices which start at current scanline into + // active vertex vector + ::std::for_each( maScanlines[y].begin(), + maScanlines[y].end(), + LineNodeGenerator( aActiveVertices ) ); + nb = aActiveVertices.size(); + if(nb != nb_previous) + { + nb_previous = nb; + bSort = true; + } + + // sort with increasing X + if(bSort) + { + bSort = false; + + if( nb ) + { + rs.sort(&aActiveVertices[0].mfXPos, + nb, + sizeof(ImplLineNode)); + } + } + + const std::size_t nLen( nb ); + if( !nLen ) + { + // empty scanline - call derived with an 'off' span + // for the full width + span( maPolyPolyRectangle.getMinX(), + maPolyPolyRectangle.getMaxX(), + nMinY + y, + false ); + } + else + { + const sal_Int32 nCurrY( nMinY + y ); + + // scanline not empty - forward all scans to derived, + // according to selected fill rule + + // TODO(P1): Maybe allow these 'off' span calls to be + // switched off (or all 'on' span calls, depending on + // use case scenario) + + // sorting didn't change the order of the elements + // in memory but prepared a list of indices in sorted order. + // thus we now process the nodes with an additional indirection. + sal_uInt32 *sorted = rs.indices(); + + // call derived with 'off' span for everything left of first active span + if( aActiveVertices[sorted[0]].getXPos() > maPolyPolyRectangle.getMinX() ) + { + span( maPolyPolyRectangle.getMinX(), + aActiveVertices[sorted[0]].getXPos(), + nCurrY, + false ); + } + + switch( eFillRule ) + { + default: + OSL_ENSURE(false, + "B2DPolyPolygonRasterConverter::rasterConvert(): Unexpected fill rule"); + return; + + case FillRule_EVEN_ODD: + // process each span in current scanline, with + // even-odd fill rule + for( ::std::size_t i(0), nLength(aActiveVertices.size()); + i+1 < nLength; + ++i ) + { + sal_uInt32 nIndex = sorted[i]; + sal_uInt32 nNextIndex = sorted[i+1]; + span( aActiveVertices[nIndex].getXPos(), + aActiveVertices[nNextIndex].getXPos(), + nCurrY, + i % 2 == 0 ); + + float delta = aActiveVertices[nIndex].nextLine(); + if(delta > 0.0f) + { + if(aActiveVertices[nIndex].getXPos() > aActiveVertices[nNextIndex].getXPos()) + bSort = true; + } + else if(delta < 0.0f) + { + if(i) + { + sal_uInt32 nPrevIndex = sorted[i-1]; + if(aActiveVertices[nIndex].getXPos() < aActiveVertices[nPrevIndex].getXPos()) + bSort = true; + } + } + } + break; + + case FillRule_NONZERO_WINDING_NUMBER: + // process each span in current scanline, with + // non-zero winding numbe fill rule + sal_Int32 nWindingNumber(0); + for( ::std::size_t i(0), nLength(aActiveVertices.size()); + i+1 < nLength; + ++i ) + { + sal_uInt32 nIndex = sorted[i]; + sal_uInt32 nNextIndex = sorted[i+1]; + nWindingNumber += -1 + 2*aActiveVertices[nIndex].isDownwards(); + + span( aActiveVertices[nIndex].getXPos(), + aActiveVertices[nNextIndex].getXPos(), + nCurrY, + nWindingNumber != 0 ); + + float delta = aActiveVertices[nIndex].nextLine(); + if(delta > 0.0f) + { + if(aActiveVertices[nIndex].getXPos() > aActiveVertices[nNextIndex].getXPos()) + bSort = true; + } + else if(delta < 0.0f) + { + if(i) + { + sal_uInt32 nPrevIndex = sorted[i-1]; + if(aActiveVertices[nIndex].getXPos() < aActiveVertices[nPrevIndex].getXPos()) + bSort = true; + } + } + } + break; + } + + // call derived with 'off' span for everything right of last active span + if( aActiveVertices[sorted[nb-1]].getXPos()+1.0 < maPolyPolyRectangle.getMaxX() ) + { + span( aActiveVertices[sorted[nb-1]].getXPos()+1.0, + maPolyPolyRectangle.getMaxX(), + nCurrY, + false ); + } + + // also call nextLine on very last line node + sal_uInt32 nIndex = sorted[nb-1]; + float delta = aActiveVertices[nIndex].nextLine(); + if(delta < 0.0f) + { + if(nb) + { + sal_uInt32 nPrevIndex = sorted[nb-2]; + if(aActiveVertices[nIndex].getXPos() < aActiveVertices[nPrevIndex].getXPos()) + bSort = true; + } + } + } + + // remove line nodes which have ended on the current scanline + aActiveVertices.erase( ::std::remove_if( aActiveVertices.begin(), + aActiveVertices.end(), + ::boost::mem_fn( &ImplLineNode::isEnded ) ), + aActiveVertices.end() ); + nb = aActiveVertices.size(); + if(nb != nb_previous) + { + nb_previous = nb; + bSort = true; + } + } + } +} +// eof diff --git a/basegfx/source/polygon/b2dpolypolygontools.cxx b/basegfx/source/polygon/b2dpolypolygontools.cxx new file mode 100644 index 000000000000..dcfa34f93c02 --- /dev/null +++ b/basegfx/source/polygon/b2dpolypolygontools.cxx @@ -0,0 +1,585 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <osl/diagnose.h> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> + +#include <numeric> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + B2DPolyPolygon correctOrientations(const B2DPolyPolygon& rCandidate) + { + B2DPolyPolygon aRetval(rCandidate); + const sal_uInt32 nCount(aRetval.count()); + + for(sal_uInt32 a(0L); a < nCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + const B2VectorOrientation aOrientation(tools::getOrientation(aCandidate)); + sal_uInt32 nDepth(0L); + + for(sal_uInt32 b(0L); b < nCount; b++) + { + if(b != a) + { + const B2DPolygon aCompare(rCandidate.getB2DPolygon(b)); + + if(tools::isInside(aCompare, aCandidate, true)) + { + nDepth++; + } + } + } + + const bool bShallBeHole(1L == (nDepth & 0x00000001)); + const bool bIsHole(ORIENTATION_NEGATIVE == aOrientation); + + if(bShallBeHole != bIsHole && ORIENTATION_NEUTRAL != aOrientation) + { + B2DPolygon aFlipped(aCandidate); + aFlipped.flip(); + aRetval.setB2DPolygon(a, aFlipped); + } + } + + return aRetval; + } + + B2DPolyPolygon correctOutmostPolygon(const B2DPolyPolygon& rCandidate) + { + const sal_uInt32 nCount(rCandidate.count()); + + if(nCount > 1L) + { + for(sal_uInt32 a(0L); a < nCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + sal_uInt32 nDepth(0L); + + for(sal_uInt32 b(0L); b < nCount; b++) + { + if(b != a) + { + const B2DPolygon aCompare(rCandidate.getB2DPolygon(b)); + + if(tools::isInside(aCompare, aCandidate, true)) + { + nDepth++; + } + } + } + + if(!nDepth) + { + B2DPolyPolygon aRetval(rCandidate); + + if(a != 0L) + { + // exchange polygon a and polygon 0L + aRetval.setB2DPolygon(0L, aCandidate); + aRetval.setB2DPolygon(a, rCandidate.getB2DPolygon(0L)); + } + + // exit + return aRetval; + } + } + } + + return rCandidate; + } + + B2DPolyPolygon adaptiveSubdivideByDistance(const B2DPolyPolygon& rCandidate, double fDistanceBound) + { + if(rCandidate.areControlPointsUsed()) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + if(aCandidate.areControlPointsUsed()) + { + aRetval.append(tools::adaptiveSubdivideByDistance(aCandidate, fDistanceBound)); + } + else + { + aRetval.append(aCandidate); + } + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + B2DPolyPolygon adaptiveSubdivideByAngle(const B2DPolyPolygon& rCandidate, double fAngleBound) + { + if(rCandidate.areControlPointsUsed()) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + if(aCandidate.areControlPointsUsed()) + { + aRetval.append(tools::adaptiveSubdivideByAngle(aCandidate, fAngleBound)); + } + else + { + aRetval.append(aCandidate); + } + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + B2DPolyPolygon adaptiveSubdivideByCount(const B2DPolyPolygon& rCandidate, sal_uInt32 nCount) + { + if(rCandidate.areControlPointsUsed()) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + if(aCandidate.areControlPointsUsed()) + { + aRetval.append(tools::adaptiveSubdivideByCount(aCandidate, nCount)); + } + else + { + aRetval.append(aCandidate); + } + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + bool isInside(const B2DPolyPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + + if(1L == nPolygonCount) + { + return isInside(rCandidate.getB2DPolygon(0L), rPoint, bWithBorder); + } + else + { + sal_Int32 nInsideCount(0L); + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolygon aPolygon(rCandidate.getB2DPolygon(a)); + const bool bInside(isInside(aPolygon, rPoint, bWithBorder)); + + if(bInside) + { + nInsideCount++; + } + } + + return (nInsideCount % 2L); + } + } + + B2DRange getRangeWithControlPoints(const B2DPolyPolygon& rCandidate) + { + B2DRange aRetval; + const sal_uInt32 nPolygonCount(rCandidate.count()); + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + B2DPolygon aCandidate = rCandidate.getB2DPolygon(a); + aRetval.expand(tools::getRangeWithControlPoints(aCandidate)); + } + + return aRetval; + } + + B2DRange getRange(const B2DPolyPolygon& rCandidate) + { + B2DRange aRetval; + const sal_uInt32 nPolygonCount(rCandidate.count()); + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + B2DPolygon aCandidate = rCandidate.getB2DPolygon(a); + aRetval.expand(tools::getRange(aCandidate)); + } + + return aRetval; + } + + void applyLineDashing(const B2DPolyPolygon& rCandidate, const ::std::vector<double>& rDotDashArray, B2DPolyPolygon* pLineTarget, B2DPolyPolygon* pGapTarget, double fFullDashDotLen) + { + if(0.0 == fFullDashDotLen && rDotDashArray.size()) + { + // calculate fFullDashDotLen from rDotDashArray + fFullDashDotLen = ::std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0); + } + + if(rCandidate.count() && fFullDashDotLen > 0.0) + { + B2DPolyPolygon aLineTarget, aGapTarget; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + applyLineDashing( + aCandidate, + rDotDashArray, + pLineTarget ? &aLineTarget : 0, + pGapTarget ? &aGapTarget : 0, + fFullDashDotLen); + + if(pLineTarget) + { + pLineTarget->append(aLineTarget); + } + + if(pGapTarget) + { + pGapTarget->append(aGapTarget); + } + } + } + } + + bool isInEpsilonRange(const B2DPolyPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + if(isInEpsilonRange(aCandidate, rTestPosition, fDistance)) + { + return true; + } + } + + return false; + } + + B3DPolyPolygon createB3DPolyPolygonFromB2DPolyPolygon(const B2DPolyPolygon& rCandidate, double fZCoordinate) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B3DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + aRetval.append(createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate)); + } + + return aRetval; + } + + B2DPolyPolygon createB2DPolyPolygonFromB3DPolyPolygon(const B3DPolyPolygon& rCandidate, const B3DHomMatrix& rMat) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + B3DPolygon aCandidate(rCandidate.getB3DPolygon(a)); + + aRetval.append(createB2DPolygonFromB3DPolygon(aCandidate, rMat)); + } + + return aRetval; + } + + double getSmallestDistancePointToPolyPolygon(const B2DPolyPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rPolygonIndex, sal_uInt32& rEdgeIndex, double& rCut) + { + double fRetval(DBL_MAX); + const double fZero(0.0); + const sal_uInt32 nPolygonCount(rCandidate.count()); + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + sal_uInt32 nNewEdgeIndex; + double fNewCut; + const double fNewDistance(getSmallestDistancePointToPolygon(aCandidate, rTestPoint, nNewEdgeIndex, fNewCut)); + + if(DBL_MAX == fRetval || fNewDistance < fRetval) + { + fRetval = fNewDistance; + rPolygonIndex = a; + rEdgeIndex = nNewEdgeIndex; + rCut = fNewCut; + + if(fTools::equal(fRetval, fZero)) + { + // already found zero distance, cannot get better. Ensure numerical zero value and end loop. + fRetval = 0.0; + break; + } + } + } + + return fRetval; + } + + B2DPolyPolygon distort(const B2DPolyPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + aRetval.append(distort(aCandidate, rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight)); + } + + return aRetval; + } + + B2DPolyPolygon rotateAroundPoint(const B2DPolyPolygon& rCandidate, const B2DPoint& rCenter, double fAngle) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + aRetval.append(rotateAroundPoint(aCandidate, rCenter, fAngle)); + } + + return aRetval; + } + + B2DPolyPolygon expandToCurve(const B2DPolyPolygon& rCandidate) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + aRetval.append(expandToCurve(aCandidate)); + } + + return aRetval; + } + + B2DPolyPolygon setContinuity(const B2DPolyPolygon& rCandidate, B2VectorContinuity eContinuity) + { + if(rCandidate.areControlPointsUsed()) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + const B2DPolygon aCandidate(rCandidate.getB2DPolygon(a)); + + aRetval.append(setContinuity(aCandidate, eContinuity)); + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + B2DPolyPolygon growInNormalDirection(const B2DPolyPolygon& rCandidate, double fValue) + { + if(0.0 != fValue) + { + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(growInNormalDirection(rCandidate.getB2DPolygon(a), fValue)); + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + void correctGrowShrinkPolygonPair(B2DPolyPolygon& /*rOriginal*/, B2DPolyPolygon& /*rGrown*/) + { + } + + B2DPolyPolygon reSegmentPolyPolygon(const B2DPolyPolygon& rCandidate, sal_uInt32 nSegments) + { + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(reSegmentPolygon(rCandidate.getB2DPolygon(a), nSegments)); + } + + return aRetval; + } + + B2DPolyPolygon interpolate(const B2DPolyPolygon& rOld1, const B2DPolyPolygon& rOld2, double t) + { + OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolyPolygon interpolate: Different geometry (!)"); + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rOld1.count(); a++) + { + aRetval.append(interpolate(rOld1.getB2DPolygon(a), rOld2.getB2DPolygon(a), t)); + } + + return aRetval; + } + + bool isRectangle( const B2DPolyPolygon& rPoly ) + { + // exclude some cheap cases first + if( rPoly.count() != 1 ) + return false; + + return isRectangle( rPoly.getB2DPolygon(0) ); + } + + // #i76891# + B2DPolyPolygon simplifyCurveSegments(const B2DPolyPolygon& rCandidate) + { + if(rCandidate.areControlPointsUsed()) + { + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(simplifyCurveSegments(rCandidate.getB2DPolygon(a))); + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + B2DPolyPolygon reSegmentPolyPolygonEdges(const B2DPolyPolygon& rCandidate, sal_uInt32 nSubEdges, bool bHandleCurvedEdges, bool bHandleStraightEdges) + { + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(reSegmentPolygonEdges(rCandidate.getB2DPolygon(a), nSubEdges, bHandleCurvedEdges, bHandleStraightEdges)); + } + + return aRetval; + } + + ////////////////////////////////////////////////////////////////////// + // comparators with tolerance for 2D PolyPolygons + + bool equal(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB, const double& rfSmallValue) + { + const sal_uInt32 nPolygonCount(rCandidateA.count()); + + if(nPolygonCount != rCandidateB.count()) + return false; + + for(sal_uInt32 a(0); a < nPolygonCount; a++) + { + const B2DPolygon aCandidate(rCandidateA.getB2DPolygon(a)); + + if(!equal(aCandidate, rCandidateB.getB2DPolygon(a), rfSmallValue)) + return false; + } + + return true; + } + + bool equal(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB) + { + const double fSmallValue(fTools::getSmallValue()); + + return equal(rCandidateA, rCandidateB, fSmallValue); + } + + B2DPolyPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolyPolygon& rCandidate) + { + B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(snapPointsOfHorizontalOrVerticalEdges(rCandidate.getB2DPolygon(a))); + } + + return aRetval; + } + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/polygon/b2dsvgpolypolygon.cxx b/basegfx/source/polygon/b2dsvgpolypolygon.cxx new file mode 100644 index 000000000000..bbb6db4c064a --- /dev/null +++ b/basegfx/source/polygon/b2dsvgpolypolygon.cxx @@ -0,0 +1,1105 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" + +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <rtl/ustring.hxx> +#include <rtl/math.hxx> + +namespace basegfx +{ + namespace tools + { + namespace + { + void lcl_skipSpaces(sal_Int32& io_rPos, + const ::rtl::OUString& rStr, + const sal_Int32 nLen) + { + while( io_rPos < nLen && + sal_Unicode(' ') == rStr[io_rPos] ) + { + ++io_rPos; + } + } + + void lcl_skipSpacesAndCommas(sal_Int32& io_rPos, + const ::rtl::OUString& rStr, + const sal_Int32 nLen) + { + while(io_rPos < nLen + && (sal_Unicode(' ') == rStr[io_rPos] || sal_Unicode(',') == rStr[io_rPos])) + { + ++io_rPos; + } + } + + inline bool lcl_isOnNumberChar(const ::rtl::OUString& rStr, const sal_Int32 nPos, bool bSignAllowed = true) + { + const sal_Unicode aChar(rStr[nPos]); + + const bool bPredicate( (sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar) + || (bSignAllowed && sal_Unicode('+') == aChar) + || (bSignAllowed && sal_Unicode('-') == aChar) ); + + return bPredicate; + } + + bool lcl_getDoubleChar(double& o_fRetval, + sal_Int32& io_rPos, + const ::rtl::OUString& rStr, + const sal_Int32 /*nLen*/) + { + sal_Unicode aChar( rStr[io_rPos] ); + ::rtl::OUStringBuffer sNumberString; + + if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar) + { + sNumberString.append(rStr[io_rPos]); + aChar = rStr[++io_rPos]; + } + + while((sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar) + || sal_Unicode('.') == aChar) + { + sNumberString.append(rStr[io_rPos]); + aChar = rStr[++io_rPos]; + } + + if(sal_Unicode('e') == aChar || sal_Unicode('E') == aChar) + { + sNumberString.append(rStr[io_rPos]); + aChar = rStr[++io_rPos]; + + if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar) + { + sNumberString.append(rStr[io_rPos]); + aChar = rStr[++io_rPos]; + } + + while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar) + { + sNumberString.append(rStr[io_rPos]); + aChar = rStr[++io_rPos]; + } + } + + if(sNumberString.getLength()) + { + rtl_math_ConversionStatus eStatus; + o_fRetval = ::rtl::math::stringToDouble( sNumberString.makeStringAndClear(), + (sal_Unicode)('.'), + (sal_Unicode)(','), + &eStatus, + NULL ); + return ( eStatus == rtl_math_ConversionStatus_Ok ); + } + + return false; + } + + bool lcl_importDoubleAndSpaces( double& o_fRetval, + sal_Int32& io_rPos, + const ::rtl::OUString& rStr, + const sal_Int32 nLen ) + { + if( !lcl_getDoubleChar(o_fRetval, io_rPos, rStr, nLen) ) + return false; + + lcl_skipSpacesAndCommas(io_rPos, rStr, nLen); + + return true; + } + + bool lcl_importNumberAndSpaces(sal_Int32& o_nRetval, + sal_Int32& io_rPos, + const ::rtl::OUString& rStr, + const sal_Int32 nLen) + { + sal_Unicode aChar( rStr[io_rPos] ); + ::rtl::OUStringBuffer sNumberString; + + if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar) + { + sNumberString.append(rStr[io_rPos]); + aChar = rStr[++io_rPos]; + } + + while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar) + { + sNumberString.append(rStr[io_rPos]); + aChar = rStr[++io_rPos]; + } + + if(sNumberString.getLength()) + { + o_nRetval = sNumberString.makeStringAndClear().toInt32(); + lcl_skipSpacesAndCommas(io_rPos, rStr, nLen); + + return true; + } + + return false; + } + + void lcl_skipNumber(sal_Int32& io_rPos, + const ::rtl::OUString& rStr, + const sal_Int32 nLen) + { + bool bSignAllowed(true); + + while(io_rPos < nLen && lcl_isOnNumberChar(rStr, io_rPos, bSignAllowed)) + { + bSignAllowed = false; + ++io_rPos; + } + } + + void lcl_skipDouble(sal_Int32& io_rPos, + const ::rtl::OUString& rStr, + const sal_Int32 /*nLen*/) + { + sal_Unicode aChar( rStr[io_rPos] ); + + if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar) + aChar = rStr[++io_rPos]; + + while((sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar) + || sal_Unicode('.') == aChar) + { + aChar = rStr[++io_rPos]; + } + + if(sal_Unicode('e') == aChar || sal_Unicode('E') == aChar) + { + aChar = rStr[++io_rPos]; + + if(sal_Unicode('+') == aChar || sal_Unicode('-') == aChar) + aChar = rStr[++io_rPos]; + + while(sal_Unicode('0') <= aChar && sal_Unicode('9') >= aChar) + { + aChar = rStr[++io_rPos]; + } + } + } + void lcl_skipNumberAndSpacesAndCommas(sal_Int32& io_rPos, + const ::rtl::OUString& rStr, + const sal_Int32 nLen) + { + lcl_skipNumber(io_rPos, rStr, nLen); + lcl_skipSpacesAndCommas(io_rPos, rStr, nLen); + } + + // #100617# Allow to skip doubles, too. + void lcl_skipDoubleAndSpacesAndCommas(sal_Int32& io_rPos, + const ::rtl::OUString& rStr, + const sal_Int32 nLen) + { + lcl_skipDouble(io_rPos, rStr, nLen); + lcl_skipSpacesAndCommas(io_rPos, rStr, nLen); + } + + void lcl_putNumberChar( ::rtl::OUString& rStr, + double fValue ) + { + rStr += ::rtl::OUString::valueOf( fValue ); + } + + void lcl_putNumberCharWithSpace( ::rtl::OUString& rStr, + double fValue, + double fOldValue, + bool bUseRelativeCoordinates ) + { + if( bUseRelativeCoordinates ) + fValue -= fOldValue; + + const sal_Int32 aLen( rStr.getLength() ); + if(aLen) + { + if( lcl_isOnNumberChar(rStr, aLen - 1, false) && + fValue >= 0.0 ) + { + rStr += ::rtl::OUString::valueOf( + sal_Unicode(' ') ); + } + } + + lcl_putNumberChar(rStr, fValue); + } + + inline sal_Unicode lcl_getCommand( sal_Char cUpperCaseCommand, + sal_Char cLowerCaseCommand, + bool bUseRelativeCoordinates ) + { + return bUseRelativeCoordinates ? cLowerCaseCommand : cUpperCaseCommand; + } + } + + bool importFromSvgD(B2DPolyPolygon& o_rPolyPolygon, const ::rtl::OUString& rSvgDStatement) + { + o_rPolyPolygon.clear(); + const sal_Int32 nLen(rSvgDStatement.getLength()); + sal_Int32 nPos(0); + bool bIsClosed(false); + double nLastX( 0.0 ); + double nLastY( 0.0 ); + B2DPolygon aCurrPoly; + + // skip initial whitespace + lcl_skipSpaces(nPos, rSvgDStatement, nLen); + + while(nPos < nLen) + { + bool bRelative(false); + bool bMoveTo(false); + const sal_Unicode aCurrChar(rSvgDStatement[nPos]); + + switch(aCurrChar) + { + case 'z' : + case 'Z' : + { + nPos++; + lcl_skipSpaces(nPos, rSvgDStatement, nLen); + + // remember closed state of current polygon + bIsClosed = true; + break; + } + + case 'm' : + case 'M' : + { + bMoveTo = true; + // FALLTHROUGH intended + } + case 'l' : + case 'L' : + { + if('m' == aCurrChar || 'l' == aCurrChar) + { + bRelative = true; + } + + if(bMoveTo) + { + // new polygon start, finish old one + if(aCurrPoly.count()) + { + // add current polygon + if(bIsClosed) + { + closeWithGeometryChange(aCurrPoly); + } + + o_rPolyPolygon.append(aCurrPoly); + + // reset import values + bIsClosed = false; + aCurrPoly.clear(); + } + } + + nPos++; + lcl_skipSpaces(nPos, rSvgDStatement, nLen); + + while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) + { + double nX, nY; + + if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; + + if(bRelative) + { + nX += nLastX; + nY += nLastY; + } + + // set last position + nLastX = nX; + nLastY = nY; + + // add point + aCurrPoly.append(B2DPoint(nX, nY)); + } + break; + } + + case 'h' : + { + bRelative = true; + // FALLTHROUGH intended + } + case 'H' : + { + nPos++; + lcl_skipSpaces(nPos, rSvgDStatement, nLen); + + while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) + { + double nX, nY(nLastY); + + if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; + + if(bRelative) + { + nX += nLastX; + } + + // set last position + nLastX = nX; + + // add point + aCurrPoly.append(B2DPoint(nX, nY)); + } + break; + } + + case 'v' : + { + bRelative = true; + // FALLTHROUGH intended + } + case 'V' : + { + nPos++; + lcl_skipSpaces(nPos, rSvgDStatement, nLen); + + while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) + { + double nX(nLastX), nY; + + if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; + + if(bRelative) + { + nY += nLastY; + } + + // set last position + nLastY = nY; + + // add point + aCurrPoly.append(B2DPoint(nX, nY)); + } + break; + } + + case 's' : + { + bRelative = true; + // FALLTHROUGH intended + } + case 'S' : + { + nPos++; + lcl_skipSpaces(nPos, rSvgDStatement, nLen); + + while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) + { + double nX, nY; + double nX2, nY2; + + if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; + + if(bRelative) + { + nX2 += nLastX; + nY2 += nLastY; + nX += nLastX; + nY += nLastY; + } + + // ensure existance of start point + if(!aCurrPoly.count()) + { + aCurrPoly.append(B2DPoint(nLastX, nLastY)); + } + + // get first control point. It's the reflection of the PrevControlPoint + // of the last point. If not existent, use current point (see SVG) + B2DPoint aPrevControl(B2DPoint(nLastX, nLastY)); + const sal_uInt32 nIndex(aCurrPoly.count() - 1); + + if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex)) + { + const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex)); + const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex)); + + // use mirrored previous control point + aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX()); + aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY()); + } + + // append curved edge + aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY)); + + // set last position + nLastX = nX; + nLastY = nY; + } + break; + } + + case 'c' : + { + bRelative = true; + // FALLTHROUGH intended + } + case 'C' : + { + nPos++; + lcl_skipSpaces(nPos, rSvgDStatement, nLen); + + while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) + { + double nX, nY; + double nX1, nY1; + double nX2, nY2; + + if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; + + if(bRelative) + { + nX1 += nLastX; + nY1 += nLastY; + nX2 += nLastX; + nY2 += nLastY; + nX += nLastX; + nY += nLastY; + } + + // ensure existance of start point + if(!aCurrPoly.count()) + { + aCurrPoly.append(B2DPoint(nLastX, nLastY)); + } + + // append curved edge + aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY)); + + // set last position + nLastX = nX; + nLastY = nY; + } + break; + } + + // #100617# quadratic beziers are imported as cubic ones + case 'q' : + { + bRelative = true; + // FALLTHROUGH intended + } + case 'Q' : + { + nPos++; + lcl_skipSpaces(nPos, rSvgDStatement, nLen); + + while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) + { + double nX, nY; + double nX1, nY1; + + if(!lcl_importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; + + if(bRelative) + { + nX1 += nLastX; + nY1 += nLastY; + nX += nLastX; + nY += nLastY; + } + + // calculate the cubic bezier coefficients from the quadratic ones + const double nX1Prime((nX1 * 2.0 + nLastX) / 3.0); + const double nY1Prime((nY1 * 2.0 + nLastY) / 3.0); + const double nX2Prime((nX1 * 2.0 + nX) / 3.0); + const double nY2Prime((nY1 * 2.0 + nY) / 3.0); + + // ensure existance of start point + if(!aCurrPoly.count()) + { + aCurrPoly.append(B2DPoint(nLastX, nLastY)); + } + + // append curved edge + aCurrPoly.appendBezierSegment(B2DPoint(nX1Prime, nY1Prime), B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY)); + + // set last position + nLastX = nX; + nLastY = nY; + } + break; + } + + // #100617# relative quadratic beziers are imported as cubic + case 't' : + { + bRelative = true; + // FALLTHROUGH intended + } + case 'T' : + { + nPos++; + lcl_skipSpaces(nPos, rSvgDStatement, nLen); + + while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) + { + double nX, nY; + + if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; + + if(bRelative) + { + nX += nLastX; + nY += nLastY; + } + + // ensure existance of start point + if(!aCurrPoly.count()) + { + aCurrPoly.append(B2DPoint(nLastX, nLastY)); + } + + // get first control point. It's the reflection of the PrevControlPoint + // of the last point. If not existent, use current point (see SVG) + B2DPoint aPrevControl(B2DPoint(nLastX, nLastY)); + const sal_uInt32 nIndex(aCurrPoly.count() - 1); + const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex)); + + if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex)) + { + const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex)); + + // use mirrored previous control point + aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX()); + aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY()); + } + + if(!aPrevControl.equal(aPrevPoint)) + { + // there is a prev control point, and we have the already mirrored one + // in aPrevControl. We also need the quadratic control point for this + // new quadratic segment to calculate the 2nd cubic control point + const B2DPoint aQuadControlPoint( + ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0, + ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0); + + // calculate the cubic bezier coefficients from the quadratic ones. + const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0); + const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0); + + // append curved edge, use mirrored cubic control point directly + aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY)); + } + else + { + // when no previous control, SVG says to use current point -> straight line. + // Just add end point + aCurrPoly.append(B2DPoint(nX, nY)); + } + + // set last position + nLastX = nX; + nLastY = nY; + } + break; + } + + case 'a' : + { + bRelative = true; + // FALLTHROUGH intended + } + case 'A' : + { + nPos++; + lcl_skipSpaces(nPos, rSvgDStatement, nLen); + + while(nPos < nLen && lcl_isOnNumberChar(rSvgDStatement, nPos)) + { + double nX, nY; + double fRX, fRY, fPhi; + sal_Int32 bLargeArcFlag, bSweepFlag; + + if(!lcl_importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importNumberAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importNumberAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false; + + if(bRelative) + { + nX += nLastX; + nY += nLastY; + } + + const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(aCurrPoly.count() - 1)); + + if( nX == nLastX && nY == nLastY ) + continue; // start==end -> skip according to SVG spec + + if( fRX == 0.0 || fRY == 0.0 ) + { + // straight line segment according to SVG spec + aCurrPoly.append(B2DPoint(nX, nY)); + } + else + { + // normalize according to SVG spec + fRX=fabs(fRX); fRY=fabs(fRY); + + // from the SVG spec, appendix F.6.4 + + // |x1'| |cos phi sin phi| |(x1 - x2)/2| + // |y1'| = |-sin phi cos phi| |(y1 - y2)/2| + const B2DPoint p1(nLastX, nLastY); + const B2DPoint p2(nX, nY); + B2DHomMatrix aTransform(basegfx::tools::createRotateB2DHomMatrix(-fPhi*M_PI/180)); + + const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) ); + + // ______________________________________ rx y1' + // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry + // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1' + // rx + // chose + if f_A != f_S + // chose - if f_A = f_S + B2DPoint aCenter_prime; + const double fRadicant( + (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/ + (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX())); + if( fRadicant < 0.0 ) + { + // no solution - according to SVG + // spec, scale up ellipse + // uniformly such that it passes + // through end points (denominator + // of radicant solved for fRY, + // with s=fRX/fRY) + const double fRatio(fRX/fRY); + const double fRadicant2( + p1_prime.getY()*p1_prime.getY() + + p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio)); + if( fRadicant2 < 0.0 ) + { + // only trivial solution, one + // of the axes 0 -> straight + // line segment according to + // SVG spec + aCurrPoly.append(B2DPoint(nX, nY)); + continue; + } + + fRY=sqrt(fRadicant2); + fRX=fRatio*fRY; + + // keep center_prime forced to (0,0) + } + else + { + const double fFactor( + (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) * + sqrt(fRadicant)); + + // actually calculate center_prime + aCenter_prime = B2DPoint( + fFactor*fRX*p1_prime.getY()/fRY, + -fFactor*fRY*p1_prime.getX()/fRX); + } + + // + u - v + // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx)) + // - ||u|| ||v|| + + // 1 | (x1' - cx')/rx | + // theta1 = angle(( ), | | ) + // 0 | (y1' - cy')/ry | + const B2DPoint aRadii(fRX,fRY); + double fTheta1( + B2DVector(1.0,0.0).angle( + (p1_prime-aCenter_prime)/aRadii)); + + // |1| | (-x1' - cx')/rx | + // theta2 = angle( | | , | | ) + // |0| | (-y1' - cy')/ry | + double fTheta2( + B2DVector(1.0,0.0).angle( + (-p1_prime-aCenter_prime)/aRadii)); + + // map both angles to [0,2pi) + fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI); + fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI); + + // make sure the large arc is taken + // (since + // createPolygonFromEllipseSegment() + // normalizes to e.g. cw arc) + const bool bFlipSegment( (bLargeArcFlag!=0) == + (fmod(fTheta2+2*M_PI-fTheta1, + 2*M_PI)<M_PI) ); + if( bFlipSegment ) + std::swap(fTheta1,fTheta2); + + // finally, create bezier polygon from this + B2DPolygon aSegment( + tools::createPolygonFromUnitEllipseSegment( + fTheta1, fTheta2 )); + + // transform ellipse by rotation & move to final center + aTransform = basegfx::tools::createScaleB2DHomMatrix(fRX, fRY); + aTransform.translate(aCenter_prime.getX(), + aCenter_prime.getY()); + aTransform.rotate(fPhi*M_PI/180); + const B2DPoint aOffset((p1+p2)/2.0); + aTransform.translate(aOffset.getX(), + aOffset.getY()); + aSegment.transform(aTransform); + + // createPolygonFromEllipseSegment() + // always creates arcs that are + // positively oriented - flip polygon + // if we swapped angles above + if( bFlipSegment ) + aSegment.flip(); + aCurrPoly.append(aSegment); + } + + // set last position + nLastX = nX; + nLastY = nY; + } + break; + } + + default: + { + OSL_ENSURE(false, "importFromSvgD(): skipping tags in svg:d element (unknown)!"); + OSL_TRACE("importFromSvgD(): skipping tags in svg:d element (unknown: \"%c\")!", aCurrChar); + ++nPos; + break; + } + } + } + + if(aCurrPoly.count()) + { + // end-process last poly + if(bIsClosed) + { + closeWithGeometryChange(aCurrPoly); + } + + o_rPolyPolygon.append(aCurrPoly); + } + + return true; + } + + bool importFromSvgPoints( B2DPolygon& o_rPoly, + const ::rtl::OUString& rSvgPointsAttribute ) + { + o_rPoly.clear(); + const sal_Int32 nLen(rSvgPointsAttribute.getLength()); + sal_Int32 nPos(0); + double nX, nY; + + // skip initial whitespace + lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen); + + while(nPos < nLen) + { + if(!lcl_importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false; + if(!lcl_importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false; + + // add point + o_rPoly.append(B2DPoint(nX, nY)); + + // skip to next number, or finish + lcl_skipSpaces(nPos, rSvgPointsAttribute, nLen); + } + + return true; + } + + ::rtl::OUString exportToSvgD( + const B2DPolyPolygon& rPolyPolygon, + bool bUseRelativeCoordinates, + bool bDetectQuadraticBeziers) + { + const sal_uInt32 nCount(rPolyPolygon.count()); + ::rtl::OUString aResult; + B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point + + for(sal_uInt32 i(0); i < nCount; i++) + { + const B2DPolygon aPolygon(rPolyPolygon.getB2DPolygon(i)); + const sal_uInt32 nPointCount(aPolygon.count()); + + if(nPointCount) + { + const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed()); + const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1); + sal_Unicode aLastSVGCommand(' '); // last SVG command char + B2DPoint aLeft, aRight; // for quadratic bezier test + + // handle polygon start point + B2DPoint aEdgeStart(aPolygon.getB2DPoint(0)); + aResult += ::rtl::OUString::valueOf(lcl_getCommand('M', 'm', bUseRelativeCoordinates)); + lcl_putNumberCharWithSpace(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + aLastSVGCommand = lcl_getCommand('L', 'l', bUseRelativeCoordinates); + aCurrentSVGPosition = aEdgeStart; + + for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++) + { + // prepare access to next point + const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount); + const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex)); + + // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex) + const bool bEdgeIsBezier(bPolyUsesControlPoints + && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex))); + + if(bEdgeIsBezier) + { + // handle bezier edge + const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex)); + const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex)); + bool bIsQuadraticBezier(false); + + // check continuity at current edge's start point. For SVG, do NOT use an + // existing continuity since no 'S' or 's' statement should be written. At + // import, that 'previous' control vector is not available. SVG documentation + // says for interpretation: + // + // "(If there is no previous command or if the previous command was + // not an C, c, S or s, assume the first control point is coincident + // with the current point.)" + // + // That's what is done from our import, so avoid exporting it as first statement + // is necessary. + const bool bSymmetricAtEdgeStart( + 0 != nIndex + && CONTINUITY_C2 == aPolygon.getContinuityInPoint(nIndex)); + + if(bDetectQuadraticBeziers) + { + // check for quadratic beziers - that's + // the case if both control points are in + // the same place when they are prolonged + // to the common quadratic control point + // + // Left: P = (3P1 - P0) / 2 + // Right: P = (3P2 - P3) / 2 + aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0); + aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0); + bIsQuadraticBezier = aLeft.equal(aRight); + } + + if(bIsQuadraticBezier) + { + // approximately equal, export as quadratic bezier + if(bSymmetricAtEdgeStart) + { + const sal_Unicode aCommand(lcl_getCommand('T', 't', bUseRelativeCoordinates)); + + if(aLastSVGCommand != aCommand) + { + aResult += ::rtl::OUString::valueOf(aCommand); + aLastSVGCommand = aCommand; + } + + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + aLastSVGCommand = aCommand; + aCurrentSVGPosition = aEdgeEnd; + } + else + { + const sal_Unicode aCommand(lcl_getCommand('Q', 'q', bUseRelativeCoordinates)); + + if(aLastSVGCommand != aCommand) + { + aResult += ::rtl::OUString::valueOf(aCommand); + aLastSVGCommand = aCommand; + } + + lcl_putNumberCharWithSpace(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + aLastSVGCommand = aCommand; + aCurrentSVGPosition = aEdgeEnd; + } + } + else + { + // export as cubic bezier + if(bSymmetricAtEdgeStart) + { + const sal_Unicode aCommand(lcl_getCommand('S', 's', bUseRelativeCoordinates)); + + if(aLastSVGCommand != aCommand) + { + aResult += ::rtl::OUString::valueOf(aCommand); + aLastSVGCommand = aCommand; + } + + lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + aLastSVGCommand = aCommand; + aCurrentSVGPosition = aEdgeEnd; + } + else + { + const sal_Unicode aCommand(lcl_getCommand('C', 'c', bUseRelativeCoordinates)); + + if(aLastSVGCommand != aCommand) + { + aResult += ::rtl::OUString::valueOf(aCommand); + aLastSVGCommand = aCommand; + } + + lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + aLastSVGCommand = aCommand; + aCurrentSVGPosition = aEdgeEnd; + } + } + } + else + { + // straight edge + if(0 == nNextIndex) + { + // it's a closed polygon's last edge and it's not a bezier edge, so there is + // no need to write it + } + else + { + const bool bXEqual(aEdgeStart.getX() == aEdgeEnd.getX()); + const bool bYEqual(aEdgeStart.getY() == aEdgeEnd.getY()); + + if(bXEqual && bYEqual) + { + // point is a double point; do not export at all + } + else if(bXEqual) + { + // export as vertical line + const sal_Unicode aCommand(lcl_getCommand('V', 'v', bUseRelativeCoordinates)); + + if(aLastSVGCommand != aCommand) + { + aResult += ::rtl::OUString::valueOf(aCommand); + aLastSVGCommand = aCommand; + } + + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + aCurrentSVGPosition = aEdgeEnd; + } + else if(bYEqual) + { + // export as horizontal line + const sal_Unicode aCommand(lcl_getCommand('H', 'h', bUseRelativeCoordinates)); + + if(aLastSVGCommand != aCommand) + { + aResult += ::rtl::OUString::valueOf(aCommand); + aLastSVGCommand = aCommand; + } + + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + aCurrentSVGPosition = aEdgeEnd; + } + else + { + // export as line + const sal_Unicode aCommand(lcl_getCommand('L', 'l', bUseRelativeCoordinates)); + + if(aLastSVGCommand != aCommand) + { + aResult += ::rtl::OUString::valueOf(aCommand); + aLastSVGCommand = aCommand; + } + + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates); + lcl_putNumberCharWithSpace(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates); + aCurrentSVGPosition = aEdgeEnd; + } + } + } + + // prepare edge start for next loop step + aEdgeStart = aEdgeEnd; + } + + // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched) + if(aPolygon.isClosed()) + { + aResult += ::rtl::OUString::valueOf(lcl_getCommand('Z', 'z', bUseRelativeCoordinates)); + } + } + } + + return aResult; + } + } +} + +// eof diff --git a/basegfx/source/polygon/b2dtrapezoid.cxx b/basegfx/source/polygon/b2dtrapezoid.cxx new file mode 100644 index 000000000000..4cd63f938114 --- /dev/null +++ b/basegfx/source/polygon/b2dtrapezoid.cxx @@ -0,0 +1,1227 @@ +/************************************************************************* + * + * 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); + } + else + { + delete pNewLeft; + } + + B2DPoint* pNewRight = new B2DPoint(aSplitRight); + + if(splitEdgeAtGivenPoint(aRight, *pNewRight, aCurrent)) + { + maNewPoints.push_back(pNewRight); + } + else + { + delete pNewRight; + } + + bDone = true; + } + } + + 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()) + { + aSource = aSource.getDefaultAdaptiveSubdivision(); + } + + 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/b3dgeometry.cxx b/basegfx/source/polygon/b3dgeometry.cxx new file mode 100644 index 000000000000..6c1537bac0a6 --- /dev/null +++ b/basegfx/source/polygon/b3dgeometry.cxx @@ -0,0 +1,55 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <osl/diagnose.h> +#include <basegfx/polygon/b3dgeometry.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + B3DGeometry::B3DGeometry() + : mbUnifiedVectorValid(false) + { + } + + B3DGeometry::~B3DGeometry() + { + } + + bool B3DGeometry::operator==(const B3DGeometry& rGeometry) const + { + return (maPolyPolygon == maPolyPolygon + && maPolygonTo3D == maPolygonTo3D + && maPolyNormal == maPolyNormal + && maPolyTexture == maPolyTexture + } + +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/polygon/b3dpolygon.cxx b/basegfx/source/polygon/b3dpolygon.cxx new file mode 100644 index 000000000000..1985d3301d4b --- /dev/null +++ b/basegfx/source/polygon/b3dpolygon.cxx @@ -0,0 +1,1816 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <osl/diagnose.h> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <basegfx/point/b3dpoint.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <rtl/instance.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <vector> +#include <algorithm> + +////////////////////////////////////////////////////////////////////////////// + +class CoordinateData3D +{ + basegfx::B3DPoint maPoint; + +public: + CoordinateData3D() + : maPoint() + { + } + + explicit CoordinateData3D(const basegfx::B3DPoint& rData) + : maPoint(rData) + { + } + + const basegfx::B3DPoint& getCoordinate() const + { + return maPoint; + } + + void setCoordinate(const basegfx::B3DPoint& rValue) + { + if(rValue != maPoint) + maPoint = rValue; + } + + bool operator==(const CoordinateData3D& rData) const + { + return (maPoint == rData.getCoordinate()); + } + + void transform(const basegfx::B3DHomMatrix& rMatrix) + { + maPoint *= rMatrix; + } +}; + +////////////////////////////////////////////////////////////////////////////// + +class CoordinateDataArray3D +{ + typedef ::std::vector< CoordinateData3D > CoordinateData3DVector; + + CoordinateData3DVector maVector; + +public: + explicit CoordinateDataArray3D(sal_uInt32 nCount) + : maVector(nCount) + { + } + + explicit CoordinateDataArray3D(const CoordinateDataArray3D& rOriginal) + : maVector(rOriginal.maVector) + { + } + + CoordinateDataArray3D(const CoordinateDataArray3D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount) + : maVector(rOriginal.maVector.begin() + nIndex, rOriginal.maVector.begin() + (nIndex + nCount)) + { + } + + ~CoordinateDataArray3D() + { + } + + ::basegfx::B3DVector getNormal() const + { + ::basegfx::B3DVector aRetval; + const sal_uInt32 nPointCount(maVector.size()); + + if(nPointCount > 2) + { + sal_uInt32 nISmallest(0); + sal_uInt32 a(0); + const basegfx::B3DPoint* pSmallest(&maVector[0].getCoordinate()); + const basegfx::B3DPoint* pNext(0); + const basegfx::B3DPoint* pPrev(0); + + // To guarantee a correctly oriented point, choose an outmost one + // which then cannot be concave + for(a = 1; a < nPointCount; a++) + { + const basegfx::B3DPoint& rCandidate = maVector[a].getCoordinate(); + + if((rCandidate.getX() < pSmallest->getX()) + || (rCandidate.getX() == pSmallest->getX() && rCandidate.getY() < pSmallest->getY()) + || (rCandidate.getX() == pSmallest->getX() && rCandidate.getY() == pSmallest->getY() && rCandidate.getZ() < pSmallest->getZ())) + { + nISmallest = a; + pSmallest = &rCandidate; + } + } + + // look for a next point different from minimal one + for(a = (nISmallest + 1) % nPointCount; a != nISmallest; a = (a + 1) % nPointCount) + { + const basegfx::B3DPoint& rCandidate = maVector[a].getCoordinate(); + + if(!rCandidate.equal(*pSmallest)) + { + pNext = &rCandidate; + break; + } + } + + // look for a previous point different from minimal one + for(a = (nISmallest + nPointCount - 1) % nPointCount; a != nISmallest; a = (a + nPointCount - 1) % nPointCount) + { + const basegfx::B3DPoint& rCandidate = maVector[a].getCoordinate(); + + if(!rCandidate.equal(*pSmallest)) + { + pPrev = &rCandidate; + break; + } + } + + // we always have a minimal point. If we also have a different next and previous, + // we can calculate the normal + if(pNext && pPrev) + { + const basegfx::B3DVector aPrev(*pPrev - *pSmallest); + const basegfx::B3DVector aNext(*pNext - *pSmallest); + + aRetval = cross(aPrev, aNext); + aRetval.normalize(); + } + } + + return aRetval; + } + + sal_uInt32 count() const + { + return maVector.size(); + } + + bool operator==(const CoordinateDataArray3D& rCandidate) const + { + return (maVector == rCandidate.maVector); + } + + const basegfx::B3DPoint& getCoordinate(sal_uInt32 nIndex) const + { + return maVector[nIndex].getCoordinate(); + } + + void setCoordinate(sal_uInt32 nIndex, const basegfx::B3DPoint& rValue) + { + maVector[nIndex].setCoordinate(rValue); + } + + void insert(sal_uInt32 nIndex, const CoordinateData3D& rValue, sal_uInt32 nCount) + { + if(nCount) + { + // add nCount copies of rValue + CoordinateData3DVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + maVector.insert(aIndex, nCount, rValue); + } + } + + void insert(sal_uInt32 nIndex, const CoordinateDataArray3D& rSource) + { + const sal_uInt32 nCount(rSource.maVector.size()); + + if(nCount) + { + // insert data + CoordinateData3DVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + CoordinateData3DVector::const_iterator aStart(rSource.maVector.begin()); + CoordinateData3DVector::const_iterator aEnd(rSource.maVector.end()); + maVector.insert(aIndex, aStart, aEnd); + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + // remove point data + CoordinateData3DVector::iterator aStart(maVector.begin()); + aStart += nIndex; + const CoordinateData3DVector::iterator aEnd(aStart + nCount); + maVector.erase(aStart, aEnd); + } + } + + void flip() + { + if(maVector.size() > 1) + { + const sal_uInt32 nHalfSize(maVector.size() >> 1L); + CoordinateData3DVector::iterator aStart(maVector.begin()); + CoordinateData3DVector::iterator aEnd(maVector.end() - 1L); + + for(sal_uInt32 a(0); a < nHalfSize; a++) + { + ::std::swap(*aStart, *aEnd); + aStart++; + aEnd--; + } + } + } + + void transform(const ::basegfx::B3DHomMatrix& rMatrix) + { + CoordinateData3DVector::iterator aStart(maVector.begin()); + CoordinateData3DVector::iterator aEnd(maVector.end()); + + for(; aStart != aEnd; aStart++) + { + aStart->transform(rMatrix); + } + } +}; + +////////////////////////////////////////////////////////////////////////////// + +class BColorArray +{ + typedef ::std::vector< ::basegfx::BColor > BColorDataVector; + + BColorDataVector maVector; + sal_uInt32 mnUsedEntries; + +public: + explicit BColorArray(sal_uInt32 nCount) + : maVector(nCount), + mnUsedEntries(0L) + { + } + + explicit BColorArray(const BColorArray& rOriginal) + : maVector(rOriginal.maVector), + mnUsedEntries(rOriginal.mnUsedEntries) + { + } + + BColorArray(const BColorArray& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount) + : maVector(), + mnUsedEntries(0L) + { + BColorDataVector::const_iterator aStart(rOriginal.maVector.begin()); + aStart += nIndex; + BColorDataVector::const_iterator aEnd(aStart); + aEnd += nCount; + maVector.reserve(nCount); + + for(; aStart != aEnd; aStart++) + { + if(!aStart->equalZero()) + mnUsedEntries++; + + maVector.push_back(*aStart); + } + } + + ~BColorArray() + { + } + + sal_uInt32 count() const + { + return maVector.size(); + } + + bool operator==(const BColorArray& rCandidate) const + { + return (maVector == rCandidate.maVector); + } + + bool isUsed() const + { + return (0L != mnUsedEntries); + } + + const ::basegfx::BColor& getBColor(sal_uInt32 nIndex) const + { + return maVector[nIndex]; + } + + void setBColor(sal_uInt32 nIndex, const ::basegfx::BColor& rValue) + { + bool bWasUsed(mnUsedEntries && !maVector[nIndex].equalZero()); + bool bIsUsed(!rValue.equalZero()); + + if(bWasUsed) + { + if(bIsUsed) + { + maVector[nIndex] = rValue; + } + else + { + maVector[nIndex] = ::basegfx::BColor::getEmptyBColor(); + mnUsedEntries--; + } + } + else + { + if(bIsUsed) + { + maVector[nIndex] = rValue; + mnUsedEntries++; + } + } + } + + void insert(sal_uInt32 nIndex, const ::basegfx::BColor& rValue, sal_uInt32 nCount) + { + if(nCount) + { + // add nCount copies of rValue + BColorDataVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + maVector.insert(aIndex, nCount, rValue); + + if(!rValue.equalZero()) + mnUsedEntries += nCount; + } + } + + void insert(sal_uInt32 nIndex, const BColorArray& rSource) + { + const sal_uInt32 nCount(rSource.maVector.size()); + + if(nCount) + { + // insert data + BColorDataVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + BColorDataVector::const_iterator aStart(rSource.maVector.begin()); + BColorDataVector::const_iterator aEnd(rSource.maVector.end()); + maVector.insert(aIndex, aStart, aEnd); + + for(; aStart != aEnd; aStart++) + { + if(!aStart->equalZero()) + mnUsedEntries++; + } + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + const BColorDataVector::iterator aDeleteStart(maVector.begin() + nIndex); + const BColorDataVector::iterator aDeleteEnd(aDeleteStart + nCount); + BColorDataVector::const_iterator aStart(aDeleteStart); + + for(; mnUsedEntries && aStart != aDeleteEnd; aStart++) + { + if(!aStart->equalZero()) + mnUsedEntries--; + } + + // remove point data + maVector.erase(aDeleteStart, aDeleteEnd); + } + } + + void flip() + { + if(maVector.size() > 1) + { + const sal_uInt32 nHalfSize(maVector.size() >> 1L); + BColorDataVector::iterator aStart(maVector.begin()); + BColorDataVector::iterator aEnd(maVector.end() - 1L); + + for(sal_uInt32 a(0); a < nHalfSize; a++) + { + ::std::swap(*aStart, *aEnd); + aStart++; + aEnd--; + } + } + } +}; + +////////////////////////////////////////////////////////////////////////////// + +class NormalsArray3D +{ + typedef ::std::vector< ::basegfx::B3DVector > NormalsData3DVector; + + NormalsData3DVector maVector; + sal_uInt32 mnUsedEntries; + +public: + explicit NormalsArray3D(sal_uInt32 nCount) + : maVector(nCount), + mnUsedEntries(0L) + { + } + + explicit NormalsArray3D(const NormalsArray3D& rOriginal) + : maVector(rOriginal.maVector), + mnUsedEntries(rOriginal.mnUsedEntries) + { + } + + NormalsArray3D(const NormalsArray3D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount) + : maVector(), + mnUsedEntries(0L) + { + NormalsData3DVector::const_iterator aStart(rOriginal.maVector.begin()); + aStart += nIndex; + NormalsData3DVector::const_iterator aEnd(aStart); + aEnd += nCount; + maVector.reserve(nCount); + + for(; aStart != aEnd; aStart++) + { + if(!aStart->equalZero()) + mnUsedEntries++; + + maVector.push_back(*aStart); + } + } + + ~NormalsArray3D() + { + } + + sal_uInt32 count() const + { + return maVector.size(); + } + + bool operator==(const NormalsArray3D& rCandidate) const + { + return (maVector == rCandidate.maVector); + } + + bool isUsed() const + { + return (0L != mnUsedEntries); + } + + const ::basegfx::B3DVector& getNormal(sal_uInt32 nIndex) const + { + return maVector[nIndex]; + } + + void setNormal(sal_uInt32 nIndex, const ::basegfx::B3DVector& rValue) + { + bool bWasUsed(mnUsedEntries && !maVector[nIndex].equalZero()); + bool bIsUsed(!rValue.equalZero()); + + if(bWasUsed) + { + if(bIsUsed) + { + maVector[nIndex] = rValue; + } + else + { + maVector[nIndex] = ::basegfx::B3DVector::getEmptyVector(); + mnUsedEntries--; + } + } + else + { + if(bIsUsed) + { + maVector[nIndex] = rValue; + mnUsedEntries++; + } + } + } + + void insert(sal_uInt32 nIndex, const ::basegfx::B3DVector& rValue, sal_uInt32 nCount) + { + if(nCount) + { + // add nCount copies of rValue + NormalsData3DVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + maVector.insert(aIndex, nCount, rValue); + + if(!rValue.equalZero()) + mnUsedEntries += nCount; + } + } + + void insert(sal_uInt32 nIndex, const NormalsArray3D& rSource) + { + const sal_uInt32 nCount(rSource.maVector.size()); + + if(nCount) + { + // insert data + NormalsData3DVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + NormalsData3DVector::const_iterator aStart(rSource.maVector.begin()); + NormalsData3DVector::const_iterator aEnd(rSource.maVector.end()); + maVector.insert(aIndex, aStart, aEnd); + + for(; aStart != aEnd; aStart++) + { + if(!aStart->equalZero()) + mnUsedEntries++; + } + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + const NormalsData3DVector::iterator aDeleteStart(maVector.begin() + nIndex); + const NormalsData3DVector::iterator aDeleteEnd(aDeleteStart + nCount); + NormalsData3DVector::const_iterator aStart(aDeleteStart); + + for(; mnUsedEntries && aStart != aDeleteEnd; aStart++) + { + if(!aStart->equalZero()) + mnUsedEntries--; + } + + // remove point data + maVector.erase(aDeleteStart, aDeleteEnd); + } + } + + void flip() + { + if(maVector.size() > 1) + { + const sal_uInt32 nHalfSize(maVector.size() >> 1L); + NormalsData3DVector::iterator aStart(maVector.begin()); + NormalsData3DVector::iterator aEnd(maVector.end() - 1L); + + for(sal_uInt32 a(0); a < nHalfSize; a++) + { + ::std::swap(*aStart, *aEnd); + aStart++; + aEnd--; + } + } + } + + void transform(const basegfx::B3DHomMatrix& rMatrix) + { + NormalsData3DVector::iterator aStart(maVector.begin()); + NormalsData3DVector::iterator aEnd(maVector.end()); + + for(; aStart != aEnd; aStart++) + { + (*aStart) *= rMatrix; + } + } +}; + +////////////////////////////////////////////////////////////////////////////// + +class TextureCoordinate2D +{ + typedef ::std::vector< ::basegfx::B2DPoint > TextureData2DVector; + + TextureData2DVector maVector; + sal_uInt32 mnUsedEntries; + +public: + explicit TextureCoordinate2D(sal_uInt32 nCount) + : maVector(nCount), + mnUsedEntries(0L) + { + } + + explicit TextureCoordinate2D(const TextureCoordinate2D& rOriginal) + : maVector(rOriginal.maVector), + mnUsedEntries(rOriginal.mnUsedEntries) + { + } + + TextureCoordinate2D(const TextureCoordinate2D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount) + : maVector(), + mnUsedEntries(0L) + { + TextureData2DVector::const_iterator aStart(rOriginal.maVector.begin()); + aStart += nIndex; + TextureData2DVector::const_iterator aEnd(aStart); + aEnd += nCount; + maVector.reserve(nCount); + + for(; aStart != aEnd; aStart++) + { + if(!aStart->equalZero()) + mnUsedEntries++; + + maVector.push_back(*aStart); + } + } + + ~TextureCoordinate2D() + { + } + + sal_uInt32 count() const + { + return maVector.size(); + } + + bool operator==(const TextureCoordinate2D& rCandidate) const + { + return (maVector == rCandidate.maVector); + } + + bool isUsed() const + { + return (0L != mnUsedEntries); + } + + const ::basegfx::B2DPoint& getTextureCoordinate(sal_uInt32 nIndex) const + { + return maVector[nIndex]; + } + + void setTextureCoordinate(sal_uInt32 nIndex, const ::basegfx::B2DPoint& rValue) + { + bool bWasUsed(mnUsedEntries && !maVector[nIndex].equalZero()); + bool bIsUsed(!rValue.equalZero()); + + if(bWasUsed) + { + if(bIsUsed) + { + maVector[nIndex] = rValue; + } + else + { + maVector[nIndex] = ::basegfx::B2DPoint::getEmptyPoint(); + mnUsedEntries--; + } + } + else + { + if(bIsUsed) + { + maVector[nIndex] = rValue; + mnUsedEntries++; + } + } + } + + void insert(sal_uInt32 nIndex, const ::basegfx::B2DPoint& rValue, sal_uInt32 nCount) + { + if(nCount) + { + // add nCount copies of rValue + TextureData2DVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + maVector.insert(aIndex, nCount, rValue); + + if(!rValue.equalZero()) + mnUsedEntries += nCount; + } + } + + void insert(sal_uInt32 nIndex, const TextureCoordinate2D& rSource) + { + const sal_uInt32 nCount(rSource.maVector.size()); + + if(nCount) + { + // insert data + TextureData2DVector::iterator aIndex(maVector.begin()); + aIndex += nIndex; + TextureData2DVector::const_iterator aStart(rSource.maVector.begin()); + TextureData2DVector::const_iterator aEnd(rSource.maVector.end()); + maVector.insert(aIndex, aStart, aEnd); + + for(; aStart != aEnd; aStart++) + { + if(!aStart->equalZero()) + mnUsedEntries++; + } + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + const TextureData2DVector::iterator aDeleteStart(maVector.begin() + nIndex); + const TextureData2DVector::iterator aDeleteEnd(aDeleteStart + nCount); + TextureData2DVector::const_iterator aStart(aDeleteStart); + + for(; mnUsedEntries && aStart != aDeleteEnd; aStart++) + { + if(!aStart->equalZero()) + mnUsedEntries--; + } + + // remove point data + maVector.erase(aDeleteStart, aDeleteEnd); + } + } + + void flip() + { + if(maVector.size() > 1) + { + const sal_uInt32 nHalfSize(maVector.size() >> 1L); + TextureData2DVector::iterator aStart(maVector.begin()); + TextureData2DVector::iterator aEnd(maVector.end() - 1L); + + for(sal_uInt32 a(0); a < nHalfSize; a++) + { + ::std::swap(*aStart, *aEnd); + aStart++; + aEnd--; + } + } + } + + void transform(const ::basegfx::B2DHomMatrix& rMatrix) + { + TextureData2DVector::iterator aStart(maVector.begin()); + TextureData2DVector::iterator aEnd(maVector.end()); + + for(; aStart != aEnd; aStart++) + { + (*aStart) *= rMatrix; + } + } +}; + +////////////////////////////////////////////////////////////////////////////// + +class ImplB3DPolygon +{ + // The point vector. This vector exists always and defines the + // count of members. + CoordinateDataArray3D maPoints; + + // The BColor vector. This vectors are created on demand + // and may be zero. + BColorArray* mpBColors; + + // The Normals vector. This vectors are created on demand + // and may be zero. + NormalsArray3D* mpNormals; + + // The TextureCoordinates vector. This vectors are created on demand + // and may be zero. + TextureCoordinate2D* mpTextureCoordiantes; + + // The calculated plane normal. mbPlaneNormalValid says if it's valid. + ::basegfx::B3DVector maPlaneNormal; + + // bitfield + // flag which decides if this polygon is opened or closed + unsigned mbIsClosed : 1; + + // flag which says if maPlaneNormal is up-to-date + unsigned mbPlaneNormalValid : 1; + +protected: + void invalidatePlaneNormal() + { + if(mbPlaneNormalValid) + { + mbPlaneNormalValid = false; + } + } + +public: + // This constructor is only used from the static identity polygon, thus + // the RefCount is set to 1 to never 'delete' this static incarnation. + ImplB3DPolygon() + : maPoints(0L), + mpBColors(0L), + mpNormals(0L), + mpTextureCoordiantes(0L), + maPlaneNormal(::basegfx::B3DVector::getEmptyVector()), + mbIsClosed(false), + mbPlaneNormalValid(true) + { + // complete initialization with defaults + } + + ImplB3DPolygon(const ImplB3DPolygon& rToBeCopied) + : maPoints(rToBeCopied.maPoints), + mpBColors(0L), + mpNormals(0L), + mpTextureCoordiantes(0L), + maPlaneNormal(rToBeCopied.maPlaneNormal), + mbIsClosed(rToBeCopied.mbIsClosed), + mbPlaneNormalValid(rToBeCopied.mbPlaneNormalValid) + { + // complete initialization using copy + if(rToBeCopied.mpBColors && rToBeCopied.mpBColors->isUsed()) + { + mpBColors = new BColorArray(*rToBeCopied.mpBColors); + } + + if(rToBeCopied.mpNormals && rToBeCopied.mpNormals->isUsed()) + { + mpNormals = new NormalsArray3D(*rToBeCopied.mpNormals); + } + + if(rToBeCopied.mpTextureCoordiantes && rToBeCopied.mpTextureCoordiantes->isUsed()) + { + mpTextureCoordiantes = new TextureCoordinate2D(*rToBeCopied.mpTextureCoordiantes); + } + } + + ImplB3DPolygon(const ImplB3DPolygon& rToBeCopied, sal_uInt32 nIndex, sal_uInt32 nCount) + : maPoints(rToBeCopied.maPoints, nIndex, nCount), + mpBColors(0L), + mpNormals(0L), + mpTextureCoordiantes(0L), + maPlaneNormal(::basegfx::B3DVector::getEmptyVector()), + mbIsClosed(rToBeCopied.mbIsClosed), + mbPlaneNormalValid(false) + { + // complete initialization using partly copy + if(rToBeCopied.mpBColors && rToBeCopied.mpBColors->isUsed()) + { + mpBColors = new BColorArray(*rToBeCopied.mpBColors, nIndex, nCount); + + if(!mpBColors->isUsed()) + { + delete mpBColors; + mpBColors = 0L; + } + } + + if(rToBeCopied.mpNormals && rToBeCopied.mpNormals->isUsed()) + { + mpNormals = new NormalsArray3D(*rToBeCopied.mpNormals, nIndex, nCount); + + if(!mpNormals->isUsed()) + { + delete mpNormals; + mpNormals = 0L; + } + } + + if(rToBeCopied.mpTextureCoordiantes && rToBeCopied.mpTextureCoordiantes->isUsed()) + { + mpTextureCoordiantes = new TextureCoordinate2D(*rToBeCopied.mpTextureCoordiantes, nIndex, nCount); + + if(!mpTextureCoordiantes->isUsed()) + { + delete mpTextureCoordiantes; + mpTextureCoordiantes = 0L; + } + } + } + + ~ImplB3DPolygon() + { + if(mpBColors) + { + delete mpBColors; + mpBColors = 0L; + } + + if(mpNormals) + { + delete mpNormals; + mpNormals = 0L; + } + + if(mpTextureCoordiantes) + { + delete mpTextureCoordiantes; + mpTextureCoordiantes = 0L; + } + } + + sal_uInt32 count() const + { + return maPoints.count(); + } + + bool isClosed() const + { + return mbIsClosed; + } + + void setClosed(bool bNew) + { + if(bNew != (bool)mbIsClosed) + { + mbIsClosed = bNew; + } + } + + inline bool impBColorsAreEqual(const ImplB3DPolygon& rCandidate) const + { + bool bBColorsAreEqual(true); + + if(mpBColors) + { + if(rCandidate.mpBColors) + { + bBColorsAreEqual = (*mpBColors == *rCandidate.mpBColors); + } + else + { + // candidate has no BColors, so it's assumed all unused. + bBColorsAreEqual = !mpBColors->isUsed(); + } + } + else + { + if(rCandidate.mpBColors) + { + // we have no TextureCoordiantes, so it's assumed all unused. + bBColorsAreEqual = !rCandidate.mpBColors->isUsed(); + } + } + + return bBColorsAreEqual; + } + + inline bool impNormalsAreEqual(const ImplB3DPolygon& rCandidate) const + { + bool bNormalsAreEqual(true); + + if(mpNormals) + { + if(rCandidate.mpNormals) + { + bNormalsAreEqual = (*mpNormals == *rCandidate.mpNormals); + } + else + { + // candidate has no normals, so it's assumed all unused. + bNormalsAreEqual = !mpNormals->isUsed(); + } + } + else + { + if(rCandidate.mpNormals) + { + // we have no normals, so it's assumed all unused. + bNormalsAreEqual = !rCandidate.mpNormals->isUsed(); + } + } + + return bNormalsAreEqual; + } + + inline bool impTextureCoordinatesAreEqual(const ImplB3DPolygon& rCandidate) const + { + bool bTextureCoordinatesAreEqual(true); + + if(mpTextureCoordiantes) + { + if(rCandidate.mpTextureCoordiantes) + { + bTextureCoordinatesAreEqual = (*mpTextureCoordiantes == *rCandidate.mpTextureCoordiantes); + } + else + { + // candidate has no TextureCoordinates, so it's assumed all unused. + bTextureCoordinatesAreEqual = !mpTextureCoordiantes->isUsed(); + } + } + else + { + if(rCandidate.mpTextureCoordiantes) + { + // we have no TextureCoordiantes, so it's assumed all unused. + bTextureCoordinatesAreEqual = !rCandidate.mpTextureCoordiantes->isUsed(); + } + } + + return bTextureCoordinatesAreEqual; + } + + bool operator==(const ImplB3DPolygon& rCandidate) const + { + if(mbIsClosed == rCandidate.mbIsClosed) + { + if(maPoints == rCandidate.maPoints) + { + if(impBColorsAreEqual(rCandidate)) + { + if(impNormalsAreEqual(rCandidate)) + { + if(impTextureCoordinatesAreEqual(rCandidate)) + { + return true; + } + } + } + } + } + + return false; + } + + const ::basegfx::B3DPoint& getPoint(sal_uInt32 nIndex) const + { + return maPoints.getCoordinate(nIndex); + } + + void setPoint(sal_uInt32 nIndex, const ::basegfx::B3DPoint& rValue) + { + maPoints.setCoordinate(nIndex, rValue); + invalidatePlaneNormal(); + } + + void insert(sal_uInt32 nIndex, const ::basegfx::B3DPoint& rPoint, sal_uInt32 nCount) + { + if(nCount) + { + CoordinateData3D aCoordinate(rPoint); + maPoints.insert(nIndex, aCoordinate, nCount); + invalidatePlaneNormal(); + + if(mpBColors) + { + mpBColors->insert(nIndex, ::basegfx::BColor::getEmptyBColor(), nCount); + } + + if(mpNormals) + { + mpNormals->insert(nIndex, ::basegfx::B3DVector::getEmptyVector(), nCount); + } + + if(mpTextureCoordiantes) + { + mpTextureCoordiantes->insert(nIndex, ::basegfx::B2DPoint::getEmptyPoint(), nCount); + } + } + } + + const ::basegfx::BColor& getBColor(sal_uInt32 nIndex) const + { + if(mpBColors) + { + return mpBColors->getBColor(nIndex); + } + else + { + return ::basegfx::BColor::getEmptyBColor(); + } + } + + void setBColor(sal_uInt32 nIndex, const ::basegfx::BColor& rValue) + { + if(!mpBColors) + { + if(!rValue.equalZero()) + { + mpBColors = new BColorArray(maPoints.count()); + mpBColors->setBColor(nIndex, rValue); + } + } + else + { + mpBColors->setBColor(nIndex, rValue); + + if(!mpBColors->isUsed()) + { + delete mpBColors; + mpBColors = 0L; + } + } + } + + bool areBColorsUsed() const + { + return (mpBColors && mpBColors->isUsed()); + } + + void clearBColors() + { + if(mpBColors) + { + delete mpBColors; + mpBColors = 0L; + } + } + + const ::basegfx::B3DVector& getNormal() const + { + if(!mbPlaneNormalValid) + { + const_cast< ImplB3DPolygon* >(this)->maPlaneNormal = maPoints.getNormal(); + const_cast< ImplB3DPolygon* >(this)->mbPlaneNormalValid = true; + } + + return maPlaneNormal; + } + + const ::basegfx::B3DVector& getNormal(sal_uInt32 nIndex) const + { + if(mpNormals) + { + return mpNormals->getNormal(nIndex); + } + else + { + return ::basegfx::B3DVector::getEmptyVector(); + } + } + + void setNormal(sal_uInt32 nIndex, const ::basegfx::B3DVector& rValue) + { + if(!mpNormals) + { + if(!rValue.equalZero()) + { + mpNormals = new NormalsArray3D(maPoints.count()); + mpNormals->setNormal(nIndex, rValue); + } + } + else + { + mpNormals->setNormal(nIndex, rValue); + + if(!mpNormals->isUsed()) + { + delete mpNormals; + mpNormals = 0L; + } + } + } + + void transformNormals(const ::basegfx::B3DHomMatrix& rMatrix) + { + if(mpNormals) + { + mpNormals->transform(rMatrix); + } + } + + bool areNormalsUsed() const + { + return (mpNormals && mpNormals->isUsed()); + } + + void clearNormals() + { + if(mpNormals) + { + delete mpNormals; + mpNormals = 0L; + } + } + + const ::basegfx::B2DPoint& getTextureCoordinate(sal_uInt32 nIndex) const + { + if(mpTextureCoordiantes) + { + return mpTextureCoordiantes->getTextureCoordinate(nIndex); + } + else + { + return ::basegfx::B2DPoint::getEmptyPoint(); + } + } + + void setTextureCoordinate(sal_uInt32 nIndex, const ::basegfx::B2DPoint& rValue) + { + if(!mpTextureCoordiantes) + { + if(!rValue.equalZero()) + { + mpTextureCoordiantes = new TextureCoordinate2D(maPoints.count()); + mpTextureCoordiantes->setTextureCoordinate(nIndex, rValue); + } + } + else + { + mpTextureCoordiantes->setTextureCoordinate(nIndex, rValue); + + if(!mpTextureCoordiantes->isUsed()) + { + delete mpTextureCoordiantes; + mpTextureCoordiantes = 0L; + } + } + } + + bool areTextureCoordinatesUsed() const + { + return (mpTextureCoordiantes && mpTextureCoordiantes->isUsed()); + } + + void clearTextureCoordinates() + { + if(mpTextureCoordiantes) + { + delete mpTextureCoordiantes; + mpTextureCoordiantes = 0L; + } + } + + void transformTextureCoordinates(const ::basegfx::B2DHomMatrix& rMatrix) + { + if(mpTextureCoordiantes) + { + mpTextureCoordiantes->transform(rMatrix); + } + } + + void insert(sal_uInt32 nIndex, const ImplB3DPolygon& rSource) + { + const sal_uInt32 nCount(rSource.maPoints.count()); + + if(nCount) + { + maPoints.insert(nIndex, rSource.maPoints); + invalidatePlaneNormal(); + + if(rSource.mpBColors && rSource.mpBColors->isUsed()) + { + if(!mpBColors) + { + mpBColors = new BColorArray(maPoints.count()); + } + + mpBColors->insert(nIndex, *rSource.mpBColors); + } + else + { + if(mpBColors) + { + mpBColors->insert(nIndex, ::basegfx::BColor::getEmptyBColor(), nCount); + } + } + + if(rSource.mpNormals && rSource.mpNormals->isUsed()) + { + if(!mpNormals) + { + mpNormals = new NormalsArray3D(maPoints.count()); + } + + mpNormals->insert(nIndex, *rSource.mpNormals); + } + else + { + if(mpNormals) + { + mpNormals->insert(nIndex, ::basegfx::B3DVector::getEmptyVector(), nCount); + } + } + + if(rSource.mpTextureCoordiantes && rSource.mpTextureCoordiantes->isUsed()) + { + if(!mpTextureCoordiantes) + { + mpTextureCoordiantes = new TextureCoordinate2D(maPoints.count()); + } + + mpTextureCoordiantes->insert(nIndex, *rSource.mpTextureCoordiantes); + } + else + { + if(mpTextureCoordiantes) + { + mpTextureCoordiantes->insert(nIndex, ::basegfx::B2DPoint::getEmptyPoint(), nCount); + } + } + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + maPoints.remove(nIndex, nCount); + invalidatePlaneNormal(); + + if(mpBColors) + { + mpBColors->remove(nIndex, nCount); + + if(!mpBColors->isUsed()) + { + delete mpBColors; + mpBColors = 0L; + } + } + + if(mpNormals) + { + mpNormals->remove(nIndex, nCount); + + if(!mpNormals->isUsed()) + { + delete mpNormals; + mpNormals = 0L; + } + } + + if(mpTextureCoordiantes) + { + mpTextureCoordiantes->remove(nIndex, nCount); + + if(!mpTextureCoordiantes->isUsed()) + { + delete mpTextureCoordiantes; + mpTextureCoordiantes = 0L; + } + } + } + } + + void flip() + { + if(maPoints.count() > 1) + { + maPoints.flip(); + + if(mbPlaneNormalValid) + { + // mirror plane normal + maPlaneNormal = -maPlaneNormal; + } + + if(mpBColors) + { + mpBColors->flip(); + } + + if(mpNormals) + { + mpNormals->flip(); + } + + if(mpTextureCoordiantes) + { + mpTextureCoordiantes->flip(); + } + } + } + + bool hasDoublePoints() const + { + if(mbIsClosed) + { + // check for same start and end point + const sal_uInt32 nIndex(maPoints.count() - 1L); + + if(maPoints.getCoordinate(0L) == maPoints.getCoordinate(nIndex)) + { + const bool bBColorEqual(!mpBColors || (mpBColors->getBColor(0L) == mpBColors->getBColor(nIndex))); + + if(bBColorEqual) + { + const bool bNormalsEqual(!mpNormals || (mpNormals->getNormal(0L) == mpNormals->getNormal(nIndex))); + + if(bNormalsEqual) + { + const bool bTextureCoordinatesEqual(!mpTextureCoordiantes || (mpTextureCoordiantes->getTextureCoordinate(0L) == mpTextureCoordiantes->getTextureCoordinate(nIndex))); + + if(bTextureCoordinatesEqual) + { + return true; + } + } + } + } + } + + // test for range + for(sal_uInt32 a(0L); a < maPoints.count() - 1L; a++) + { + if(maPoints.getCoordinate(a) == maPoints.getCoordinate(a + 1L)) + { + const bool bBColorEqual(!mpBColors || (mpBColors->getBColor(a) == mpBColors->getBColor(a + 1L))); + + if(bBColorEqual) + { + const bool bNormalsEqual(!mpNormals || (mpNormals->getNormal(a) == mpNormals->getNormal(a + 1L))); + + if(bNormalsEqual) + { + const bool bTextureCoordinatesEqual(!mpTextureCoordiantes || (mpTextureCoordiantes->getTextureCoordinate(a) == mpTextureCoordiantes->getTextureCoordinate(a + 1L))); + + if(bTextureCoordinatesEqual) + { + return true; + } + } + } + } + } + + return false; + } + + void removeDoublePointsAtBeginEnd() + { + // Only remove DoublePoints at Begin and End when poly is closed + if(mbIsClosed) + { + bool bRemove; + + do + { + bRemove = false; + + if(maPoints.count() > 1L) + { + const sal_uInt32 nIndex(maPoints.count() - 1L); + bRemove = (maPoints.getCoordinate(0L) == maPoints.getCoordinate(nIndex)); + + if(bRemove && mpBColors && !(mpBColors->getBColor(0L) == mpBColors->getBColor(nIndex))) + { + bRemove = false; + } + + if(bRemove && mpNormals && !(mpNormals->getNormal(0L) == mpNormals->getNormal(nIndex))) + { + bRemove = false; + } + + if(bRemove && mpTextureCoordiantes && !(mpTextureCoordiantes->getTextureCoordinate(0L) == mpTextureCoordiantes->getTextureCoordinate(nIndex))) + { + bRemove = false; + } + } + + if(bRemove) + { + const sal_uInt32 nIndex(maPoints.count() - 1L); + remove(nIndex, 1L); + } + } while(bRemove); + } + } + + void removeDoublePointsWholeTrack() + { + sal_uInt32 nIndex(0L); + + // test as long as there are at least two points and as long as the index + // is smaller or equal second last point + while((maPoints.count() > 1L) && (nIndex <= maPoints.count() - 2L)) + { + const sal_uInt32 nNextIndex(nIndex + 1L); + bool bRemove(maPoints.getCoordinate(nIndex) == maPoints.getCoordinate(nNextIndex)); + + if(bRemove && mpBColors && !(mpBColors->getBColor(nIndex) == mpBColors->getBColor(nNextIndex))) + { + bRemove = false; + } + + if(bRemove && mpNormals && !(mpNormals->getNormal(nIndex) == mpNormals->getNormal(nNextIndex))) + { + bRemove = false; + } + + if(bRemove && mpTextureCoordiantes && !(mpTextureCoordiantes->getTextureCoordinate(nIndex) == mpTextureCoordiantes->getTextureCoordinate(nNextIndex))) + { + bRemove = false; + } + + if(bRemove) + { + // if next is same as index and the control vectors are unused, delete index + remove(nIndex, 1L); + } + else + { + // if different, step forward + nIndex++; + } + } + } + + void transform(const ::basegfx::B3DHomMatrix& rMatrix) + { + maPoints.transform(rMatrix); + + // Here, it seems to be possible to transform a valid plane normal and to avoid + // invalidation, but it's not true. If the transformation contains shears or e.g. + // perspective projection, the orthogonality to the transformed plane will not + // be preserved. It may be possible to test that at the matrix to not invalidate in + // all cases or to extract a matrix which does not 'shear' the vector which is + // a normal in this case. As long as this is not sure, i will just invalidate. + invalidatePlaneNormal(); + } +}; + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace { struct DefaultPolygon : public rtl::Static< B3DPolygon::ImplType, + DefaultPolygon > {}; } + + B3DPolygon::B3DPolygon() : + mpPolygon(DefaultPolygon::get()) + { + } + + B3DPolygon::B3DPolygon(const B3DPolygon& rPolygon) : + mpPolygon(rPolygon.mpPolygon) + { + } + + B3DPolygon::B3DPolygon(const B3DPolygon& rPolygon, sal_uInt32 nIndex, sal_uInt32 nCount) : + mpPolygon(ImplB3DPolygon(*rPolygon.mpPolygon, nIndex, nCount)) + { + // TODO(P2): one extra temporary here (cow_wrapper copies + // given ImplB3DPolygon into its internal impl_t wrapper type) + OSL_ENSURE(nIndex + nCount > rPolygon.mpPolygon->count(), "B3DPolygon constructor outside range (!)"); + } + + B3DPolygon::~B3DPolygon() + { + } + + B3DPolygon& B3DPolygon::operator=(const B3DPolygon& rPolygon) + { + mpPolygon = rPolygon.mpPolygon; + return *this; + } + + void B3DPolygon::makeUnique() + { + mpPolygon.make_unique(); + } + + bool B3DPolygon::operator==(const B3DPolygon& rPolygon) const + { + if(mpPolygon.same_object(rPolygon.mpPolygon)) + return true; + + return (*mpPolygon == *rPolygon.mpPolygon); + } + + bool B3DPolygon::operator!=(const B3DPolygon& rPolygon) const + { + return !(*this == rPolygon); + } + + sal_uInt32 B3DPolygon::count() const + { + return mpPolygon->count(); + } + + basegfx::B3DPoint B3DPolygon::getB3DPoint(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)"); + + return mpPolygon->getPoint(nIndex); + } + + void B3DPolygon::setB3DPoint(sal_uInt32 nIndex, const basegfx::B3DPoint& rValue) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)"); + + if(getB3DPoint(nIndex) != rValue) + mpPolygon->setPoint(nIndex, rValue); + } + + BColor B3DPolygon::getBColor(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)"); + + return mpPolygon->getBColor(nIndex); + } + + void B3DPolygon::setBColor(sal_uInt32 nIndex, const BColor& rValue) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)"); + + if(mpPolygon->getBColor(nIndex) != rValue) + mpPolygon->setBColor(nIndex, rValue); + } + + bool B3DPolygon::areBColorsUsed() const + { + return mpPolygon->areBColorsUsed(); + } + + void B3DPolygon::clearBColors() + { + if(mpPolygon->areBColorsUsed()) + mpPolygon->clearBColors(); + } + + B3DVector B3DPolygon::getNormal() const + { + return mpPolygon->getNormal(); + } + + B3DVector B3DPolygon::getNormal(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)"); + + return mpPolygon->getNormal(nIndex); + } + + void B3DPolygon::setNormal(sal_uInt32 nIndex, const B3DVector& rValue) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)"); + + if(mpPolygon->getNormal(nIndex) != rValue) + mpPolygon->setNormal(nIndex, rValue); + } + + void B3DPolygon::transformNormals(const B3DHomMatrix& rMatrix) + { + if(mpPolygon->areNormalsUsed() && !rMatrix.isIdentity()) + mpPolygon->transformNormals(rMatrix); + } + + bool B3DPolygon::areNormalsUsed() const + { + return mpPolygon->areNormalsUsed(); + } + + void B3DPolygon::clearNormals() + { + if(mpPolygon->areNormalsUsed()) + mpPolygon->clearNormals(); + } + + B2DPoint B3DPolygon::getTextureCoordinate(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)"); + + return mpPolygon->getTextureCoordinate(nIndex); + } + + void B3DPolygon::setTextureCoordinate(sal_uInt32 nIndex, const B2DPoint& rValue) + { + OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)"); + + if(mpPolygon->getTextureCoordinate(nIndex) != rValue) + mpPolygon->setTextureCoordinate(nIndex, rValue); + } + + void B3DPolygon::transformTextureCoordiantes(const B2DHomMatrix& rMatrix) + { + if(mpPolygon->areTextureCoordinatesUsed() && !rMatrix.isIdentity()) + mpPolygon->transformTextureCoordinates(rMatrix); + } + + bool B3DPolygon::areTextureCoordinatesUsed() const + { + return mpPolygon->areTextureCoordinatesUsed(); + } + + void B3DPolygon::clearTextureCoordinates() + { + if(mpPolygon->areTextureCoordinatesUsed()) + mpPolygon->clearTextureCoordinates(); + } + + void B3DPolygon::insert(sal_uInt32 nIndex, const ::basegfx::B3DPoint& rPoint, sal_uInt32 nCount) + { + OSL_ENSURE(nIndex <= mpPolygon->count(), "B3DPolygon Insert outside range (!)"); + + if(nCount) + mpPolygon->insert(nIndex, rPoint, nCount); + } + + void B3DPolygon::append(const basegfx::B3DPoint& rPoint, sal_uInt32 nCount) + { + if(nCount) + mpPolygon->insert(mpPolygon->count(), rPoint, nCount); + } + + void B3DPolygon::insert(sal_uInt32 nIndex, const B3DPolygon& rPoly, sal_uInt32 nIndex2, sal_uInt32 nCount) + { + OSL_ENSURE(nIndex <= mpPolygon->count(), "B3DPolygon Insert outside range (!)"); + + if(rPoly.count()) + { + if(!nCount) + { + nCount = rPoly.count(); + } + + if(0L == nIndex2 && nCount == rPoly.count()) + { + mpPolygon->insert(nIndex, *rPoly.mpPolygon); + } + else + { + OSL_ENSURE(nIndex2 + nCount <= rPoly.mpPolygon->count(), "B3DPolygon Insert outside range (!)"); + ImplB3DPolygon aTempPoly(*rPoly.mpPolygon, nIndex2, nCount); + mpPolygon->insert(nIndex, aTempPoly); + } + } + } + + void B3DPolygon::append(const B3DPolygon& rPoly, sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(rPoly.count()) + { + if(!nCount) + { + nCount = rPoly.count(); + } + + if(0L == nIndex && nCount == rPoly.count()) + { + mpPolygon->insert(mpPolygon->count(), *rPoly.mpPolygon); + } + else + { + OSL_ENSURE(nIndex + nCount <= rPoly.mpPolygon->count(), "B3DPolygon Append outside range (!)"); + ImplB3DPolygon aTempPoly(*rPoly.mpPolygon, nIndex, nCount); + mpPolygon->insert(mpPolygon->count(), aTempPoly); + } + } + } + + void B3DPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + OSL_ENSURE(nIndex + nCount <= mpPolygon->count(), "B3DPolygon Remove outside range (!)"); + + if(nCount) + mpPolygon->remove(nIndex, nCount); + } + + void B3DPolygon::clear() + { + mpPolygon = DefaultPolygon::get(); + } + + bool B3DPolygon::isClosed() const + { + return mpPolygon->isClosed(); + } + + void B3DPolygon::setClosed(bool bNew) + { + if(isClosed() != bNew) + mpPolygon->setClosed(bNew); + } + + void B3DPolygon::flip() + { + if(count() > 1) + mpPolygon->flip(); + } + + bool B3DPolygon::hasDoublePoints() const + { + return (mpPolygon->count() > 1L && mpPolygon->hasDoublePoints()); + } + + void B3DPolygon::removeDoublePoints() + { + if(hasDoublePoints()) + { + mpPolygon->removeDoublePointsAtBeginEnd(); + mpPolygon->removeDoublePointsWholeTrack(); + } + } + + void B3DPolygon::transform(const basegfx::B3DHomMatrix& rMatrix) + { + if(mpPolygon->count() && !rMatrix.isIdentity()) + { + mpPolygon->transform(rMatrix); + } + } +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// +// eof diff --git a/basegfx/source/polygon/b3dpolygonclipper.cxx b/basegfx/source/polygon/b3dpolygonclipper.cxx new file mode 100644 index 000000000000..88ebf12dae7b --- /dev/null +++ b/basegfx/source/polygon/b3dpolygonclipper.cxx @@ -0,0 +1,574 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" + +#include <basegfx/polygon/b3dpolygonclipper.hxx> +#include <osl/diagnose.h> +#include <basegfx/polygon/b3dpolygontools.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <basegfx/polygon/b3dpolygontools.hxx> +#include <basegfx/range/b3drange.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/color/bcolor.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace + { + inline bool impIsInside(const B3DPoint& rCandidate, double fPlaneOffset, tools::B3DOrientation ePlaneOrthogonal) + { + if(tools::B3DORIENTATION_X == ePlaneOrthogonal) + { + return fTools::moreOrEqual(rCandidate.getX(), fPlaneOffset); + } + else if(tools::B3DORIENTATION_Y == ePlaneOrthogonal) + { + return fTools::moreOrEqual(rCandidate.getY(), fPlaneOffset); + } + else + { + return fTools::moreOrEqual(rCandidate.getZ(), fPlaneOffset); + } + } + + inline double impGetCut(const B3DPoint& rCurrent, const B3DPoint& rNext, double fPlaneOffset, tools::B3DOrientation ePlaneOrthogonal) + { + if(tools::B3DORIENTATION_X == ePlaneOrthogonal) + { + return ((fPlaneOffset - rCurrent.getX())/(rNext.getX() - rCurrent.getX())); + } + else if(tools::B3DORIENTATION_Y == ePlaneOrthogonal) + { + return ((fPlaneOffset - rCurrent.getY())/(rNext.getY() - rCurrent.getY())); + } + else + { + return ((fPlaneOffset - rCurrent.getZ())/(rNext.getZ() - rCurrent.getZ())); + } + } + + void impAppendCopy(B3DPolygon& rDest, const B3DPolygon& rSource, sal_uInt32 nIndex) + { + rDest.append(rSource.getB3DPoint(nIndex)); + + if(rSource.areBColorsUsed()) + { + rDest.setBColor(rDest.count() - 1L, rSource.getBColor(nIndex)); + } + + if(rSource.areNormalsUsed()) + { + rDest.setNormal(rDest.count() - 1L, rSource.getNormal(nIndex)); + } + + if(rSource.areTextureCoordinatesUsed()) + { + rDest.setTextureCoordinate(rDest.count() - 1L, rSource.getTextureCoordinate(nIndex)); + } + } + + void impAppendInterpolate(B3DPolygon& rDest, const B3DPolygon& rSource, sal_uInt32 nIndA, sal_uInt32 nIndB, double fCut) + { + const B3DPoint aCurrPoint(rSource.getB3DPoint(nIndA)); + const B3DPoint aNextPoint(rSource.getB3DPoint(nIndB)); + rDest.append(interpolate(aCurrPoint, aNextPoint, fCut)); + + if(rSource.areBColorsUsed()) + { + const BColor aCurrBColor(rSource.getBColor(nIndA)); + const BColor aNextBColor(rSource.getBColor(nIndB)); + rDest.setBColor(rDest.count() - 1L, interpolate(aCurrBColor, aNextBColor, fCut)); + } + + if(rSource.areNormalsUsed()) + { + const B3DVector aCurrVector(rSource.getNormal(nIndA)); + const B3DVector aNextVector(rSource.getNormal(nIndB)); + rDest.setNormal(rDest.count() - 1L, interpolate(aCurrVector, aNextVector, fCut)); + } + + if(rSource.areTextureCoordinatesUsed()) + { + const B2DPoint aCurrTxCo(rSource.getTextureCoordinate(nIndA)); + const B2DPoint aNextTxCo(rSource.getTextureCoordinate(nIndB)); + rDest.setTextureCoordinate(rDest.count() - 1L, interpolate(aCurrTxCo, aNextTxCo, fCut)); + } + } + } +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + B3DPolyPolygon clipPolygonOnOrthogonalPlane(const B3DPolygon& rCandidate, B3DOrientation ePlaneOrthogonal, bool bClipPositive, double fPlaneOffset, bool bStroke) + { + B3DPolyPolygon aRetval; + + if(rCandidate.count()) + { + const B3DRange aCandidateRange(getRange(rCandidate)); + + if(B3DORIENTATION_X == ePlaneOrthogonal && fTools::moreOrEqual(aCandidateRange.getMinX(), fPlaneOffset)) + { + // completely above and on the clip plane. + if(bClipPositive) + { + // add completely + aRetval.append(rCandidate); + } + } + else if(B3DORIENTATION_X == ePlaneOrthogonal && fTools::lessOrEqual(aCandidateRange.getMaxX(), fPlaneOffset)) + { + // completely below and on the clip plane. + if(!bClipPositive) + { + // add completely + aRetval.append(rCandidate); + } + } + else if(B3DORIENTATION_Y == ePlaneOrthogonal && fTools::moreOrEqual(aCandidateRange.getMinY(), fPlaneOffset)) + { + // completely above and on the clip plane. + if(bClipPositive) + { + // add completely + aRetval.append(rCandidate); + } + } + else if(B3DORIENTATION_Y == ePlaneOrthogonal && fTools::lessOrEqual(aCandidateRange.getMaxY(), fPlaneOffset)) + { + // completely below and on the clip plane. + if(!bClipPositive) + { + // add completely + aRetval.append(rCandidate); + } + } + else if(B3DORIENTATION_Z == ePlaneOrthogonal && fTools::moreOrEqual(aCandidateRange.getMinZ(), fPlaneOffset)) + { + // completely above and on the clip plane. + if(bClipPositive) + { + // add completely + aRetval.append(rCandidate); + } + } + else if(B3DORIENTATION_Z == ePlaneOrthogonal && fTools::lessOrEqual(aCandidateRange.getMaxZ(), fPlaneOffset)) + { + // completely below and on the clip plane. + if(!bClipPositive) + { + // add completely + aRetval.append(rCandidate); + } + } + else + { + // prepare loop(s) + B3DPolygon aNewPolygon; + B3DPoint aCurrent(rCandidate.getB3DPoint(0L)); + const sal_uInt32 nPointCount(rCandidate.count()); + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); + bool bCurrentInside(impIsInside(aCurrent, fPlaneOffset, ePlaneOrthogonal) == bClipPositive); + + if(bCurrentInside) + { + impAppendCopy(aNewPolygon, rCandidate, 0L); + } + + if(bStroke) + { + // open polygon, create clipped line snippets. + for(sal_uInt32 a(0L); a < nEdgeCount; a++) + { + // get next point data + const sal_uInt32 nNextIndex((a + 1L == nPointCount) ? 0L : a + 1L); + const B3DPoint aNext(rCandidate.getB3DPoint(nNextIndex)); + const bool bNextInside(impIsInside(aNext, fPlaneOffset, ePlaneOrthogonal) == bClipPositive); + + if(bCurrentInside != bNextInside) + { + // change inside/outside + if(bNextInside) + { + // entering, finish existing and start new line polygon + if(aNewPolygon.count() > 1L) + { + aRetval.append(aNewPolygon); + } + + aNewPolygon.clear(); + } + + // calculate and add cut point + const double fCut(impGetCut(aCurrent, aNext, fPlaneOffset, ePlaneOrthogonal)); + impAppendInterpolate(aNewPolygon, rCandidate, a, nNextIndex, fCut); + + // pepare next step + bCurrentInside = bNextInside; + } + + if(bNextInside) + { + impAppendCopy(aNewPolygon, rCandidate, nNextIndex); + } + + // pepare next step + aCurrent = aNext; + } + + if(aNewPolygon.count() > 1L) + { + aRetval.append(aNewPolygon); + } + } + else + { + // closed polygon, create single clipped closed polygon + for(sal_uInt32 a(0L); a < nEdgeCount; a++) + { + // get next point data, use offset + const sal_uInt32 nNextIndex((a + 1L == nPointCount) ? 0L : a + 1L); + const B3DPoint aNext(rCandidate.getB3DPoint(nNextIndex)); + const bool bNextInside(impIsInside(aNext, fPlaneOffset, ePlaneOrthogonal) == bClipPositive); + + if(bCurrentInside != bNextInside) + { + // calculate and add cut point + const double fCut(impGetCut(aCurrent, aNext, fPlaneOffset, ePlaneOrthogonal)); + impAppendInterpolate(aNewPolygon, rCandidate, a, nNextIndex, fCut); + + // pepare next step + bCurrentInside = bNextInside; + } + + if(bNextInside && nNextIndex) + { + impAppendCopy(aNewPolygon, rCandidate, nNextIndex); + } + + // pepare next step + aCurrent = aNext; + } + + if(aNewPolygon.count() > 2L) + { + aNewPolygon.setClosed(true); + aRetval.append(aNewPolygon); + } + } + } + } + + return aRetval; + } + + B3DPolyPolygon clipPolyPolygonOnOrthogonalPlane(const B3DPolyPolygon& rCandidate, B3DOrientation ePlaneOrthogonal, bool bClipPositive, double fPlaneOffset, bool bStroke) + { + B3DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(clipPolygonOnOrthogonalPlane(rCandidate.getB3DPolygon(a), ePlaneOrthogonal, bClipPositive, fPlaneOffset, bStroke)); + } + + return aRetval; + } + + B3DPolyPolygon clipPolyPolygonOnRange(const B3DPolyPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke) + { + B3DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(clipPolygonOnRange(rCandidate.getB3DPolygon(a), rRange, bInside, bStroke)); + } + + return aRetval; + } + + B3DPolyPolygon clipPolygonOnRange(const B3DPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke) + { + B3DPolyPolygon aRetval; + + if(rRange.isEmpty()) + { + // clipping against an empty range. Nothing is inside an empty range, so the polygon + // is outside the range. So only return if not inside is wanted + if(!bInside && rCandidate.count()) + { + aRetval.append(rCandidate); + } + } + else if(rCandidate.count()) + { + const B3DRange aCandidateRange3D(getRange(rCandidate)); + const B2DRange aCandidateRange( + aCandidateRange3D.getMinX(), aCandidateRange3D.getMinY(), + aCandidateRange3D.getMaxX(), aCandidateRange3D.getMaxY()); + + if(rRange.isInside(aCandidateRange)) + { + // candidate is completely inside given range, nothing to do. Is also true with curves. + if(bInside) + { + aRetval.append(rCandidate); + } + } + else if(!rRange.overlaps(aCandidateRange)) + { + // candidate is completely outside given range, nothing to do. Is also true with curves. + if(!bInside) + { + aRetval.append(rCandidate); + } + } + else + { + // clip against the six planes of the range + // against lower X + aRetval = clipPolygonOnOrthogonalPlane(rCandidate, tools::B3DORIENTATION_X, bInside, rRange.getMinX(), bStroke); + + if(aRetval.count()) + { + // against lower Y + if(1L == aRetval.count()) + { + aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Y, bInside, rRange.getMinY(), bStroke); + } + else + { + aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Y, bInside, rRange.getMinY(), bStroke); + } + + if(aRetval.count()) + { + // against higher X + if(1L == aRetval.count()) + { + aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_X, !bInside, rRange.getMaxX(), bStroke); + } + else + { + aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_X, !bInside, rRange.getMaxX(), bStroke); + } + + if(aRetval.count()) + { + // against higher Y + if(1L == aRetval.count()) + { + aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Y, !bInside, rRange.getMaxY(), bStroke); + } + else + { + aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Y, !bInside, rRange.getMaxY(), bStroke); + } + } + } + } + } + } + + return aRetval; + } + + B3DPolyPolygon clipPolyPolygonOnRange(const B3DPolyPolygon& rCandidate, const B3DRange& rRange, bool bInside, bool bStroke) + { + B3DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(clipPolygonOnRange(rCandidate.getB3DPolygon(a), rRange, bInside, bStroke)); + } + + return aRetval; + } + + B3DPolyPolygon clipPolygonOnRange(const B3DPolygon& rCandidate, const B3DRange& rRange, bool bInside, bool bStroke) + { + B3DPolyPolygon aRetval; + + if(rRange.isEmpty()) + { + // clipping against an empty range. Nothing is inside an empty range, so the polygon + // is outside the range. So only return if not inside is wanted + if(!bInside && rCandidate.count()) + { + aRetval.append(rCandidate); + } + } + else if(rCandidate.count()) + { + const B3DRange aCandidateRange(getRange(rCandidate)); + + if(rRange.isInside(aCandidateRange)) + { + // candidate is completely inside given range, nothing to do. Is also true with curves. + if(bInside) + { + aRetval.append(rCandidate); + } + } + else if(!rRange.overlaps(aCandidateRange)) + { + // candidate is completely outside given range, nothing to do. Is also true with curves. + if(!bInside) + { + aRetval.append(rCandidate); + } + } + else + { + // clip against X,Y first and see if there's something left + const B2DRange aCandidateRange2D(rRange.getMinX(), rRange.getMinY(), rRange.getMaxX(), rRange.getMaxY()); + aRetval = clipPolygonOnRange(rCandidate, aCandidateRange2D, bInside, bStroke); + + if(aRetval.count()) + { + // against lower Z + if(1L == aRetval.count()) + { + aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Z, bInside, rRange.getMinZ(), bStroke); + } + else + { + aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Z, bInside, rRange.getMinZ(), bStroke); + } + + if(aRetval.count()) + { + // against higher Z + if(1L == aRetval.count()) + { + aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Z, !bInside, rRange.getMaxZ(), bStroke); + } + else + { + aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Z, !bInside, rRange.getMaxZ(), bStroke); + } + } + } + } + } + + return aRetval; + } + + B3DPolyPolygon clipPolygonOnPlane(const B3DPolygon& rCandidate, const B3DPoint& rPointOnPlane, const B3DVector& rPlaneNormal, bool bClipPositive, bool bStroke) + { + B3DPolyPolygon aRetval; + + if(rPlaneNormal.equalZero()) + { + // not really a plane definition, return polygon + aRetval.append(rCandidate); + } + else if(rCandidate.count()) + { + // build transform to project planeNormal on X-Axis and pointOnPlane to null point + B3DHomMatrix aMatrixTransform; + aMatrixTransform.translate(-rPointOnPlane.getX(), -rPointOnPlane.getY(), -rPointOnPlane.getZ()); + const double fRotInXY(atan2(rPlaneNormal.getY(), rPlaneNormal.getX())); + const double fRotInXZ(atan2(-rPlaneNormal.getZ(), rPlaneNormal.getXYLength())); + if(!fTools::equalZero(fRotInXY) || !fTools::equalZero(fRotInXZ)) + { + aMatrixTransform.rotate(0.0, fRotInXZ, fRotInXY); + } + + // transform polygon to clip scenario + B3DPolygon aCandidate(rCandidate); + aCandidate.transform(aMatrixTransform); + + // clip on YZ plane + aRetval = clipPolygonOnOrthogonalPlane(aCandidate, tools::B3DORIENTATION_X, bClipPositive, 0.0, bStroke); + + if(aRetval.count()) + { + // if there is a result, it needs to be transformed back + aMatrixTransform.invert(); + aRetval.transform(aMatrixTransform); + } + } + + return aRetval; + } + + B3DPolyPolygon clipPolyPolygonOnPlane(const B3DPolyPolygon& rCandidate, const B3DPoint& rPointOnPlane, const B3DVector& rPlaneNormal, bool bClipPositive, bool bStroke) + { + B3DPolyPolygon aRetval; + + if(rPlaneNormal.equalZero()) + { + // not really a plane definition, return polygon + aRetval = rCandidate; + } + else if(rCandidate.count()) + { + // build transform to project planeNormal on X-Axis and pointOnPlane to null point + B3DHomMatrix aMatrixTransform; + aMatrixTransform.translate(-rPointOnPlane.getX(), -rPointOnPlane.getY(), -rPointOnPlane.getZ()); + const double fRotInXY(atan2(rPlaneNormal.getY(), rPlaneNormal.getX())); + const double fRotInXZ(atan2(-rPlaneNormal.getZ(), rPlaneNormal.getXYLength())); + if(!fTools::equalZero(fRotInXY) || !fTools::equalZero(fRotInXZ)) + { + aMatrixTransform.rotate(0.0, fRotInXZ, fRotInXY); + } + + // transform polygon to clip scenario + aRetval = rCandidate; + aRetval.transform(aMatrixTransform); + + // clip on YZ plane + aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_X, bClipPositive, 0.0, bStroke); + + if(aRetval.count()) + { + // if there is a result, it needs to be transformed back + aMatrixTransform.invert(); + aRetval.transform(aMatrixTransform); + } + } + + return aRetval; + } + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +// eof diff --git a/basegfx/source/polygon/b3dpolygontools.cxx b/basegfx/source/polygon/b3dpolygontools.cxx new file mode 100644 index 000000000000..77bbbd379d3c --- /dev/null +++ b/basegfx/source/polygon/b3dpolygontools.cxx @@ -0,0 +1,1263 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <osl/diagnose.h> +#include <basegfx/polygon/b3dpolygontools.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/range/b3drange.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/tuple/b3ituple.hxx> +#include <numeric> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + // B3DPolygon tools + void checkClosed(B3DPolygon& rCandidate) + { + while(rCandidate.count() > 1L + && rCandidate.getB3DPoint(0L).equal(rCandidate.getB3DPoint(rCandidate.count() - 1L))) + { + rCandidate.setClosed(true); + rCandidate.remove(rCandidate.count() - 1L); + } + } + + // Get successor and predecessor indices. Returning the same index means there + // is none. Same for successor. + sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B3DPolygon& rCandidate) + { + OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)"); + + if(nIndex) + { + return nIndex - 1L; + } + else if(rCandidate.count()) + { + return rCandidate.count() - 1L; + } + else + { + return nIndex; + } + } + + sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B3DPolygon& rCandidate) + { + OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)"); + + if(nIndex + 1L < rCandidate.count()) + { + return nIndex + 1L; + } + else + { + return 0L; + } + } + + B3DRange getRange(const B3DPolygon& rCandidate) + { + B3DRange aRetval; + const sal_uInt32 nPointCount(rCandidate.count()); + + for(sal_uInt32 a(0L); a < nPointCount; a++) + { + const B3DPoint aTestPoint(rCandidate.getB3DPoint(a)); + aRetval.expand(aTestPoint); + } + + return aRetval; + } + + B3DVector getNormal(const B3DPolygon& rCandidate) + { + return rCandidate.getNormal(); + } + + B3DVector getPositiveOrientedNormal(const B3DPolygon& rCandidate) + { + B3DVector aRetval(rCandidate.getNormal()); + + if(ORIENTATION_NEGATIVE == getOrientation(rCandidate)) + { + aRetval = -aRetval; + } + + return aRetval; + } + + B2VectorOrientation getOrientation(const B3DPolygon& rCandidate) + { + B2VectorOrientation eRetval(ORIENTATION_NEUTRAL); + + if(rCandidate.count() > 2L) + { + const double fSignedArea(getSignedArea(rCandidate)); + + if(fSignedArea > 0.0) + { + eRetval = ORIENTATION_POSITIVE; + } + else if(fSignedArea < 0.0) + { + eRetval = ORIENTATION_NEGATIVE; + } + } + + return eRetval; + } + + double getSignedArea(const B3DPolygon& rCandidate) + { + double fRetval(0.0); + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount > 2) + { + const B3DVector aAbsNormal(absolute(getNormal(rCandidate))); + sal_uInt16 nCase(3); // default: ignore z + + if(aAbsNormal.getX() > aAbsNormal.getY()) + { + if(aAbsNormal.getX() > aAbsNormal.getZ()) + { + nCase = 1; // ignore x + } + } + else if(aAbsNormal.getY() > aAbsNormal.getZ()) + { + nCase = 2; // ignore y + } + + B3DPoint aPreviousPoint(rCandidate.getB3DPoint(nPointCount - 1L)); + + for(sal_uInt32 a(0L); a < nPointCount; a++) + { + const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(a)); + + switch(nCase) + { + case 1: // ignore x + fRetval += aPreviousPoint.getZ() * aCurrentPoint.getY(); + fRetval -= aPreviousPoint.getY() * aCurrentPoint.getZ(); + break; + case 2: // ignore y + fRetval += aPreviousPoint.getX() * aCurrentPoint.getZ(); + fRetval -= aPreviousPoint.getZ() * aCurrentPoint.getX(); + break; + case 3: // ignore z + fRetval += aPreviousPoint.getX() * aCurrentPoint.getY(); + fRetval -= aPreviousPoint.getY() * aCurrentPoint.getX(); + break; + } + + // prepare next step + aPreviousPoint = aCurrentPoint; + } + + switch(nCase) + { + case 1: // ignore x + fRetval /= 2.0 * aAbsNormal.getX(); + break; + case 2: // ignore y + fRetval /= 2.0 * aAbsNormal.getY(); + break; + case 3: // ignore z + fRetval /= 2.0 * aAbsNormal.getZ(); + break; + } + } + + return fRetval; + } + + double getArea(const B3DPolygon& rCandidate) + { + double fRetval(0.0); + + if(rCandidate.count() > 2) + { + fRetval = getSignedArea(rCandidate); + const double fZero(0.0); + + if(fTools::less(fRetval, fZero)) + { + fRetval = -fRetval; + } + } + + return fRetval; + } + + double getEdgeLength(const B3DPolygon& rCandidate, sal_uInt32 nIndex) + { + OSL_ENSURE(nIndex < rCandidate.count(), "getEdgeLength: Access to polygon out of range (!)"); + double fRetval(0.0); + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nIndex < nPointCount) + { + if(rCandidate.isClosed() || ((nIndex + 1L) != nPointCount)) + { + const sal_uInt32 nNextIndex(getIndexOfSuccessor(nIndex, rCandidate)); + const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(nIndex)); + const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex)); + const B3DVector aVector(aNextPoint - aCurrentPoint); + fRetval = aVector.getLength(); + } + } + + return fRetval; + } + + double getLength(const B3DPolygon& rCandidate) + { + double fRetval(0.0); + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount > 1L) + { + const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); + + for(sal_uInt32 a(0L); a < nLoopCount; a++) + { + const sal_uInt32 nNextIndex(getIndexOfSuccessor(a, rCandidate)); + const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(a)); + const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex)); + const B3DVector aVector(aNextPoint - aCurrentPoint); + fRetval += aVector.getLength(); + } + } + + return fRetval; + } + + B3DPoint getPositionAbsolute(const B3DPolygon& rCandidate, double fDistance, double fLength) + { + B3DPoint aRetval; + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount > 1L) + { + sal_uInt32 nIndex(0L); + bool bIndexDone(false); + const double fZero(0.0); + double fEdgeLength(fZero); + + // get length if not given + if(fTools::equalZero(fLength)) + { + fLength = getLength(rCandidate); + } + + // handle fDistance < 0.0 + if(fTools::less(fDistance, fZero)) + { + if(rCandidate.isClosed()) + { + // if fDistance < 0.0 increment with multiple of fLength + sal_uInt32 nCount(sal_uInt32(-fDistance / fLength)); + fDistance += double(nCount + 1L) * fLength; + } + else + { + // crop to polygon start + fDistance = fZero; + bIndexDone = true; + } + } + + // handle fDistance >= fLength + if(fTools::moreOrEqual(fDistance, fLength)) + { + if(rCandidate.isClosed()) + { + // if fDistance >= fLength decrement with multiple of fLength + sal_uInt32 nCount(sal_uInt32(fDistance / fLength)); + fDistance -= (double)(nCount) * fLength; + } + else + { + // crop to polygon end + fDistance = fZero; + nIndex = nPointCount - 1L; + bIndexDone = true; + } + } + + // look for correct index. fDistance is now [0.0 .. fLength[ + if(!bIndexDone) + { + do + { + // get length of next edge + fEdgeLength = getEdgeLength(rCandidate, nIndex); + + if(fTools::moreOrEqual(fDistance, fEdgeLength)) + { + // go to next edge + fDistance -= fEdgeLength; + nIndex++; + } + else + { + // it's on this edge, stop + bIndexDone = true; + } + } while (!bIndexDone); + } + + // get the point using nIndex + aRetval = rCandidate.getB3DPoint(nIndex); + + // if fDistance != 0.0, move that length on the edge. The edge + // length is in fEdgeLength. + if(!fTools::equalZero(fDistance)) + { + sal_uInt32 nNextIndex(getIndexOfSuccessor(nIndex, rCandidate)); + const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex)); + double fRelative(fZero); + + if(!fTools::equalZero(fEdgeLength)) + { + fRelative = fDistance / fEdgeLength; + } + + // add calculated average value to the return value + aRetval += interpolate(aRetval, aNextPoint, fRelative); + } + } + + return aRetval; + } + + B3DPoint getPositionRelative(const B3DPolygon& rCandidate, double fDistance, double fLength) + { + // get length if not given + if(fTools::equalZero(fLength)) + { + fLength = getLength(rCandidate); + } + + // multiply fDistance with real length to get absolute position and + // use getPositionAbsolute + return getPositionAbsolute(rCandidate, fDistance * fLength, fLength); + } + + void applyLineDashing(const B3DPolygon& rCandidate, const ::std::vector<double>& rDotDashArray, B3DPolyPolygon* pLineTarget, B3DPolyPolygon* pGapTarget, double fDotDashLength) + { + const sal_uInt32 nPointCount(rCandidate.count()); + const sal_uInt32 nDotDashCount(rDotDashArray.size()); + + if(fTools::lessOrEqual(fDotDashLength, 0.0)) + { + fDotDashLength = ::std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0); + } + + if(fTools::more(fDotDashLength, 0.0) && (pLineTarget || pGapTarget) && nPointCount) + { + // clear targets + if(pLineTarget) + { + pLineTarget->clear(); + } + + if(pGapTarget) + { + pGapTarget->clear(); + } + + // prepare current edge's start + B3DPoint aCurrentPoint(rCandidate.getB3DPoint(0)); + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1); + + // prepare DotDashArray iteration and the line/gap switching bool + sal_uInt32 nDotDashIndex(0); + bool bIsLine(true); + double fDotDashMovingLength(rDotDashArray[0]); + B3DPolygon aSnippet; + + // iterate over all edges + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + // update current edge + double fLastDotDashMovingLength(0.0); + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex)); + const double fEdgeLength(B3DVector(aNextPoint - aCurrentPoint).getLength()); + + while(fTools::less(fDotDashMovingLength, fEdgeLength)) + { + // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength] + const bool bHandleLine(bIsLine && pLineTarget); + const bool bHandleGap(!bIsLine && pGapTarget); + + if(bHandleLine || bHandleGap) + { + if(!aSnippet.count()) + { + aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fLastDotDashMovingLength / fEdgeLength)); + } + + aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fDotDashMovingLength / fEdgeLength)); + + if(bHandleLine) + { + pLineTarget->append(aSnippet); + } + else + { + pGapTarget->append(aSnippet); + } + + aSnippet.clear(); + } + + // prepare next DotDashArray step and flip line/gap flag + fLastDotDashMovingLength = fDotDashMovingLength; + fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount]; + bIsLine = !bIsLine; + } + + // append snippet [fLastDotDashMovingLength, fEdgeLength] + const bool bHandleLine(bIsLine && pLineTarget); + const bool bHandleGap(!bIsLine && pGapTarget); + + if(bHandleLine || bHandleGap) + { + if(!aSnippet.count()) + { + aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fLastDotDashMovingLength / fEdgeLength)); + } + + aSnippet.append(aNextPoint); + } + + // prepare move to next edge + fDotDashMovingLength -= fEdgeLength; + + // prepare next edge step (end point gets new start point) + aCurrentPoint = aNextPoint; + } + + // append last intermediate results (if exists) + if(aSnippet.count()) + { + if(bIsLine && pLineTarget) + { + pLineTarget->append(aSnippet); + } + else if(!bIsLine && pGapTarget) + { + pGapTarget->append(aSnippet); + } + } + + // check if start and end polygon may be merged + if(pLineTarget) + { + const sal_uInt32 nCount(pLineTarget->count()); + + if(nCount > 1) + { + // these polygons were created above, there exists none with less than two points, + // thus dircet point access below is allowed + const B3DPolygon aFirst(pLineTarget->getB3DPolygon(0)); + B3DPolygon aLast(pLineTarget->getB3DPolygon(nCount - 1)); + + if(aFirst.getB3DPoint(0).equal(aLast.getB3DPoint(aLast.count() - 1))) + { + // start of first and end of last are the same -> merge them + aLast.append(aFirst); + aLast.removeDoublePoints(); + pLineTarget->setB3DPolygon(0, aLast); + pLineTarget->remove(nCount - 1); + } + } + } + + if(pGapTarget) + { + const sal_uInt32 nCount(pGapTarget->count()); + + if(nCount > 1) + { + // these polygons were created above, there exists none with less than two points, + // thus dircet point access below is allowed + const B3DPolygon aFirst(pGapTarget->getB3DPolygon(0)); + B3DPolygon aLast(pGapTarget->getB3DPolygon(nCount - 1)); + + if(aFirst.getB3DPoint(0).equal(aLast.getB3DPoint(aLast.count() - 1))) + { + // start of first and end of last are the same -> merge them + aLast.append(aFirst); + aLast.removeDoublePoints(); + pGapTarget->setB3DPolygon(0, aLast); + pGapTarget->remove(nCount - 1); + } + } + } + } + else + { + // parameters make no sense, just add source to targets + if(pLineTarget) + { + pLineTarget->append(rCandidate); + } + + if(pGapTarget) + { + pGapTarget->append(rCandidate); + } + } + } + + B3DPolygon applyDefaultNormalsSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter) + { + B3DPolygon aRetval(rCandidate); + + for(sal_uInt32 a(0L); a < aRetval.count(); a++) + { + B3DVector aVector(aRetval.getB3DPoint(a) - rCenter); + aVector.normalize(); + aRetval.setNormal(a, aVector); + } + + return aRetval; + } + + B3DPolygon invertNormals( const B3DPolygon& rCandidate) + { + B3DPolygon aRetval(rCandidate); + + if(aRetval.areNormalsUsed()) + { + for(sal_uInt32 a(0L); a < aRetval.count(); a++) + { + aRetval.setNormal(a, -aRetval.getNormal(a)); + } + } + + return aRetval; + } + + B3DPolygon applyDefaultTextureCoordinatesParallel( const B3DPolygon& rCandidate, const B3DRange& rRange, bool bChangeX, bool bChangeY) + { + B3DPolygon aRetval(rCandidate); + + if(bChangeX || bChangeY) + { + // create projection of standard texture coordinates in (X, Y) onto + // the 3d coordinates straight + const double fWidth(rRange.getWidth()); + const double fHeight(rRange.getHeight()); + const bool bWidthSet(!fTools::equalZero(fWidth)); + const bool bHeightSet(!fTools::equalZero(fHeight)); + const double fOne(1.0); + + for(sal_uInt32 a(0L); a < aRetval.count(); a++) + { + const B3DPoint aPoint(aRetval.getB3DPoint(a)); + B2DPoint aTextureCoordinate(aRetval.getTextureCoordinate(a)); + + if(bChangeX) + { + if(bWidthSet) + { + aTextureCoordinate.setX((aPoint.getX() - rRange.getMinX()) / fWidth); + } + else + { + aTextureCoordinate.setX(0.0); + } + } + + if(bChangeY) + { + if(bHeightSet) + { + aTextureCoordinate.setY(fOne - ((aPoint.getY() - rRange.getMinY()) / fHeight)); + } + else + { + aTextureCoordinate.setY(fOne); + } + } + + aRetval.setTextureCoordinate(a, aTextureCoordinate); + } + } + + return aRetval; + } + + B3DPolygon applyDefaultTextureCoordinatesSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter, bool bChangeX, bool bChangeY) + { + B3DPolygon aRetval(rCandidate); + + if(bChangeX || bChangeY) + { + // create texture coordinates using sphere projection to cartesian coordinates, + // use object's center as base + const double fOne(1.0); + const sal_uInt32 nPointCount(aRetval.count()); + bool bPolarPoints(false); + sal_uInt32 a; + + // create center cartesian coordinates to have a possibility to decide if on boundary + // transitions which value to choose + const B3DRange aPlaneRange(getRange(rCandidate)); + const B3DPoint aPlaneCenter(aPlaneRange.getCenter() - rCenter); + const double fXCenter(fOne - ((atan2(aPlaneCenter.getZ(), aPlaneCenter.getX()) + F_PI) / F_2PI)); + + for(a = 0L; a < nPointCount; a++) + { + const B3DVector aVector(aRetval.getB3DPoint(a) - rCenter); + const double fY(fOne - ((atan2(aVector.getY(), aVector.getXZLength()) + F_PI2) / F_PI)); + B2DPoint aTexCoor(aRetval.getTextureCoordinate(a)); + + if(fTools::equalZero(fY)) + { + // point is a north polar point, no useful X-coordinate can be created. + if(bChangeY) + { + aTexCoor.setY(0.0); + + if(bChangeX) + { + bPolarPoints = true; + } + } + } + else if(fTools::equal(fY, fOne)) + { + // point is a south polar point, no useful X-coordinate can be created. Set + // Y-coordinte, though + if(bChangeY) + { + aTexCoor.setY(fOne); + + if(bChangeX) + { + bPolarPoints = true; + } + } + } + else + { + double fX(fOne - ((atan2(aVector.getZ(), aVector.getX()) + F_PI) / F_2PI)); + + // correct cartesinan point coordiante dependent from center value + if(fX > fXCenter + 0.5) + { + fX -= fOne; + } + else if(fX < fXCenter - 0.5) + { + fX += fOne; + } + + if(bChangeX) + { + aTexCoor.setX(fX); + } + + if(bChangeY) + { + aTexCoor.setY(fY); + } + } + + aRetval.setTextureCoordinate(a, aTexCoor); + } + + if(bPolarPoints) + { + // correct X-texture coordinates if polar points are contained. Those + // coordinates cannot be correct, so use prev or next X-coordinate + for(a = 0L; a < nPointCount; a++) + { + B2DPoint aTexCoor(aRetval.getTextureCoordinate(a)); + + if(fTools::equalZero(aTexCoor.getY()) || fTools::equal(aTexCoor.getY(), fOne)) + { + // get prev, next TexCoor and test for pole + const B2DPoint aPrevTexCoor(aRetval.getTextureCoordinate(a ? a - 1L : nPointCount - 1L)); + const B2DPoint aNextTexCoor(aRetval.getTextureCoordinate((a + 1L) % nPointCount)); + const bool bPrevPole(fTools::equalZero(aPrevTexCoor.getY()) || fTools::equal(aPrevTexCoor.getY(), fOne)); + const bool bNextPole(fTools::equalZero(aNextTexCoor.getY()) || fTools::equal(aNextTexCoor.getY(), fOne)); + + if(!bPrevPole && !bNextPole) + { + // both no poles, mix them + aTexCoor.setX((aPrevTexCoor.getX() + aNextTexCoor.getX()) / 2.0); + } + else if(!bNextPole) + { + // copy next + aTexCoor.setX(aNextTexCoor.getX()); + } + else + { + // copy prev, even if it's a pole, hopefully it is already corrected + aTexCoor.setX(aPrevTexCoor.getX()); + } + + aRetval.setTextureCoordinate(a, aTexCoor); + } + } + } + } + + return aRetval; + } + + bool isInEpsilonRange(const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, const B3DPoint& rTestPosition, double fDistance) + { + // build edge vector + const B3DVector aEdge(rEdgeEnd - rEdgeStart); + bool bDoDistanceTestStart(false); + bool bDoDistanceTestEnd(false); + + if(aEdge.equalZero()) + { + // no edge, just a point. Do one of the distance tests. + bDoDistanceTestStart = true; + } + else + { + // calculate fCut in aEdge + const B3DVector aTestEdge(rTestPosition - rEdgeStart); + const double fScalarTestEdge(aEdge.scalar(aTestEdge)); + const double fScalarStartEdge(aEdge.scalar(rEdgeStart)); + const double fScalarEdge(aEdge.scalar(aEdge)); + const double fCut((fScalarTestEdge - fScalarStartEdge) / fScalarEdge); + const double fZero(0.0); + const double fOne(1.0); + + if(fTools::less(fCut, fZero)) + { + // left of rEdgeStart + bDoDistanceTestStart = true; + } + else if(fTools::more(fCut, fOne)) + { + // right of rEdgeEnd + bDoDistanceTestEnd = true; + } + else + { + // inside line [0.0 .. 1.0] + const B3DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut)); + const B3DVector aDelta(rTestPosition - aCutPoint); + const double fDistanceSquare(aDelta.scalar(aDelta)); + + if(fDistanceSquare <= fDistance * fDistance * fDistance) + { + return true; + } + else + { + return false; + } + } + } + + if(bDoDistanceTestStart) + { + const B3DVector aDelta(rTestPosition - rEdgeStart); + const double fDistanceSquare(aDelta.scalar(aDelta)); + + if(fDistanceSquare <= fDistance * fDistance * fDistance) + { + return true; + } + } + else if(bDoDistanceTestEnd) + { + const B3DVector aDelta(rTestPosition - rEdgeEnd); + const double fDistanceSquare(aDelta.scalar(aDelta)); + + if(fDistanceSquare <= fDistance * fDistance * fDistance) + { + return true; + } + } + + return false; + } + + bool isInEpsilonRange(const B3DPolygon& rCandidate, const B3DPoint& rTestPosition, double fDistance) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount) + { + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); + B3DPoint aCurrent(rCandidate.getB3DPoint(0)); + + if(nEdgeCount) + { + // edges + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B3DPoint aNext(rCandidate.getB3DPoint(nNextIndex)); + + if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance)) + { + return true; + } + + // prepare next step + aCurrent = aNext; + } + } + else + { + // no edges, but points -> not closed. Check single point. Just + // use isInEpsilonRange with twice the same point, it handles those well + if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance)) + { + return true; + } + } + } + + return false; + } + + bool isInside(const B3DPolygon& rCandidate, const B3DPoint& rPoint, bool bWithBorder) + { + if(bWithBorder && isPointOnPolygon(rCandidate, rPoint, true)) + { + return true; + } + else + { + bool bRetval(false); + const B3DVector aPlaneNormal(rCandidate.getNormal()); + + if(!aPlaneNormal.equalZero()) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount) + { + B3DPoint aCurrentPoint(rCandidate.getB3DPoint(nPointCount - 1)); + const double fAbsX(fabs(aPlaneNormal.getX())); + const double fAbsY(fabs(aPlaneNormal.getY())); + const double fAbsZ(fabs(aPlaneNormal.getZ())); + + if(fAbsX > fAbsY && fAbsX > fAbsZ) + { + // normal points mostly in X-Direction, use YZ-Polygon projection for check + // x -> y, y -> z + for(sal_uInt32 a(0); a < nPointCount; a++) + { + const B3DPoint aPreviousPoint(aCurrentPoint); + aCurrentPoint = rCandidate.getB3DPoint(a); + + // cross-over in Z? + const bool bCompZA(fTools::more(aPreviousPoint.getZ(), rPoint.getZ())); + const bool bCompZB(fTools::more(aCurrentPoint.getZ(), rPoint.getZ())); + + if(bCompZA != bCompZB) + { + // cross-over in Y? + const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY())); + const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY())); + + if(bCompYA == bCompYB) + { + if(bCompYA) + { + bRetval = !bRetval; + } + } + else + { + const double fCompare( + aCurrentPoint.getY() - (aCurrentPoint.getZ() - rPoint.getZ()) * + (aPreviousPoint.getY() - aCurrentPoint.getY()) / + (aPreviousPoint.getZ() - aCurrentPoint.getZ())); + + if(fTools::more(fCompare, rPoint.getY())) + { + bRetval = !bRetval; + } + } + } + } + } + else if(fAbsY > fAbsX && fAbsY > fAbsZ) + { + // normal points mostly in Y-Direction, use XZ-Polygon projection for check + // x -> x, y -> z + for(sal_uInt32 a(0); a < nPointCount; a++) + { + const B3DPoint aPreviousPoint(aCurrentPoint); + aCurrentPoint = rCandidate.getB3DPoint(a); + + // cross-over in Z? + const bool bCompZA(fTools::more(aPreviousPoint.getZ(), rPoint.getZ())); + const bool bCompZB(fTools::more(aCurrentPoint.getZ(), rPoint.getZ())); + + if(bCompZA != bCompZB) + { + // cross-over in X? + const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX())); + const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX())); + + if(bCompXA == bCompXB) + { + if(bCompXA) + { + bRetval = !bRetval; + } + } + else + { + const double fCompare( + aCurrentPoint.getX() - (aCurrentPoint.getZ() - rPoint.getZ()) * + (aPreviousPoint.getX() - aCurrentPoint.getX()) / + (aPreviousPoint.getZ() - aCurrentPoint.getZ())); + + if(fTools::more(fCompare, rPoint.getX())) + { + bRetval = !bRetval; + } + } + } + } + } + else + { + // normal points mostly in Z-Direction, use XY-Polygon projection for check + // x -> x, y -> y + for(sal_uInt32 a(0); a < nPointCount; a++) + { + const B3DPoint aPreviousPoint(aCurrentPoint); + aCurrentPoint = rCandidate.getB3DPoint(a); + + // cross-over in Y? + const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY())); + const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY())); + + if(bCompYA != bCompYB) + { + // cross-over in X? + const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX())); + const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX())); + + if(bCompXA == bCompXB) + { + if(bCompXA) + { + bRetval = !bRetval; + } + } + else + { + const double fCompare( + aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) * + (aPreviousPoint.getX() - aCurrentPoint.getX()) / + (aPreviousPoint.getY() - aCurrentPoint.getY())); + + if(fTools::more(fCompare, rPoint.getX())) + { + bRetval = !bRetval; + } + } + } + } + } + } + } + + return bRetval; + } + } + + bool isInside(const B3DPolygon& rCandidate, const B3DPolygon& rPolygon, bool bWithBorder) + { + const sal_uInt32 nPointCount(rPolygon.count()); + + for(sal_uInt32 a(0L); a < nPointCount; a++) + { + const B3DPoint aTestPoint(rPolygon.getB3DPoint(a)); + + if(!isInside(rCandidate, aTestPoint, bWithBorder)) + { + return false; + } + } + + return true; + } + + bool isPointOnLine(const B3DPoint& rStart, const B3DPoint& rEnd, const B3DPoint& rCandidate, bool bWithPoints) + { + if(rCandidate.equal(rStart) || rCandidate.equal(rEnd)) + { + // candidate is in epsilon around start or end -> inside + return bWithPoints; + } + else if(rStart.equal(rEnd)) + { + // start and end are equal, but candidate is outside their epsilon -> outside + return false; + } + else + { + const B3DVector aEdgeVector(rEnd - rStart); + const B3DVector aTestVector(rCandidate - rStart); + + if(areParallel(aEdgeVector, aTestVector)) + { + const double fZero(0.0); + const double fOne(1.0); + double fParamTestOnCurr(0.0); + + if(aEdgeVector.getX() > aEdgeVector.getY()) + { + if(aEdgeVector.getX() > aEdgeVector.getZ()) + { + // X is biggest + fParamTestOnCurr = aTestVector.getX() / aEdgeVector.getX(); + } + else + { + // Z is biggest + fParamTestOnCurr = aTestVector.getZ() / aEdgeVector.getZ(); + } + } + else + { + if(aEdgeVector.getY() > aEdgeVector.getZ()) + { + // Y is biggest + fParamTestOnCurr = aTestVector.getY() / aEdgeVector.getY(); + } + else + { + // Z is biggest + fParamTestOnCurr = aTestVector.getZ() / aEdgeVector.getZ(); + } + } + + if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne)) + { + return true; + } + } + + return false; + } + } + + bool isPointOnPolygon(const B3DPolygon& rCandidate, const B3DPoint& rPoint, bool bWithPoints) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount > 1L) + { + const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); + B3DPoint aCurrentPoint(rCandidate.getB3DPoint(0)); + + for(sal_uInt32 a(0); a < nLoopCount; a++) + { + const B3DPoint aNextPoint(rCandidate.getB3DPoint((a + 1) % nPointCount)); + + if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints)) + { + return true; + } + + aCurrentPoint = aNextPoint; + } + } + else if(nPointCount && bWithPoints) + { + return rPoint.equal(rCandidate.getB3DPoint(0)); + } + + return false; + } + + bool getCutBetweenLineAndPlane(const B3DVector& rPlaneNormal, const B3DPoint& rPlanePoint, const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, double& fCut) + { + if(!rPlaneNormal.equalZero() && !rEdgeStart.equal(rEdgeEnd)) + { + const B3DVector aTestEdge(rEdgeEnd - rEdgeStart); + const double fScalarEdge(rPlaneNormal.scalar(aTestEdge)); + + if(!fTools::equalZero(fScalarEdge)) + { + const B3DVector aCompareEdge(rPlanePoint - rEdgeStart); + const double fScalarCompare(rPlaneNormal.scalar(aCompareEdge)); + + fCut = fScalarCompare / fScalarEdge; + return true; + } + } + + return false; + } + + bool getCutBetweenLineAndPolygon(const B3DPolygon& rCandidate, const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, double& fCut) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount > 2 && !rEdgeStart.equal(rEdgeEnd)) + { + const B3DVector aPlaneNormal(rCandidate.getNormal()); + + if(!aPlaneNormal.equalZero()) + { + const B3DPoint aPointOnPlane(rCandidate.getB3DPoint(0)); + + return getCutBetweenLineAndPlane(aPlaneNormal, aPointOnPlane, rEdgeStart, rEdgeEnd, fCut); + } + } + + return false; + } + + ////////////////////////////////////////////////////////////////////// + // comparators with tolerance for 3D Polygons + + bool equal(const B3DPolygon& rCandidateA, const B3DPolygon& rCandidateB, const double& rfSmallValue) + { + const sal_uInt32 nPointCount(rCandidateA.count()); + + if(nPointCount != rCandidateB.count()) + return false; + + const bool bClosed(rCandidateA.isClosed()); + + if(bClosed != rCandidateB.isClosed()) + return false; + + for(sal_uInt32 a(0); a < nPointCount; a++) + { + const B3DPoint aPoint(rCandidateA.getB3DPoint(a)); + + if(!aPoint.equal(rCandidateB.getB3DPoint(a), rfSmallValue)) + return false; + } + + return true; + } + + bool equal(const B3DPolygon& rCandidateA, const B3DPolygon& rCandidateB) + { + const double fSmallValue(fTools::getSmallValue()); + + return equal(rCandidateA, rCandidateB, fSmallValue); + } + + // snap points of horizontal or vertical edges to discrete values + B3DPolygon snapPointsOfHorizontalOrVerticalEdges(const B3DPolygon& rCandidate) + { + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount > 1) + { + // Start by copying the source polygon to get a writeable copy. The closed state is + // copied by aRetval's initialisation, too, so no need to copy it in this method + B3DPolygon aRetval(rCandidate); + + // prepare geometry data. Get rounded from original + B3ITuple aPrevTuple(basegfx::fround(rCandidate.getB3DPoint(nPointCount - 1))); + B3DPoint aCurrPoint(rCandidate.getB3DPoint(0)); + B3ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + + // loop over all points. This will also snap the implicit closing edge + // even when not closed, but that's no problem here + for(sal_uInt32 a(0); a < nPointCount; a++) + { + // get next point. Get rounded from original + const bool bLastRun(a + 1 == nPointCount); + const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1); + const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex)); + const B3ITuple aNextTuple(basegfx::fround(aNextPoint)); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if(bSnapX || bSnapY) + { + const B3DPoint aSnappedPoint( + bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY(), + aCurrPoint.getZ()); + + aRetval.setB3DPoint(a, aSnappedPoint); + } + + // prepare next point + if(!bLastRun) + { + aPrevTuple = aCurrTuple; + aCurrPoint = aNextPoint; + aCurrTuple = aNextTuple; + } + } + + return aRetval; + } + else + { + return rCandidate; + } + } + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +// eof diff --git a/basegfx/source/polygon/b3dpolypolygon.cxx b/basegfx/source/polygon/b3dpolypolygon.cxx new file mode 100644 index 000000000000..a29680b14a59 --- /dev/null +++ b/basegfx/source/polygon/b3dpolypolygon.cxx @@ -0,0 +1,446 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <osl/diagnose.h> +#include <basegfx/polygon/b3dpolypolygon.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <rtl/instance.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <functional> +#include <vector> +#include <algorithm> + +////////////////////////////////////////////////////////////////////////////// + +class ImplB3DPolyPolygon +{ + typedef ::std::vector< ::basegfx::B3DPolygon > PolygonVector; + + PolygonVector maPolygons; + +public: + ImplB3DPolyPolygon() : maPolygons() + { + } + + ImplB3DPolyPolygon(const ::basegfx::B3DPolygon& rToBeCopied) : + maPolygons(1,rToBeCopied) + { + } + + bool operator==(const ImplB3DPolyPolygon& rPolygonList) const + { + // same polygon count? + if(maPolygons.size() != rPolygonList.maPolygons.size()) + return false; + + // compare polygon content + if(maPolygons != rPolygonList.maPolygons) + return false; + + return true; + } + + const ::basegfx::B3DPolygon& getB3DPolygon(sal_uInt32 nIndex) const + { + return maPolygons[nIndex]; + } + + void setB3DPolygon(sal_uInt32 nIndex, const ::basegfx::B3DPolygon& rPolygon) + { + maPolygons[nIndex] = rPolygon; + } + + void insert(sal_uInt32 nIndex, const ::basegfx::B3DPolygon& rPolygon, sal_uInt32 nCount) + { + if(nCount) + { + // add nCount copies of rPolygon + PolygonVector::iterator aIndex(maPolygons.begin()); + aIndex += nIndex; + maPolygons.insert(aIndex, nCount, rPolygon); + } + } + + void insert(sal_uInt32 nIndex, const ::basegfx::B3DPolyPolygon& rPolyPolygon) + { + const sal_uInt32 nCount = rPolyPolygon.count(); + + if(nCount) + { + // add nCount polygons from rPolyPolygon + maPolygons.reserve(maPolygons.size() + nCount); + PolygonVector::iterator aIndex(maPolygons.begin()); + aIndex += nIndex; + + for(sal_uInt32 a(0L); a < nCount; a++) + { + maPolygons.insert(aIndex, rPolyPolygon.getB3DPolygon(a)); + aIndex++; + } + } + } + + void remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + if(nCount) + { + // remove polygon data + PolygonVector::iterator aStart(maPolygons.begin()); + aStart += nIndex; + const PolygonVector::iterator aEnd(aStart + nCount); + + maPolygons.erase(aStart, aEnd); + } + } + + sal_uInt32 count() const + { + return maPolygons.size(); + } + + void setClosed(bool bNew) + { + for(sal_uInt32 a(0L); a < maPolygons.size(); a++) + { + maPolygons[a].setClosed(bNew); + } + } + + void flip() + { + std::for_each( maPolygons.begin(), + maPolygons.end(), + std::mem_fun_ref( &::basegfx::B3DPolygon::flip )); + } + + void removeDoublePoints() + { + std::for_each( maPolygons.begin(), + maPolygons.end(), + std::mem_fun_ref( &::basegfx::B3DPolygon::removeDoublePoints )); + } + + void transform(const ::basegfx::B3DHomMatrix& rMatrix) + { + for(sal_uInt32 a(0L); a < maPolygons.size(); a++) + { + maPolygons[a].transform(rMatrix); + } + } + + void clearBColors() + { + for(sal_uInt32 a(0L); a < maPolygons.size(); a++) + { + maPolygons[a].clearBColors(); + } + } + + void transformNormals(const ::basegfx::B3DHomMatrix& rMatrix) + { + for(sal_uInt32 a(0L); a < maPolygons.size(); a++) + { + maPolygons[a].transformNormals(rMatrix); + } + } + + void clearNormals() + { + for(sal_uInt32 a(0L); a < maPolygons.size(); a++) + { + maPolygons[a].clearNormals(); + } + } + + void transformTextureCoordiantes(const ::basegfx::B2DHomMatrix& rMatrix) + { + for(sal_uInt32 a(0L); a < maPolygons.size(); a++) + { + maPolygons[a].transformTextureCoordiantes(rMatrix); + } + } + + void clearTextureCoordinates() + { + for(sal_uInt32 a(0L); a < maPolygons.size(); a++) + { + maPolygons[a].clearTextureCoordinates(); + } + } + + void makeUnique() + { + std::for_each( maPolygons.begin(), + maPolygons.end(), + std::mem_fun_ref( &::basegfx::B3DPolygon::makeUnique )); + } +}; + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace { struct DefaultPolyPolygon : public rtl::Static<B3DPolyPolygon::ImplType, + DefaultPolyPolygon> {}; } + + B3DPolyPolygon::B3DPolyPolygon() : + mpPolyPolygon(DefaultPolyPolygon::get()) + { + } + + B3DPolyPolygon::B3DPolyPolygon(const B3DPolyPolygon& rPolyPolygon) : + mpPolyPolygon(rPolyPolygon.mpPolyPolygon) + { + } + + B3DPolyPolygon::B3DPolyPolygon(const B3DPolygon& rPolygon) : + mpPolyPolygon( ImplB3DPolyPolygon(rPolygon) ) + { + } + + B3DPolyPolygon::~B3DPolyPolygon() + { + } + + B3DPolyPolygon& B3DPolyPolygon::operator=(const B3DPolyPolygon& rPolyPolygon) + { + mpPolyPolygon = rPolyPolygon.mpPolyPolygon; + return *this; + } + + void B3DPolyPolygon::makeUnique() + { + mpPolyPolygon.make_unique(); + mpPolyPolygon->makeUnique(); + } + + bool B3DPolyPolygon::operator==(const B3DPolyPolygon& rPolyPolygon) const + { + if(mpPolyPolygon.same_object(rPolyPolygon.mpPolyPolygon)) + return true; + + return ((*mpPolyPolygon) == (*rPolyPolygon.mpPolyPolygon)); + } + + bool B3DPolyPolygon::operator!=(const B3DPolyPolygon& rPolyPolygon) const + { + return !(*this == rPolyPolygon); + } + + sal_uInt32 B3DPolyPolygon::count() const + { + return mpPolyPolygon->count(); + } + + B3DPolygon B3DPolyPolygon::getB3DPolygon(sal_uInt32 nIndex) const + { + OSL_ENSURE(nIndex < mpPolyPolygon->count(), "B3DPolyPolygon access outside range (!)"); + + return mpPolyPolygon->getB3DPolygon(nIndex); + } + + void B3DPolyPolygon::setB3DPolygon(sal_uInt32 nIndex, const B3DPolygon& rPolygon) + { + OSL_ENSURE(nIndex < mpPolyPolygon->count(), "B3DPolyPolygon access outside range (!)"); + + if(getB3DPolygon(nIndex) != rPolygon) + mpPolyPolygon->setB3DPolygon(nIndex, rPolygon); + } + + bool B3DPolyPolygon::areBColorsUsed() const + { + for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++) + { + if((mpPolyPolygon->getB3DPolygon(a)).areBColorsUsed()) + { + return true; + } + } + + return false; + } + + void B3DPolyPolygon::clearBColors() + { + if(areBColorsUsed()) + mpPolyPolygon->clearBColors(); + } + + void B3DPolyPolygon::transformNormals(const B3DHomMatrix& rMatrix) + { + if(!rMatrix.isIdentity()) + mpPolyPolygon->transformNormals(rMatrix); + } + + bool B3DPolyPolygon::areNormalsUsed() const + { + for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++) + { + if((mpPolyPolygon->getB3DPolygon(a)).areNormalsUsed()) + { + return true; + } + } + + return false; + } + + void B3DPolyPolygon::clearNormals() + { + if(areNormalsUsed()) + mpPolyPolygon->clearNormals(); + } + + void B3DPolyPolygon::transformTextureCoordiantes(const B2DHomMatrix& rMatrix) + { + if(!rMatrix.isIdentity()) + mpPolyPolygon->transformTextureCoordiantes(rMatrix); + } + + bool B3DPolyPolygon::areTextureCoordinatesUsed() const + { + for(sal_uInt32 a(0L); a < mpPolyPolygon->count(); a++) + { + if((mpPolyPolygon->getB3DPolygon(a)).areTextureCoordinatesUsed()) + { + return true; + } + } + + return false; + } + + void B3DPolyPolygon::clearTextureCoordinates() + { + if(areTextureCoordinatesUsed()) + mpPolyPolygon->clearTextureCoordinates(); + } + + void B3DPolyPolygon::insert(sal_uInt32 nIndex, const B3DPolygon& rPolygon, sal_uInt32 nCount) + { + OSL_ENSURE(nIndex <= mpPolyPolygon->count(), "B3DPolyPolygon Insert outside range (!)"); + + if(nCount) + mpPolyPolygon->insert(nIndex, rPolygon, nCount); + } + + void B3DPolyPolygon::append(const B3DPolygon& rPolygon, sal_uInt32 nCount) + { + if(nCount) + mpPolyPolygon->insert(mpPolyPolygon->count(), rPolygon, nCount); + } + + void B3DPolyPolygon::insert(sal_uInt32 nIndex, const B3DPolyPolygon& rPolyPolygon) + { + OSL_ENSURE(nIndex <= mpPolyPolygon->count(), "B3DPolyPolygon Insert outside range (!)"); + + if(rPolyPolygon.count()) + mpPolyPolygon->insert(nIndex, rPolyPolygon); + } + + void B3DPolyPolygon::append(const B3DPolyPolygon& rPolyPolygon) + { + if(rPolyPolygon.count()) + mpPolyPolygon->insert(mpPolyPolygon->count(), rPolyPolygon); + } + + void B3DPolyPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount) + { + OSL_ENSURE(nIndex + nCount <= mpPolyPolygon->count(), "B3DPolyPolygon Remove outside range (!)"); + + if(nCount) + mpPolyPolygon->remove(nIndex, nCount); + } + + void B3DPolyPolygon::clear() + { + mpPolyPolygon = DefaultPolyPolygon::get(); + } + + bool B3DPolyPolygon::isClosed() const + { + bool bRetval(true); + + // PolyPOlygon is closed when all contained Polygons are closed or + // no Polygon exists. + for(sal_uInt32 a(0L); bRetval && a < mpPolyPolygon->count(); a++) + { + if(!(mpPolyPolygon->getB3DPolygon(a)).isClosed()) + { + bRetval = false; + } + } + + return bRetval; + } + + void B3DPolyPolygon::setClosed(bool bNew) + { + if(bNew != isClosed()) + mpPolyPolygon->setClosed(bNew); + } + + void B3DPolyPolygon::flip() + { + mpPolyPolygon->flip(); + } + + bool B3DPolyPolygon::hasDoublePoints() const + { + bool bRetval(false); + + for(sal_uInt32 a(0L); !bRetval && a < mpPolyPolygon->count(); a++) + { + if((mpPolyPolygon->getB3DPolygon(a)).hasDoublePoints()) + { + bRetval = true; + } + } + + return bRetval; + } + + void B3DPolyPolygon::removeDoublePoints() + { + if(hasDoublePoints()) + mpPolyPolygon->removeDoublePoints(); + } + + void B3DPolyPolygon::transform(const B3DHomMatrix& rMatrix) + { + if(mpPolyPolygon->count() && !rMatrix.isIdentity()) + { + mpPolyPolygon->transform(rMatrix); + } + } +} // end of namespace basegfx + +// eof diff --git a/basegfx/source/polygon/b3dpolypolygontools.cxx b/basegfx/source/polygon/b3dpolypolygontools.cxx new file mode 100644 index 000000000000..d86a4526acfd --- /dev/null +++ b/basegfx/source/polygon/b3dpolypolygontools.cxx @@ -0,0 +1,556 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <basegfx/polygon/b3dpolypolygontools.hxx> +#include <basegfx/range/b3drange.hxx> +#include <basegfx/polygon/b3dpolypolygon.hxx> +#include <basegfx/polygon/b3dpolygon.hxx> +#include <basegfx/polygon/b3dpolygontools.hxx> +#include <numeric> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <osl/mutex.hxx> + +////////////////////////////////////////////////////////////////////////////// + +namespace basegfx +{ + namespace tools + { + // B3DPolyPolygon tools + B3DRange getRange(const B3DPolyPolygon& rCandidate) + { + B3DRange aRetval; + const sal_uInt32 nPolygonCount(rCandidate.count()); + + for(sal_uInt32 a(0L); a < nPolygonCount; a++) + { + B3DPolygon aCandidate = rCandidate.getB3DPolygon(a); + aRetval.expand(getRange(aCandidate)); + } + + return aRetval; + } + + void applyLineDashing(const B3DPolyPolygon& rCandidate, const ::std::vector<double>& rDotDashArray, B3DPolyPolygon* pLineTarget, B3DPolyPolygon* pGapTarget, double fFullDashDotLen) + { + if(0.0 == fFullDashDotLen && rDotDashArray.size()) + { + // calculate fFullDashDotLen from rDotDashArray + fFullDashDotLen = ::std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0); + } + + if(rCandidate.count() && fFullDashDotLen > 0.0) + { + B3DPolyPolygon aLineTarget, aGapTarget; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + const B3DPolygon aCandidate(rCandidate.getB3DPolygon(a)); + + applyLineDashing( + aCandidate, + rDotDashArray, + pLineTarget ? &aLineTarget : 0, + pGapTarget ? &aGapTarget : 0, + fFullDashDotLen); + + if(pLineTarget) + { + pLineTarget->append(aLineTarget); + } + + if(pGapTarget) + { + pGapTarget->append(aGapTarget); + } + } + } + } + + B3DPolyPolygon createUnitCubePolyPolygon() + { + static B3DPolyPolygon aRetval; + ::osl::Mutex m_mutex; + + if(!aRetval.count()) + { + B3DPolygon aTemp; + aTemp.append(B3DPoint(0.0, 0.0, 1.0)); + aTemp.append(B3DPoint(0.0, 1.0, 1.0)); + aTemp.append(B3DPoint(1.0, 1.0, 1.0)); + aTemp.append(B3DPoint(1.0, 0.0, 1.0)); + aTemp.setClosed(true); + aRetval.append(aTemp); + + aTemp.clear(); + aTemp.append(B3DPoint(0.0, 0.0, 0.0)); + aTemp.append(B3DPoint(0.0, 1.0, 0.0)); + aTemp.append(B3DPoint(1.0, 1.0, 0.0)); + aTemp.append(B3DPoint(1.0, 0.0, 0.0)); + aTemp.setClosed(true); + aRetval.append(aTemp); + + aTemp.clear(); + aTemp.append(B3DPoint(0.0, 0.0, 0.0)); + aTemp.append(B3DPoint(0.0, 0.0, 1.0)); + aRetval.append(aTemp); + + aTemp.clear(); + aTemp.append(B3DPoint(0.0, 1.0, 0.0)); + aTemp.append(B3DPoint(0.0, 1.0, 1.0)); + aRetval.append(aTemp); + + aTemp.clear(); + aTemp.append(B3DPoint(1.0, 1.0, 0.0)); + aTemp.append(B3DPoint(1.0, 1.0, 1.0)); + aRetval.append(aTemp); + + aTemp.clear(); + aTemp.append(B3DPoint(1.0, 0.0, 0.0)); + aTemp.append(B3DPoint(1.0, 0.0, 1.0)); + aRetval.append(aTemp); + } + + return aRetval; + } + + B3DPolyPolygon createUnitCubeFillPolyPolygon() + { + static B3DPolyPolygon aRetval; + ::osl::Mutex m_mutex; + + if(!aRetval.count()) + { + B3DPolygon aTemp; + + // all points + const B3DPoint A(0.0, 0.0, 0.0); + const B3DPoint B(0.0, 1.0, 0.0); + const B3DPoint C(1.0, 1.0, 0.0); + const B3DPoint D(1.0, 0.0, 0.0); + const B3DPoint E(0.0, 0.0, 1.0); + const B3DPoint F(0.0, 1.0, 1.0); + const B3DPoint G(1.0, 1.0, 1.0); + const B3DPoint H(1.0, 0.0, 1.0); + + // create bottom + aTemp.append(D); + aTemp.append(A); + aTemp.append(E); + aTemp.append(H); + aTemp.setClosed(true); + aRetval.append(aTemp); + + // create front + aTemp.clear(); + aTemp.append(B); + aTemp.append(A); + aTemp.append(D); + aTemp.append(C); + aTemp.setClosed(true); + aRetval.append(aTemp); + + // create left + aTemp.clear(); + aTemp.append(E); + aTemp.append(A); + aTemp.append(B); + aTemp.append(F); + aTemp.setClosed(true); + aRetval.append(aTemp); + + // create top + aTemp.clear(); + aTemp.append(C); + aTemp.append(G); + aTemp.append(F); + aTemp.append(B); + aTemp.setClosed(true); + aRetval.append(aTemp); + + // create right + aTemp.clear(); + aTemp.append(H); + aTemp.append(G); + aTemp.append(C); + aTemp.append(D); + aTemp.setClosed(true); + aRetval.append(aTemp); + + // create back + aTemp.clear(); + aTemp.append(F); + aTemp.append(G); + aTemp.append(H); + aTemp.append(E); + aTemp.setClosed(true); + aRetval.append(aTemp); + } + + return aRetval; + } + + B3DPolyPolygon createCubePolyPolygonFromB3DRange( const B3DRange& rRange) + { + B3DPolyPolygon aRetval; + + if(!rRange.isEmpty()) + { + aRetval = createUnitCubePolyPolygon(); + B3DHomMatrix aTrans; + aTrans.scale(rRange.getWidth(), rRange.getHeight(), rRange.getDepth()); + aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ()); + aRetval.transform(aTrans); + aRetval.removeDoublePoints(); + } + + return aRetval; + } + + B3DPolyPolygon createCubeFillPolyPolygonFromB3DRange( const B3DRange& rRange) + { + B3DPolyPolygon aRetval; + + if(!rRange.isEmpty()) + { + aRetval = createUnitCubeFillPolyPolygon(); + B3DHomMatrix aTrans; + aTrans.scale(rRange.getWidth(), rRange.getHeight(), rRange.getDepth()); + aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ()); + aRetval.transform(aTrans); + aRetval.removeDoublePoints(); + } + + return aRetval; + } + + // helper for getting the 3D Point from given cartesian coordiantes. fVer is defined from + // [F_PI2 .. -F_PI2], fHor from [0.0 .. F_2PI] + inline B3DPoint getPointFromCartesian(double fVer, double fHor) + { + const double fCosHor(cos(fHor)); + return B3DPoint(fCosHor * cos(fVer), sin(fHor), fCosHor * -sin(fVer)); + } + + B3DPolyPolygon createUnitSpherePolyPolygon( + sal_uInt32 nHorSeg, sal_uInt32 nVerSeg, + double fVerStart, double fVerStop, + double fHorStart, double fHorStop) + { + B3DPolyPolygon aRetval; + sal_uInt32 a, b; + + if(!nHorSeg) + { + nHorSeg = fround(fabs(fHorStop - fHorStart) / (F_2PI / 24.0)); + } + + if(!nHorSeg) + { + nHorSeg = 1L; + } + + if(!nVerSeg) + { + nVerSeg = fround(fabs(fVerStop - fVerStart) / (F_2PI / 24.0)); + } + + if(!nVerSeg) + { + nVerSeg = 1L; + } + + // create constants + const double fVerDiffPerStep((fVerStop - fVerStart) / (double)nVerSeg); + const double fHorDiffPerStep((fHorStop - fHorStart) / (double)nHorSeg); + bool bHorClosed(fTools::equal(fHorStop - fHorStart, F_2PI)); + bool bVerFromTop(fTools::equal(fVerStart, F_PI2)); + bool bVerToBottom(fTools::equal(fVerStop, -F_PI2)); + + // create horizontal rings + const sal_uInt32 nLoopVerInit(bVerFromTop ? 1L : 0L); + const sal_uInt32 nLoopVerLimit(bVerToBottom ? nVerSeg : nVerSeg + 1L); + const sal_uInt32 nLoopHorLimit(bHorClosed ? nHorSeg : nHorSeg + 1L); + + for(a = nLoopVerInit; a < nLoopVerLimit; a++) + { + const double fVer(fVerStart + ((double)(a) * fVerDiffPerStep)); + B3DPolygon aNew; + + for(b = 0L; b < nLoopHorLimit; b++) + { + const double fHor(fHorStart + ((double)(b) * fHorDiffPerStep)); + aNew.append(getPointFromCartesian(fHor, fVer)); + } + + aNew.setClosed(bHorClosed); + aRetval.append(aNew); + } + + // create vertical half-rings + for(a = 0L; a < nLoopHorLimit; a++) + { + const double fHor(fHorStart + ((double)(a) * fHorDiffPerStep)); + B3DPolygon aNew; + + if(bVerFromTop) + { + aNew.append(B3DPoint(0.0, 1.0, 0.0)); + } + + for(b = nLoopVerInit; b < nLoopVerLimit; b++) + { + const double fVer(fVerStart + ((double)(b) * fVerDiffPerStep)); + aNew.append(getPointFromCartesian(fHor, fVer)); + } + + if(bVerToBottom) + { + aNew.append(B3DPoint(0.0, -1.0, 0.0)); + } + + aRetval.append(aNew); + } + + return aRetval; + } + + B3DPolyPolygon createSpherePolyPolygonFromB3DRange( const B3DRange& rRange, + sal_uInt32 nHorSeg, sal_uInt32 nVerSeg, + double fVerStart, double fVerStop, + double fHorStart, double fHorStop) + { + B3DPolyPolygon aRetval(createUnitSpherePolyPolygon(nHorSeg, nVerSeg, fVerStart, fVerStop, fHorStart, fHorStop)); + + if(aRetval.count()) + { + // move and scale whole construct which is now in [-1.0 .. 1.0] in all directions + B3DHomMatrix aTrans; + aTrans.translate(1.0, 1.0, 1.0); + aTrans.scale(rRange.getWidth() / 2.0, rRange.getHeight() / 2.0, rRange.getDepth() / 2.0); + aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ()); + aRetval.transform(aTrans); + } + + return aRetval; + } + + B3DPolyPolygon createUnitSphereFillPolyPolygon( + sal_uInt32 nHorSeg, sal_uInt32 nVerSeg, + bool bNormals, + double fVerStart, double fVerStop, + double fHorStart, double fHorStop) + { + B3DPolyPolygon aRetval; + + if(!nHorSeg) + { + nHorSeg = fround(fabs(fHorStop - fHorStart) / (F_2PI / 24.0)); + } + + if(!nHorSeg) + { + nHorSeg = 1L; + } + + if(!nVerSeg) + { + nVerSeg = fround(fabs(fVerStop - fVerStart) / (F_2PI / 24.0)); + } + + if(!nVerSeg) + { + nVerSeg = 1L; + } + + // vertical loop + for(sal_uInt32 a(0L); a < nVerSeg; a++) + { + const double fVer(fVerStart + (((fVerStop - fVerStart) * a) / nVerSeg)); + const double fVer2(fVerStart + (((fVerStop - fVerStart) * (a + 1)) / nVerSeg)); + + // horizontal loop + for(sal_uInt32 b(0L); b < nHorSeg; b++) + { + const double fHor(fHorStart + (((fHorStop - fHorStart) * b) / nHorSeg)); + const double fHor2(fHorStart + (((fHorStop - fHorStart) * (b + 1)) / nHorSeg)); + B3DPolygon aNew; + + aNew.append(getPointFromCartesian(fHor, fVer)); + aNew.append(getPointFromCartesian(fHor2, fVer)); + aNew.append(getPointFromCartesian(fHor2, fVer2)); + aNew.append(getPointFromCartesian(fHor, fVer2)); + + if(bNormals) + { + for(sal_uInt32 c(0L); c < aNew.count(); c++) + { + aNew.setNormal(c, ::basegfx::B3DVector(aNew.getB3DPoint(c))); + } + } + + aNew.setClosed(true); + aRetval.append(aNew); + } + } + + return aRetval; + } + + B3DPolyPolygon createSphereFillPolyPolygonFromB3DRange( const B3DRange& rRange, + sal_uInt32 nHorSeg, sal_uInt32 nVerSeg, + bool bNormals, + double fVerStart, double fVerStop, + double fHorStart, double fHorStop) + { + B3DPolyPolygon aRetval(createUnitSphereFillPolyPolygon(nHorSeg, nVerSeg, bNormals, fVerStart, fVerStop, fHorStart, fHorStop)); + + if(aRetval.count()) + { + // move and scale whole construct which is now in [-1.0 .. 1.0] in all directions + B3DHomMatrix aTrans; + aTrans.translate(1.0, 1.0, 1.0); + aTrans.scale(rRange.getWidth() / 2.0, rRange.getHeight() / 2.0, rRange.getDepth() / 2.0); + aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ()); + aRetval.transform(aTrans); + } + + return aRetval; + } + + B3DPolyPolygon applyDefaultNormalsSphere( const B3DPolyPolygon& rCandidate, const B3DPoint& rCenter) + { + B3DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(applyDefaultNormalsSphere(rCandidate.getB3DPolygon(a), rCenter)); + } + + return aRetval; + } + + B3DPolyPolygon invertNormals( const B3DPolyPolygon& rCandidate) + { + B3DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(invertNormals(rCandidate.getB3DPolygon(a))); + } + + return aRetval; + } + + B3DPolyPolygon applyDefaultTextureCoordinatesParallel( const B3DPolyPolygon& rCandidate, const B3DRange& rRange, bool bChangeX, bool bChangeY) + { + B3DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(applyDefaultTextureCoordinatesParallel(rCandidate.getB3DPolygon(a), rRange, bChangeX, bChangeY)); + } + + return aRetval; + } + + B3DPolyPolygon applyDefaultTextureCoordinatesSphere( const B3DPolyPolygon& rCandidate, const B3DPoint& rCenter, bool bChangeX, bool bChangeY) + { + B3DPolyPolygon aRetval; + + for(sal_uInt32 a(0L); a < rCandidate.count(); a++) + { + aRetval.append(applyDefaultTextureCoordinatesSphere(rCandidate.getB3DPolygon(a), rCenter, bChangeX, bChangeY)); + } + + return aRetval; + } + + bool isInside(const B3DPolyPolygon& rCandidate, const B3DPoint& rPoint, bool bWithBorder) + { + const sal_uInt32 nPolygonCount(rCandidate.count()); + + if(1L == nPolygonCount) + { + return isInside(rCandidate.getB3DPolygon(0), rPoint, bWithBorder); + } + else + { + sal_Int32 nInsideCount(0); + + for(sal_uInt32 a(0); a < nPolygonCount; a++) + { + const B3DPolygon aPolygon(rCandidate.getB3DPolygon(a)); + const bool bInside(isInside(aPolygon, rPoint, bWithBorder)); + + if(bInside) + { + nInsideCount++; + } + } + + return (nInsideCount % 2L); + } + } + + ////////////////////////////////////////////////////////////////////// + // comparators with tolerance for 3D PolyPolygons + + bool equal(const B3DPolyPolygon& rCandidateA, const B3DPolyPolygon& rCandidateB, const double& rfSmallValue) + { + const sal_uInt32 nPolygonCount(rCandidateA.count()); + + if(nPolygonCount != rCandidateB.count()) + return false; + + for(sal_uInt32 a(0); a < nPolygonCount; a++) + { + const B3DPolygon aCandidate(rCandidateA.getB3DPolygon(a)); + + if(!equal(aCandidate, rCandidateB.getB3DPolygon(a), rfSmallValue)) + return false; + } + + return true; + } + + bool equal(const B3DPolyPolygon& rCandidateA, const B3DPolyPolygon& rCandidateB) + { + const double fSmallValue(fTools::getSmallValue()); + + return equal(rCandidateA, rCandidateB, fSmallValue); + } + + } // end of namespace tools +} // end of namespace basegfx + +////////////////////////////////////////////////////////////////////////////// + +// eof diff --git a/basegfx/source/polygon/makefile.mk b/basegfx/source/polygon/makefile.mk new file mode 100644 index 000000000000..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/b2dmultirange.cxx b/basegfx/source/range/b2dmultirange.cxx new file mode 100644 index 000000000000..3a47be3b7dc9 --- /dev/null +++ b/basegfx/source/range/b2dmultirange.cxx @@ -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. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_basegfx.hxx" +#include <basegfx/range/b2drange.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/range/b2dmultirange.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <boost/bind.hpp> +#include <boost/mem_fn.hpp> +#include <algorithm> +#include <vector> + + +namespace basegfx +{ + class ImplB2DMultiRange + { + public: + ImplB2DMultiRange() : + maBounds(), + maRanges() + { + } + + explicit ImplB2DMultiRange( const B2DRange& rRange ) : + maBounds(), + maRanges( 1, rRange ) + { + } + + bool isEmpty() const + { + // no ranges at all, or all ranges empty + return maRanges.empty() || + ::std::count_if( maRanges.begin(), + maRanges.end(), + ::boost::mem_fn( &B2DRange::isEmpty ) ) + == static_cast<VectorOfRanges::difference_type>(maRanges.size()); + } + + void reset() + { + // swap in empty vector + VectorOfRanges aTmp; + maRanges.swap( aTmp ); + + maBounds.reset(); + } + + 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. + VectorOfRanges::const_iterator aCurr( maRanges.begin() ); + const VectorOfRanges::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 VectorOfRanges::const_iterator aEnd( maRanges.end() ); + return ::std::find_if( maRanges.begin(), + aEnd, + ::boost::bind<bool>( ::boost::mem_fn( &B2DRange::overlaps ), + _1, + rRange ) ) != aEnd; + } + + void addRange( const B2DRange& rRange ) + { + maRanges.push_back( rRange ); + maBounds.expand( rRange ); + } + + B2DRange getBounds() const + { + return maBounds; + } + + B2DPolyPolygon getPolyPolygon() const + { + B2DPolyPolygon aRes; + + // Make range vector unique ( have to avoid duplicate + // rectangles. The polygon clipper will return an empty + // result in this case). + VectorOfRanges aUniqueRanges; + aUniqueRanges.reserve( maRanges.size() ); + + VectorOfRanges::const_iterator aCurr( maRanges.begin() ); + const VectorOfRanges::const_iterator aEnd ( maRanges.end() ); + while( aCurr != aEnd ) + { + // TODO(F3): It's plain wasted resources to apply a + // general clipping algorithm to the problem at + // hand. Go for a dedicated, scan-line-based approach. + VectorOfRanges::const_iterator aCurrScan( aCurr+1 ); + VectorOfRanges::const_iterator aFound( aEnd ); + while( aCurrScan != aEnd ) + { + if( aCurrScan->equal( *aCurr ) || + aCurrScan->isInside( *aCurr ) ) + { + // current probe is equal to aCurr, or + // completely contains aCurr. Thus, stop + // searching, because aCurr is definitely not + // a member of the unique rect list + aFound = aCurrScan; + break; + } + + ++aCurrScan; + } + + if( aFound == aEnd ) + { + // check whether aCurr is fully contained in one + // of the already added rects. If yes, we can skip + // it. + bool bUnique( true ); + VectorOfRanges::const_iterator aCurrUnique( aUniqueRanges.begin() ); + VectorOfRanges::const_iterator aEndUnique ( aUniqueRanges.end() ); + while( aCurrUnique != aEndUnique ) + { + if( aCurrUnique->isInside( *aCurr ) ) + { + // fully contained, no need to add + bUnique = false; + break; + } + + ++aCurrUnique; + } + + if( bUnique ) + aUniqueRanges.push_back( *aCurr ); + } + + ++aCurr; + } + + VectorOfRanges::const_iterator aCurrUnique( aUniqueRanges.begin() ); + const VectorOfRanges::const_iterator aEndUnique ( aUniqueRanges.end() ); + while( aCurrUnique != aEndUnique ) + { + // simply merge all unique parts (OR) + aRes.append( tools::createPolygonFromRect( *aCurrUnique++ ) ); + } + + // remove redundant intersections. Note: since all added + // rectangles are positively oriented, this method cannot + // generate any holes. + aRes = basegfx::tools::solveCrossovers(aRes); + aRes = basegfx::tools::stripNeutralPolygons(aRes); + aRes = basegfx::tools::stripDispensablePolygons(aRes, false); + + return aRes; + } + + private: + typedef ::std::vector< B2DRange > VectorOfRanges; + + B2DRange maBounds; + VectorOfRanges maRanges; + }; + + + // ==================================================================== + + + B2DMultiRange::B2DMultiRange() : + mpImpl() + { + } + + B2DMultiRange::B2DMultiRange( const B2DRange& rRange ) : + mpImpl( ImplB2DMultiRange( rRange ) ) + { + } + + B2DMultiRange::~B2DMultiRange() + { + // otherwise, ImplB2DMultiRange would be an incomplete type + } + + B2DMultiRange::B2DMultiRange( const B2DMultiRange& rSrc ) : + mpImpl( rSrc.mpImpl ) + { + } + + B2DMultiRange& B2DMultiRange::operator=( const B2DMultiRange& rSrc ) + { + mpImpl = rSrc.mpImpl; + return *this; + } + + bool B2DMultiRange::isEmpty() const + { + return mpImpl->isEmpty(); + } + + void B2DMultiRange::reset() + { + mpImpl->reset(); + } + + bool B2DMultiRange::isInside( const B2DTuple& rTuple ) const + { + return mpImpl->isInside( rTuple ); + } + + bool B2DMultiRange::isInside( const B2DRange& rRange ) const + { + return mpImpl->isInside( rRange ); + } + + bool B2DMultiRange::overlaps( const B2DRange& rRange ) const + { + return mpImpl->overlaps( rRange ); + } + + void B2DMultiRange::addRange( const B2DRange& rRange ) + { + mpImpl->addRange( rRange ); + } + + B2DRange B2DMultiRange::getBounds() const + { + return mpImpl->getBounds(); + } + + B2DPolyPolygon B2DMultiRange::getPolyPolygon() const + { + return mpImpl->getPolyPolygon(); + } + +} // 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/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..04d8e8e66fa2 --- /dev/null +++ b/basegfx/source/range/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=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)$/b2dmultirange.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/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..89293cfcb61c --- /dev/null +++ b/basegfx/source/tools/gradienttools.cxx @@ -0,0 +1,360 @@ +/************************************************************************* + * + * 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; + + 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; + } + + // add object scale before rotate + o_rGradientInfo.maTextureTransform.scale(fTargetSizeX, fTargetSizeY); + + // add texture rotate after scale to keep perpendicular angles + if(0.0 != fAngle) + { + B2DPoint aCenter(0.5, 0.5); + aCenter *= o_rGradientInfo.maTextureTransform; + + o_rGradientInfo.maTextureTransform = basegfx::tools::createRotateAroundPoint(aCenter, fAngle) + * o_rGradientInfo.maTextureTransform; + } + + // 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(); + + double fSizeWithoutBorder=0; + if( bAxial ) + { + fSizeWithoutBorder = (1.0 - fBorder) * 0.5; + o_rGradientInfo.maBackTextureTransform.translate(0.0, -0.5); + } + else + { + fSizeWithoutBorder = 1.0 - fBorder; + o_rGradientInfo.maBackTextureTransform.translate(0.0, -fBorder); + } + + if(!fTools::equal(fSizeWithoutBorder, 0.0)) + o_rGradientInfo.maBackTextureTransform.scale(1.0, 1.0 / fSizeWithoutBorder); + } + + /** 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; + + 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; + } + + // add object scale before rotate + o_rGradientInfo.maTextureTransform.scale(fTargetSizeX, fTargetSizeY); + + if( !bCircular ) + { + // add texture rotate after scale to keep perpendicular angles + if(0.0 != fAngle) + { + B2DPoint aCenter(0.5, 0.5); + aCenter *= o_rGradientInfo.maTextureTransform; + + o_rGradientInfo.maTextureTransform = basegfx::tools::createRotateAroundPoint(aCenter, fAngle) + * o_rGradientInfo.maTextureTransform; + } + } + + // 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]. As base, use inverse texture transform + o_rGradientInfo.maBackTextureTransform = o_rGradientInfo.maTextureTransform; + o_rGradientInfo.maBackTextureTransform.invert(); + o_rGradientInfo.maBackTextureTransform.translate(-0.5, -0.5); + const double fHalfBorder((1.0 - fBorder) * 0.5); + + if(!fTools::equal(fHalfBorder, 0.0)) + { + const double fFactor(1.0 / fHalfBorder); + o_rGradientInfo.maBackTextureTransform.scale(fFactor, fFactor); + } + } + + /** 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; + + 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; + } + + // add object scale before rotate + o_rGradientInfo.maTextureTransform.scale(fTargetSizeX, fTargetSizeY); + + // add texture rotate after scale to keep perpendicular angles + if(0.0 != fAngle) + { + B2DPoint aCenter(0.5, 0.5); + aCenter *= o_rGradientInfo.maTextureTransform; + + o_rGradientInfo.maTextureTransform = basegfx::tools::createRotateAroundPoint(aCenter, fAngle) + * o_rGradientInfo.maTextureTransform; + } + + // 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(); + o_rGradientInfo.maBackTextureTransform.translate(-0.5, -0.5); + const double fHalfBorder((1.0 - fBorder) * 0.5); + + if(!fTools::equal(fHalfBorder, 0.0)) + { + const double fFactor(1.0 / fHalfBorder); + o_rGradientInfo.maBackTextureTransform.scale(fFactor, fFactor); + } + } + + 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/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..c70b78be5d4b --- /dev/null +++ b/basegfx/source/tools/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=tools + +ENABLE_EXCEPTIONS=TRUE + +# --- Settings ---------------------------------- + +.INCLUDE : settings.mk + +# --- Files ------------------------------------- + +SLOFILES= $(SLO)$/canvastools.obj \ + $(SLO)$/gradienttools.obj \ + $(SLO)$/debugplotter.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; +} |