blob: 48637080f08fc51175083496c17b4ef7083fe11e [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_basegfx.hxx"
#include <basegfx/curve/b2dcubicbezier.hxx>
#include <basegfx/vector/b2dvector.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <limits>
// #i37443#
#define FACTOR_FOR_UNSHARPEN (1.6)
#ifdef DBG_UTIL
static double fMultFactUnsharpen = FACTOR_FOR_UNSHARPEN;
#endif
//////////////////////////////////////////////////////////////////////////////
namespace basegfx
{
namespace
{
void ImpSubDivAngle(
const B2DPoint& rfPA, // start point
const B2DPoint& rfEA, // edge on A
const B2DPoint& rfEB, // edge on B
const B2DPoint& rfPB, // end point
B2DPolygon& rTarget, // target polygon
double fAngleBound, // angle bound in [0.0 .. 2PI]
bool bAllowUnsharpen, // #i37443# allow the criteria to get unsharp in recursions
sal_uInt16 nMaxRecursionDepth) // endless loop protection
{
if(nMaxRecursionDepth)
{
// do angle test
B2DVector aLeft(rfEA - rfPA);
B2DVector aRight(rfEB - rfPB);
// #i72104#
if(aLeft.equalZero())
{
aLeft = rfEB - rfPA;
}
if(aRight.equalZero())
{
aRight = rfEA - rfPB;
}
const double fCurrentAngle(aLeft.angle(aRight));
if(fabs(fCurrentAngle) > (F_PI - fAngleBound))
{
// end recursion
nMaxRecursionDepth = 0;
}
else
{
if(bAllowUnsharpen)
{
// #i37443# unsharpen criteria
#ifdef DBG_UTIL
fAngleBound *= fMultFactUnsharpen;
#else
fAngleBound *= FACTOR_FOR_UNSHARPEN;
#endif
}
}
}
if(nMaxRecursionDepth)
{
// divide at 0.5
const B2DPoint aS1L(average(rfPA, rfEA));
const B2DPoint aS1C(average(rfEA, rfEB));
const B2DPoint aS1R(average(rfEB, rfPB));
const B2DPoint aS2L(average(aS1L, aS1C));
const B2DPoint aS2R(average(aS1C, aS1R));
const B2DPoint aS3C(average(aS2L, aS2R));
// left recursion
ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1);
// right recursion
ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1);
}
else
{
rTarget.append(rfPB);
}
}
void ImpSubDivAngleStart(
const B2DPoint& rfPA, // start point
const B2DPoint& rfEA, // edge on A
const B2DPoint& rfEB, // edge on B
const B2DPoint& rfPB, // end point
B2DPolygon& rTarget, // target polygon
const double& rfAngleBound, // angle bound in [0.0 .. 2PI]
bool bAllowUnsharpen) // #i37443# allow the criteria to get unsharp in recursions
{
sal_uInt16 nMaxRecursionDepth(8);
const B2DVector aLeft(rfEA - rfPA);
const B2DVector aRight(rfEB - rfPB);
bool bLeftEqualZero(aLeft.equalZero());
bool bRightEqualZero(aRight.equalZero());
bool bAllParallel(false);
if(bLeftEqualZero && bRightEqualZero)
{
nMaxRecursionDepth = 0;
}
else
{
const B2DVector aBase(rfPB - rfPA);
const bool bBaseEqualZero(aBase.equalZero()); // #i72104#
if(!bBaseEqualZero)
{
const bool bLeftParallel(bLeftEqualZero ? true : areParallel(aLeft, aBase));
const bool bRightParallel(bRightEqualZero ? true : areParallel(aRight, aBase));
if(bLeftParallel && bRightParallel)
{
bAllParallel = true;
if(!bLeftEqualZero)
{
double fFactor;
if(fabs(aBase.getX()) > fabs(aBase.getY()))
{
fFactor = aLeft.getX() / aBase.getX();
}
else
{
fFactor = aLeft.getY() / aBase.getY();
}
if(fFactor >= 0.0 && fFactor <= 1.0)
{
bLeftEqualZero = true;
}
}
if(!bRightEqualZero)
{
double fFactor;
if(fabs(aBase.getX()) > fabs(aBase.getY()))
{
fFactor = aRight.getX() / -aBase.getX();
}
else
{
fFactor = aRight.getY() / -aBase.getY();
}
if(fFactor >= 0.0 && fFactor <= 1.0)
{
bRightEqualZero = true;
}
}
if(bLeftEqualZero && bRightEqualZero)
{
nMaxRecursionDepth = 0;
}
}
}
}
if(nMaxRecursionDepth)
{
// divide at 0.5 ad test both edges for angle criteria
const B2DPoint aS1L(average(rfPA, rfEA));
const B2DPoint aS1C(average(rfEA, rfEB));
const B2DPoint aS1R(average(rfEB, rfPB));
const B2DPoint aS2L(average(aS1L, aS1C));
const B2DPoint aS2R(average(aS1C, aS1R));
const B2DPoint aS3C(average(aS2L, aS2R));
// test left
bool bAngleIsSmallerLeft(bAllParallel && bLeftEqualZero);
if(!bAngleIsSmallerLeft)
{
const B2DVector aLeftLeft(bLeftEqualZero ? aS2L - aS1L : aS1L - rfPA); // #i72104#
const B2DVector aRightLeft(aS2L - aS3C);
const double fCurrentAngleLeft(aLeftLeft.angle(aRightLeft));
bAngleIsSmallerLeft = (fabs(fCurrentAngleLeft) > (F_PI - rfAngleBound));
}
// test right
bool bAngleIsSmallerRight(bAllParallel && bRightEqualZero);
if(!bAngleIsSmallerRight)
{
const B2DVector aLeftRight(aS2R - aS3C);
const B2DVector aRightRight(bRightEqualZero ? aS2R - aS1R : aS1R - rfPB); // #i72104#
const double fCurrentAngleRight(aLeftRight.angle(aRightRight));
bAngleIsSmallerRight = (fabs(fCurrentAngleRight) > (F_PI - rfAngleBound));
}
if(bAngleIsSmallerLeft && bAngleIsSmallerRight)
{
// no recursion necessary at all
nMaxRecursionDepth = 0;
}
else
{
// left
if(bAngleIsSmallerLeft)
{
rTarget.append(aS3C);
}
else
{
ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, rfAngleBound, bAllowUnsharpen, nMaxRecursionDepth);
}
// right
if(bAngleIsSmallerRight)
{
rTarget.append(rfPB);
}
else
{
ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, rfAngleBound, bAllowUnsharpen, nMaxRecursionDepth);
}
}
}
if(!nMaxRecursionDepth)
{
rTarget.append(rfPB);
}
}
void ImpSubDivDistance(
const B2DPoint& rfPA, // start point
const B2DPoint& rfEA, // edge on A
const B2DPoint& rfEB, // edge on B
const B2DPoint& rfPB, // end point
B2DPolygon& rTarget, // target polygon
double fDistanceBound2, // quadratic distance criteria
double fLastDistanceError2, // the last quadratic distance error
sal_uInt16 nMaxRecursionDepth) // endless loop protection
{
if(nMaxRecursionDepth)
{
// decide if another recursion is needed. If not, set
// nMaxRecursionDepth to zero
// Perform bezier flatness test (lecture notes from R. Schaback,
// Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
//
// ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)||
// 0<=j<=n
//
// What is calculated here is an upper bound to the distance from
// a line through b_0 and b_3 (rfPA and P4 in our notation) and the
// curve. We can drop 0 and n from the running indices, since the
// argument of max becomes zero for those cases.
const double fJ1x(rfEA.getX() - rfPA.getX() - 1.0/3.0*(rfPB.getX() - rfPA.getX()));
const double fJ1y(rfEA.getY() - rfPA.getY() - 1.0/3.0*(rfPB.getY() - rfPA.getY()));
const double fJ2x(rfEB.getX() - rfPA.getX() - 2.0/3.0*(rfPB.getX() - rfPA.getX()));
const double fJ2y(rfEB.getY() - rfPA.getY() - 2.0/3.0*(rfPB.getY() - rfPA.getY()));
const double fDistanceError2(::std::max(fJ1x*fJ1x + fJ1y*fJ1y, fJ2x*fJ2x + fJ2y*fJ2y));
// stop if error measure does not improve anymore. This is a
// safety guard against floating point inaccuracies.
// stop if distance from line is guaranteed to be bounded by d
const bool bFurtherDivision(fLastDistanceError2 > fDistanceError2 && fDistanceError2 >= fDistanceBound2);
if(bFurtherDivision)
{
// remember last error value
fLastDistanceError2 = fDistanceError2;
}
else
{
// stop recustion
nMaxRecursionDepth = 0;
}
}
if(nMaxRecursionDepth)
{
// divide at 0.5
const B2DPoint aS1L(average(rfPA, rfEA));
const B2DPoint aS1C(average(rfEA, rfEB));
const B2DPoint aS1R(average(rfEB, rfPB));
const B2DPoint aS2L(average(aS1L, aS1C));
const B2DPoint aS2R(average(aS1C, aS1R));
const B2DPoint aS3C(average(aS2L, aS2R));
// left recursion
ImpSubDivDistance(rfPA, aS1L, aS2L, aS3C, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1);
// right recursion
ImpSubDivDistance(aS3C, aS2R, aS1R, rfPB, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1);
}
else
{
rTarget.append(rfPB);
}
}
} // end of anonymous namespace
} // end of namespace basegfx
//////////////////////////////////////////////////////////////////////////////
namespace basegfx
{
B2DCubicBezier::B2DCubicBezier(const B2DCubicBezier& rBezier)
: maStartPoint(rBezier.maStartPoint),
maEndPoint(rBezier.maEndPoint),
maControlPointA(rBezier.maControlPointA),
maControlPointB(rBezier.maControlPointB)
{
}
B2DCubicBezier::B2DCubicBezier()
{
}
B2DCubicBezier::B2DCubicBezier(const B2DPoint& rStart, const B2DPoint& rEnd)
: maStartPoint(rStart),
maEndPoint(rEnd),
maControlPointA(rStart),
maControlPointB(rEnd)
{
}
B2DCubicBezier::B2DCubicBezier(const B2DPoint& rStart, const B2DPoint& rControlPointA, const B2DPoint& rControlPointB, const B2DPoint& rEnd)
: maStartPoint(rStart),
maEndPoint(rEnd),
maControlPointA(rControlPointA),
maControlPointB(rControlPointB)
{
}
B2DCubicBezier::~B2DCubicBezier()
{
}
// assignment operator
B2DCubicBezier& B2DCubicBezier::operator=(const B2DCubicBezier& rBezier)
{
maStartPoint = rBezier.maStartPoint;
maEndPoint = rBezier.maEndPoint;
maControlPointA = rBezier.maControlPointA;
maControlPointB = rBezier.maControlPointB;
return *this;
}
// compare operators
bool B2DCubicBezier::operator==(const B2DCubicBezier& rBezier) const
{
return (
maStartPoint == rBezier.maStartPoint
&& maEndPoint == rBezier.maEndPoint
&& maControlPointA == rBezier.maControlPointA
&& maControlPointB == rBezier.maControlPointB
);
}
bool B2DCubicBezier::operator!=(const B2DCubicBezier& rBezier) const
{
return (
maStartPoint != rBezier.maStartPoint
|| maEndPoint != rBezier.maEndPoint
|| maControlPointA != rBezier.maControlPointA
|| maControlPointB != rBezier.maControlPointB
);
}
bool B2DCubicBezier::equal(const B2DCubicBezier& rBezier) const
{
return (
maStartPoint.equal(rBezier.maStartPoint)
&& maEndPoint.equal(rBezier.maEndPoint)
&& maControlPointA.equal(rBezier.maControlPointA)
&& maControlPointB.equal(rBezier.maControlPointB)
);
}
// test if vectors are used
bool B2DCubicBezier::isBezier() const
{
if(maControlPointA != maStartPoint || maControlPointB != maEndPoint)
{
return true;
}
return false;
}
void B2DCubicBezier::testAndSolveTrivialBezier()
{
if(maControlPointA != maStartPoint || maControlPointB != maEndPoint)
{
const B2DVector aEdge(maEndPoint - maStartPoint);
// controls parallel to edge can be trivial. No edge -> not parallel -> control can
// still not be trivial (e.g. ballon loop)
if(!aEdge.equalZero())
{
// get control vectors
const B2DVector aVecA(maControlPointA - maStartPoint);
const B2DVector aVecB(maControlPointB - maEndPoint);
// check if trivial per se
bool bAIsTrivial(aVecA.equalZero());
bool bBIsTrivial(aVecB.equalZero());
// #i102241# prepare inverse edge length to normalize cross values;
// else the small compare value used in fTools::equalZero
// will be length dependent and this detection will work as less
// precise as longer the edge is. In principle, the length of the control
// vector would need to be used too, but to be trivial it is assumed to
// be of roughly equal length to the edge, so edge length can be used
// for both. Only needed when one of both is not trivial per se.
const double fInverseEdgeLength(bAIsTrivial && bBIsTrivial
? 1.0
: 1.0 / aEdge.getLength());
// if A is not zero, check if it could be
if(!bAIsTrivial)
{
// #i102241# parallel to edge? Check aVecA, aEdge. Use cross() which does what
// we need here with the precision we need
const double fCross(aVecA.cross(aEdge) * fInverseEdgeLength);
if(fTools::equalZero(fCross))
{
// get scale to edge. Use bigger distance for numeric quality
const double fScale(fabs(aEdge.getX()) > fabs(aEdge.getY())
? aVecA.getX() / aEdge.getX()
: aVecA.getY() / aEdge.getY());
// relative end point of vector in edge range?
if(fTools::moreOrEqual(fScale, 0.0) && fTools::lessOrEqual(fScale, 1.0))
{
bAIsTrivial = true;
}
}
}
// if B is not zero, check if it could be, but only if A is already trivial;
// else solve to trivial will not be possible for whole edge
if(bAIsTrivial && !bBIsTrivial)
{
// parallel to edge? Check aVecB, aEdge
const double fCross(aVecB.cross(aEdge) * fInverseEdgeLength);
if(fTools::equalZero(fCross))
{
// get scale to edge. Use bigger distance for numeric quality
const double fScale(fabs(aEdge.getX()) > fabs(aEdge.getY())
? aVecB.getX() / aEdge.getX()
: aVecB.getY() / aEdge.getY());
// end point of vector in edge range? Caution: controlB is directed AGAINST edge
if(fTools::lessOrEqual(fScale, 0.0) && fTools::moreOrEqual(fScale, -1.0))
{
bBIsTrivial = true;
}
}
}
// if both are/can be reduced, do it.
// Not possible if only one is/can be reduced (!)
if(bAIsTrivial && bBIsTrivial)
{
maControlPointA = maStartPoint;
maControlPointB = maEndPoint;
}
}
}
}
namespace {
double impGetLength(const B2DCubicBezier& rEdge, double fDeviation, sal_uInt32 nRecursionWatch)
{
const double fEdgeLength(rEdge.getEdgeLength());
const double fControlPolygonLength(rEdge.getControlPolygonLength());
const double fCurrentDeviation(fTools::equalZero(fControlPolygonLength) ? 0.0 : 1.0 - (fEdgeLength / fControlPolygonLength));
if(!nRecursionWatch || fTools:: lessOrEqual(fCurrentDeviation, fDeviation))
{
return (fEdgeLength + fControlPolygonLength) * 0.5;
}
else
{
B2DCubicBezier aLeft, aRight;
const double fNewDeviation(fDeviation * 0.5);
const sal_uInt32 nNewRecursionWatch(nRecursionWatch - 1);
rEdge.split(0.5, &aLeft, &aRight);
return impGetLength(aLeft, fNewDeviation, nNewRecursionWatch)
+ impGetLength(aRight, fNewDeviation, nNewRecursionWatch);
}
}
}
double B2DCubicBezier::getLength(double fDeviation) const
{
if(isBezier())
{
if(fDeviation < 0.00000001)
{
fDeviation = 0.00000001;
}
return impGetLength(*this, fDeviation, 6);
}
else
{
return B2DVector(getEndPoint() - getStartPoint()).getLength();
}
}
double B2DCubicBezier::getEdgeLength() const
{
const B2DVector aEdge(maEndPoint - maStartPoint);
return aEdge.getLength();
}
double B2DCubicBezier::getControlPolygonLength() const
{
const B2DVector aVectorA(maControlPointA - maStartPoint);
const B2DVector aVectorB(maEndPoint - maControlPointB);
if(!aVectorA.equalZero() || !aVectorB.equalZero())
{
const B2DVector aTop(maControlPointB - maControlPointA);
return (aVectorA.getLength() + aVectorB.getLength() + aTop.getLength());
}
else
{
return getEdgeLength();
}
}
void B2DCubicBezier::adaptiveSubdivideByAngle(B2DPolygon& rTarget, double fAngleBound, bool bAllowUnsharpen) const
{
if(isBezier())
{
// use support method #i37443# and allow unsharpen the criteria
ImpSubDivAngleStart(maStartPoint, maControlPointA, maControlPointB, maEndPoint, rTarget, fAngleBound * F_PI180, bAllowUnsharpen);
}
else
{
rTarget.append(getEndPoint());
}
}
B2DVector B2DCubicBezier::getTangent(double t) const
{
if(fTools::lessOrEqual(t, 0.0))
{
// tangent in start point
B2DVector aTangent(getControlPointA() - getStartPoint());
if(!aTangent.equalZero())
{
return aTangent;
}
// start point and control vector are the same, fallback
// to implicit start vector to control point B
aTangent = (getControlPointB() - getStartPoint()) * 0.3;
if(!aTangent.equalZero())
{
return aTangent;
}
// not a bezier at all, return edge vector
return (getEndPoint() - getStartPoint()) * 0.3;
}
else if(fTools::moreOrEqual(t, 1.0))
{
// tangent in end point
B2DVector aTangent(getEndPoint() - getControlPointB());
if(!aTangent.equalZero())
{
return aTangent;
}
// end point and control vector are the same, fallback
// to implicit start vector from control point A
aTangent = (getEndPoint() - getControlPointA()) * 0.3;
if(!aTangent.equalZero())
{
return aTangent;
}
// not a bezier at all, return edge vector
return (getEndPoint() - getStartPoint()) * 0.3;
}
else
{
// t is in ]0.0 .. 1.0[. Split and extract
B2DCubicBezier aRight;
split(t, 0, &aRight);
return aRight.getControlPointA() - aRight.getStartPoint();
}
}
// #i37443# adaptive subdivide by nCount subdivisions
void B2DCubicBezier::adaptiveSubdivideByCount(B2DPolygon& rTarget, sal_uInt32 nCount) const
{
const double fLenFact(1.0 / static_cast< double >(nCount + 1));
for(sal_uInt32 a(1); a <= nCount; a++)
{
const double fPos(static_cast< double >(a) * fLenFact);
rTarget.append(interpolatePoint(fPos));
}
rTarget.append(getEndPoint());
}
// adaptive subdivide by distance
void B2DCubicBezier::adaptiveSubdivideByDistance(B2DPolygon& rTarget, double fDistanceBound) const
{
if(isBezier())
{
ImpSubDivDistance(maStartPoint, maControlPointA, maControlPointB, maEndPoint, rTarget,
fDistanceBound * fDistanceBound, ::std::numeric_limits<double>::max(), 30);
}
else
{
rTarget.append(getEndPoint());
}
}
B2DPoint B2DCubicBezier::interpolatePoint(double t) const
{
OSL_ENSURE(t >= 0.0 && t <= 1.0, "B2DCubicBezier::interpolatePoint: Access out of range (!)");
if(isBezier())
{
const B2DPoint aS1L(interpolate(maStartPoint, maControlPointA, t));
const B2DPoint aS1C(interpolate(maControlPointA, maControlPointB, t));
const B2DPoint aS1R(interpolate(maControlPointB, maEndPoint, t));
const B2DPoint aS2L(interpolate(aS1L, aS1C, t));
const B2DPoint aS2R(interpolate(aS1C, aS1R, t));
return interpolate(aS2L, aS2R, t);
}
else
{
return interpolate(maStartPoint, maEndPoint, t);
}
}
double B2DCubicBezier::getSmallestDistancePointToBezierSegment(const B2DPoint& rTestPoint, double& rCut) const
{
const sal_uInt32 nInitialDivisions(3L);
B2DPolygon aInitialPolygon;
// as start make a fix division, creates nInitialDivisions + 2L points
aInitialPolygon.append(getStartPoint());
adaptiveSubdivideByCount(aInitialPolygon, nInitialDivisions);
// now look for the closest point
const sal_uInt32 nPointCount(aInitialPolygon.count());
B2DVector aVector(rTestPoint - aInitialPolygon.getB2DPoint(0L));
double fQuadDist(aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY());
double fNewQuadDist;
sal_uInt32 nSmallestIndex(0L);
for(sal_uInt32 a(1L); a < nPointCount; a++)
{
aVector = B2DVector(rTestPoint - aInitialPolygon.getB2DPoint(a));
fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY();
if(fNewQuadDist < fQuadDist)
{
fQuadDist = fNewQuadDist;
nSmallestIndex = a;
}
}
// look right and left for even smaller distances
double fStepValue(1.0 / (double)((nPointCount - 1L) * 2L)); // half the edge step width
double fPosition((double)nSmallestIndex / (double)(nPointCount - 1L));
bool bDone(false);
while(!bDone)
{
if(!bDone)
{
// test left
double fPosLeft(fPosition - fStepValue);
if(fPosLeft < 0.0)
{
fPosLeft = 0.0;
aVector = B2DVector(rTestPoint - maStartPoint);
}
else
{
aVector = B2DVector(rTestPoint - interpolatePoint(fPosLeft));
}
fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY();
if(fTools::less(fNewQuadDist, fQuadDist))
{
fQuadDist = fNewQuadDist;
fPosition = fPosLeft;
}
else
{
// test right
double fPosRight(fPosition + fStepValue);
if(fPosRight > 1.0)
{
fPosRight = 1.0;
aVector = B2DVector(rTestPoint - maEndPoint);
}
else
{
aVector = B2DVector(rTestPoint - interpolatePoint(fPosRight));
}
fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY();
if(fTools::less(fNewQuadDist, fQuadDist))
{
fQuadDist = fNewQuadDist;
fPosition = fPosRight;
}
else
{
// not less left or right, done
bDone = true;
}
}
}
if(0.0 == fPosition || 1.0 == fPosition)
{
// if we are completely left or right, we are done
bDone = true;
}
if(!bDone)
{
// prepare next step value
fStepValue /= 2.0;
}
}
rCut = fPosition;
return sqrt(fQuadDist);
}
void B2DCubicBezier::split(double t, B2DCubicBezier* pBezierA, B2DCubicBezier* pBezierB) const
{
OSL_ENSURE(t >= 0.0 && t <= 1.0, "B2DCubicBezier::split: Access out of range (!)");
if(!pBezierA && !pBezierB)
{
return;
}
if(isBezier())
{
const B2DPoint aS1L(interpolate(maStartPoint, maControlPointA, t));
const B2DPoint aS1C(interpolate(maControlPointA, maControlPointB, t));
const B2DPoint aS1R(interpolate(maControlPointB, maEndPoint, t));
const B2DPoint aS2L(interpolate(aS1L, aS1C, t));
const B2DPoint aS2R(interpolate(aS1C, aS1R, t));
const B2DPoint aS3C(interpolate(aS2L, aS2R, t));
if(pBezierA)
{
pBezierA->setStartPoint(maStartPoint);
pBezierA->setEndPoint(aS3C);
pBezierA->setControlPointA(aS1L);
pBezierA->setControlPointB(aS2L);
}
if(pBezierB)
{
pBezierB->setStartPoint(aS3C);
pBezierB->setEndPoint(maEndPoint);
pBezierB->setControlPointA(aS2R);
pBezierB->setControlPointB(aS1R);
}
}
else
{
const B2DPoint aSplit(interpolate(maStartPoint, maEndPoint, t));
if(pBezierA)
{
pBezierA->setStartPoint(maStartPoint);
pBezierA->setEndPoint(aSplit);
pBezierA->setControlPointA(maStartPoint);
pBezierA->setControlPointB(aSplit);
}
if(pBezierB)
{
pBezierB->setStartPoint(aSplit);
pBezierB->setEndPoint(maEndPoint);
pBezierB->setControlPointA(aSplit);
pBezierB->setControlPointB(maEndPoint);
}
}
}
B2DCubicBezier B2DCubicBezier::snippet(double fStart, double fEnd) const
{
B2DCubicBezier aRetval;
if(fTools::more(fStart, 1.0))
{
fStart = 1.0;
}
else if(fTools::less(fStart, 0.0))
{
fStart = 0.0;
}
if(fTools::more(fEnd, 1.0))
{
fEnd = 1.0;
}
else if(fTools::less(fEnd, 0.0))
{
fEnd = 0.0;
}
if(fEnd <= fStart)
{
// empty or NULL, create single point at center
const double fSplit((fEnd + fStart) * 0.5);
const B2DPoint aPoint(interpolate(getStartPoint(), getEndPoint(), fSplit));
aRetval.setStartPoint(aPoint);
aRetval.setEndPoint(aPoint);
aRetval.setControlPointA(aPoint);
aRetval.setControlPointB(aPoint);
}
else
{
if(isBezier())
{
// copy bezier; cut off right, then cut off left. Do not forget to
// adapt cut value when both cuts happen
const bool bEndIsOne(fTools::equal(fEnd, 1.0));
const bool bStartIsZero(fTools::equalZero(fStart));
aRetval = *this;
if(!bEndIsOne)
{
aRetval.split(fEnd, &aRetval, 0);
if(!bStartIsZero)
{
fStart /= fEnd;
}
}
if(!bStartIsZero)
{
aRetval.split(fStart, 0, &aRetval);
}
}
else
{
// no bezier, create simple edge
const B2DPoint aPointA(interpolate(getStartPoint(), getEndPoint(), fStart));
const B2DPoint aPointB(interpolate(getStartPoint(), getEndPoint(), fEnd));
aRetval.setStartPoint(aPointA);
aRetval.setEndPoint(aPointB);
aRetval.setControlPointA(aPointA);
aRetval.setControlPointB(aPointB);
}
}
return aRetval;
}
B2DRange B2DCubicBezier::getRange() const
{
B2DRange aRetval(maStartPoint, maEndPoint);
aRetval.expand(maControlPointA);
aRetval.expand(maControlPointB);
return aRetval;
}
bool B2DCubicBezier::getMinimumExtremumPosition(double& rfResult) const
{
::std::vector< double > aAllResults;
aAllResults.reserve(4);
getAllExtremumPositions(aAllResults);
const sal_uInt32 nCount(aAllResults.size());
if(!nCount)
{
return false;
}
else if(1 == nCount)
{
rfResult = aAllResults[0];
return true;
}
else
{
rfResult = *(::std::min_element(aAllResults.begin(), aAllResults.end()));
return true;
}
}
namespace
{
inline void impCheckExtremumResult(double fCandidate, ::std::vector< double >& rResult)
{
// check for range ]0.0 .. 1.0[ with excluding 1.0 and 0.0 clearly
// by using the equalZero test, NOT ::more or ::less which will use the
// ApproxEqual() which is too exact here
if(fCandidate > 0.0 && !fTools::equalZero(fCandidate))
{
if(fCandidate < 1.0 && !fTools::equalZero(fCandidate - 1.0))
{
rResult.push_back(fCandidate);
}
}
}
}
void B2DCubicBezier::getAllExtremumPositions(::std::vector< double >& rResults) const
{
rResults.clear();
// calculate the x-extrema parameters by zeroing first x-derivative
// of the cubic bezier's parametric formula, which results in a
// quadratic equation: dBezier/dt = t*t*fAX - 2*t*fBX + fCX
const B2DPoint aControlDiff( maControlPointA - maControlPointB );
double fCX = maControlPointA.getX() - maStartPoint.getX();
const double fBX = fCX + aControlDiff.getX();
const double fAX = 3 * aControlDiff.getX() + (maEndPoint.getX() - maStartPoint.getX());
if(fTools::equalZero(fCX))
{
// detect fCX equal zero and truncate to real zero value in that case
fCX = 0.0;
}
if( !fTools::equalZero(fAX) )
{
// derivative is polynomial of order 2 => use binomial formula
const double fD = fBX*fBX - fAX*fCX;
if( fD >= 0.0 )
{
const double fS = sqrt(fD);
// calculate both roots (avoiding a numerically unstable subtraction)
const double fQ = fBX + ((fBX >= 0) ? +fS : -fS);
impCheckExtremumResult(fQ / fAX, rResults);
if( !fTools::equalZero(fS) ) // ignore root multiplicity
impCheckExtremumResult(fCX / fQ, rResults);
}
}
else if( !fTools::equalZero(fBX) )
{
// derivative is polynomial of order 1 => one extrema
impCheckExtremumResult(fCX / (2 * fBX), rResults);
}
// calculate the y-extrema parameters by zeroing first y-derivative
double fCY = maControlPointA.getY() - maStartPoint.getY();
const double fBY = fCY + aControlDiff.getY();
const double fAY = 3 * aControlDiff.getY() + (maEndPoint.getY() - maStartPoint.getY());
if(fTools::equalZero(fCY))
{
// detect fCY equal zero and truncate to real zero value in that case
fCY = 0.0;
}
if( !fTools::equalZero(fAY) )
{
// derivative is polynomial of order 2 => use binomial formula
const double fD = fBY*fBY - fAY*fCY;
if( fD >= 0.0 )
{
const double fS = sqrt(fD);
// calculate both roots (avoiding a numerically unstable subtraction)
const double fQ = fBY + ((fBY >= 0) ? +fS : -fS);
impCheckExtremumResult(fQ / fAY, rResults);
if( !fTools::equalZero(fS) ) // ignore root multiplicity
impCheckExtremumResult(fCY / fQ, rResults);
}
}
else if( !fTools::equalZero(fBY) )
{
// derivative is polynomial of order 1 => one extrema
impCheckExtremumResult(fCY / (2 * fBY), rResults);
}
}
int B2DCubicBezier::getMaxDistancePositions( double pResult[2]) const
{
// the distance from the bezier to a line through start and end
// is proportional to (ENDx-STARTx,ENDy-STARTy)*(+BEZIERy(t)-STARTy,-BEZIERx(t)-STARTx)
// this distance becomes zero for at least t==0 and t==1
// its extrema that are between 0..1 are interesting as split candidates
// its derived function has the form dD/dt = fA*t^2 + 2*fB*t + fC
const B2DPoint aRelativeEndPoint(maEndPoint-maStartPoint);
const double fA = (3 * (maControlPointA.getX() - maControlPointB.getX()) + aRelativeEndPoint.getX()) * aRelativeEndPoint.getY()
- (3 * (maControlPointA.getY() - maControlPointB.getY()) + aRelativeEndPoint.getY()) * aRelativeEndPoint.getX();
const double fB = (maControlPointB.getX() - 2 * maControlPointA.getX() + maStartPoint.getX()) * aRelativeEndPoint.getY()
- (maControlPointB.getY() - 2 * maControlPointA.getY() + maStartPoint.getY()) * aRelativeEndPoint.getX();
const double fC = (maControlPointA.getX() - maStartPoint.getX()) * aRelativeEndPoint.getY()
- (maControlPointA.getY() - maStartPoint.getY()) * aRelativeEndPoint.getX();
// test for degenerated case: order<2
if( fTools::equalZero(fA) )
{
// test for degenerated case: order==0
if( fTools::equalZero(fB) )
return 0;
// solving the order==1 polynomial is trivial
pResult[0] = -fC / (2*fB);
// test root and ignore it when it is outside the curve
int nCount = ((pResult[0] > 0) && (pResult[0] < 1));
return nCount;
}
// derivative is polynomial of order 2
// check if the polynomial has non-imaginary roots
const double fD = fB*fB - fA*fC;
if( fD >= 0.0 ) // TODO: is this test needed? geometrically not IMHO
{
// calculate first root (avoiding a numerically unstable subtraction)
const double fS = sqrt(fD);
const double fQ = -(fB + ((fB >= 0) ? +fS : -fS));
pResult[0] = fQ / fA;
// ignore root when it is outside the curve
static const double fEps = 1e-9;
int nCount = ((pResult[0] > fEps) && (pResult[0] < fEps));
// ignore root multiplicity
if( !fTools::equalZero(fD) )
{
// calculate the other root
const double fRoot = fC / fQ;
// ignore root when it is outside the curve
if( (fRoot > fEps) && (fRoot < 1.0-fEps) )
pResult[ nCount++ ] = fRoot;
}
return nCount;
}
return 0;
}
} // end of namespace basegfx
// eof