1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <basegfx/curve/b2dcubicbezier.hxx>
21 #include <basegfx/vector/b2dvector.hxx>
22 #include <basegfx/polygon/b2dpolygon.hxx>
23 #include <basegfx/matrix/b2dhommatrix.hxx>
24 #include <basegfx/numeric/ftools.hxx>
26 #include <osl/diagnose.h>
31 #define FACTOR_FOR_UNSHARPEN (1.6)
33 const double fMultFactUnsharpen
= FACTOR_FOR_UNSHARPEN
;
41 const B2DPoint
& rfPA
, // start point
42 const B2DPoint
& rfEA
, // edge on A
43 const B2DPoint
& rfEB
, // edge on B
44 const B2DPoint
& rfPB
, // end point
45 B2DPolygon
& rTarget
, // target polygon
46 double fAngleBound
, // angle bound in [0.0 .. 2PI]
47 bool bAllowUnsharpen
, // #i37443# allow the criteria to get unsharp in recursions
48 sal_uInt16 nMaxRecursionDepth
) // endless loop protection
50 if(nMaxRecursionDepth
)
53 B2DVector
aLeft(rfEA
- rfPA
);
54 B2DVector
aRight(rfEB
- rfPB
);
62 if(aRight
.equalZero())
67 const double fCurrentAngle(aLeft
.angle(aRight
));
69 if(fabs(fCurrentAngle
) > (F_PI
- fAngleBound
))
72 nMaxRecursionDepth
= 0;
78 // #i37443# unsharpen criteria
80 fAngleBound
*= fMultFactUnsharpen
;
82 fAngleBound
*= FACTOR_FOR_UNSHARPEN
;
88 if(nMaxRecursionDepth
)
91 const B2DPoint
aS1L(average(rfPA
, rfEA
));
92 const B2DPoint
aS1C(average(rfEA
, rfEB
));
93 const B2DPoint
aS1R(average(rfEB
, rfPB
));
94 const B2DPoint
aS2L(average(aS1L
, aS1C
));
95 const B2DPoint
aS2R(average(aS1C
, aS1R
));
96 const B2DPoint
aS3C(average(aS2L
, aS2R
));
99 ImpSubDivAngle(rfPA
, aS1L
, aS2L
, aS3C
, rTarget
, fAngleBound
, bAllowUnsharpen
, nMaxRecursionDepth
- 1);
102 ImpSubDivAngle(aS3C
, aS2R
, aS1R
, rfPB
, rTarget
, fAngleBound
, bAllowUnsharpen
, nMaxRecursionDepth
- 1);
106 rTarget
.append(rfPB
);
110 void ImpSubDivAngleStart(
111 const B2DPoint
& rfPA
, // start point
112 const B2DPoint
& rfEA
, // edge on A
113 const B2DPoint
& rfEB
, // edge on B
114 const B2DPoint
& rfPB
, // end point
115 B2DPolygon
& rTarget
, // target polygon
116 const double& rfAngleBound
) // angle bound in [0.0 .. 2PI]
118 sal_uInt16
nMaxRecursionDepth(8);
119 const B2DVector
aLeft(rfEA
- rfPA
);
120 const B2DVector
aRight(rfEB
- rfPB
);
121 bool bLeftEqualZero(aLeft
.equalZero());
122 bool bRightEqualZero(aRight
.equalZero());
123 bool bAllParallel(false);
125 if(bLeftEqualZero
&& bRightEqualZero
)
127 nMaxRecursionDepth
= 0;
131 const B2DVector
aBase(rfPB
- rfPA
);
132 const bool bBaseEqualZero(aBase
.equalZero()); // #i72104#
136 const bool bLeftParallel(bLeftEqualZero
|| areParallel(aLeft
, aBase
));
137 const bool bRightParallel(bRightEqualZero
|| areParallel(aRight
, aBase
));
139 if(bLeftParallel
&& bRightParallel
)
147 if(fabs(aBase
.getX()) > fabs(aBase
.getY()))
149 fFactor
= aLeft
.getX() / aBase
.getX();
153 fFactor
= aLeft
.getY() / aBase
.getY();
156 if(fFactor
>= 0.0 && fFactor
<= 1.0)
158 bLeftEqualZero
= true;
166 if(fabs(aBase
.getX()) > fabs(aBase
.getY()))
168 fFactor
= aRight
.getX() / -aBase
.getX();
172 fFactor
= aRight
.getY() / -aBase
.getY();
175 if(fFactor
>= 0.0 && fFactor
<= 1.0)
177 bRightEqualZero
= true;
181 if(bLeftEqualZero
&& bRightEqualZero
)
183 nMaxRecursionDepth
= 0;
189 if(nMaxRecursionDepth
)
191 // divide at 0.5 ad test both edges for angle criteria
192 const B2DPoint
aS1L(average(rfPA
, rfEA
));
193 const B2DPoint
aS1C(average(rfEA
, rfEB
));
194 const B2DPoint
aS1R(average(rfEB
, rfPB
));
195 const B2DPoint
aS2L(average(aS1L
, aS1C
));
196 const B2DPoint
aS2R(average(aS1C
, aS1R
));
197 const B2DPoint
aS3C(average(aS2L
, aS2R
));
200 bool bAngleIsSmallerLeft(bAllParallel
&& bLeftEqualZero
);
201 if(!bAngleIsSmallerLeft
)
203 const B2DVector
aLeftLeft(bLeftEqualZero
? aS2L
- aS1L
: aS1L
- rfPA
); // #i72104#
204 const B2DVector
aRightLeft(aS2L
- aS3C
);
205 const double fCurrentAngleLeft(aLeftLeft
.angle(aRightLeft
));
206 bAngleIsSmallerLeft
= (fabs(fCurrentAngleLeft
) > (F_PI
- rfAngleBound
));
210 bool bAngleIsSmallerRight(bAllParallel
&& bRightEqualZero
);
211 if(!bAngleIsSmallerRight
)
213 const B2DVector
aLeftRight(aS2R
- aS3C
);
214 const B2DVector
aRightRight(bRightEqualZero
? aS2R
- aS1R
: aS1R
- rfPB
); // #i72104#
215 const double fCurrentAngleRight(aLeftRight
.angle(aRightRight
));
216 bAngleIsSmallerRight
= (fabs(fCurrentAngleRight
) > (F_PI
- rfAngleBound
));
219 if(bAngleIsSmallerLeft
&& bAngleIsSmallerRight
)
221 // no recursion necessary at all
222 nMaxRecursionDepth
= 0;
227 if(bAngleIsSmallerLeft
)
229 rTarget
.append(aS3C
);
233 ImpSubDivAngle(rfPA
, aS1L
, aS2L
, aS3C
, rTarget
, rfAngleBound
, true/*bAllowUnsharpen*/, nMaxRecursionDepth
);
237 if(bAngleIsSmallerRight
)
239 rTarget
.append(rfPB
);
243 ImpSubDivAngle(aS3C
, aS2R
, aS1R
, rfPB
, rTarget
, rfAngleBound
, true/*bAllowUnsharpen*/, nMaxRecursionDepth
);
248 if(!nMaxRecursionDepth
)
250 rTarget
.append(rfPB
);
254 void ImpSubDivDistance(
255 const B2DPoint
& rfPA
, // start point
256 const B2DPoint
& rfEA
, // edge on A
257 const B2DPoint
& rfEB
, // edge on B
258 const B2DPoint
& rfPB
, // end point
259 B2DPolygon
& rTarget
, // target polygon
260 double fDistanceBound2
, // quadratic distance criteria
261 double fLastDistanceError2
, // the last quadratic distance error
262 sal_uInt16 nMaxRecursionDepth
) // endless loop protection
264 if(nMaxRecursionDepth
)
266 // decide if another recursion is needed. If not, set
267 // nMaxRecursionDepth to zero
269 // Perform bezier flatness test (lecture notes from R. Schaback,
270 // Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
272 // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)||
275 // What is calculated here is an upper bound to the distance from
276 // a line through b_0 and b_3 (rfPA and P4 in our notation) and the
277 // curve. We can drop 0 and n from the running indices, since the
278 // argument of max becomes zero for those cases.
279 const double fJ1x(rfEA
.getX() - rfPA
.getX() - 1.0/3.0*(rfPB
.getX() - rfPA
.getX()));
280 const double fJ1y(rfEA
.getY() - rfPA
.getY() - 1.0/3.0*(rfPB
.getY() - rfPA
.getY()));
281 const double fJ2x(rfEB
.getX() - rfPA
.getX() - 2.0/3.0*(rfPB
.getX() - rfPA
.getX()));
282 const double fJ2y(rfEB
.getY() - rfPA
.getY() - 2.0/3.0*(rfPB
.getY() - rfPA
.getY()));
283 const double fDistanceError2(std::max(fJ1x
*fJ1x
+ fJ1y
*fJ1y
, fJ2x
*fJ2x
+ fJ2y
*fJ2y
));
285 // stop if error measure does not improve anymore. This is a
286 // safety guard against floating point inaccuracies.
287 // stop if distance from line is guaranteed to be bounded by d
288 const bool bFurtherDivision(fLastDistanceError2
> fDistanceError2
&& fDistanceError2
>= fDistanceBound2
);
292 // remember last error value
293 fLastDistanceError2
= fDistanceError2
;
298 nMaxRecursionDepth
= 0;
302 if(nMaxRecursionDepth
)
305 const B2DPoint
aS1L(average(rfPA
, rfEA
));
306 const B2DPoint
aS1C(average(rfEA
, rfEB
));
307 const B2DPoint
aS1R(average(rfEB
, rfPB
));
308 const B2DPoint
aS2L(average(aS1L
, aS1C
));
309 const B2DPoint
aS2R(average(aS1C
, aS1R
));
310 const B2DPoint
aS3C(average(aS2L
, aS2R
));
313 ImpSubDivDistance(rfPA
, aS1L
, aS2L
, aS3C
, rTarget
, fDistanceBound2
, fLastDistanceError2
, nMaxRecursionDepth
- 1);
316 ImpSubDivDistance(aS3C
, aS2R
, aS1R
, rfPB
, rTarget
, fDistanceBound2
, fLastDistanceError2
, nMaxRecursionDepth
- 1);
320 rTarget
.append(rfPB
);
323 } // end of anonymous namespace
324 } // end of namespace basegfx
328 B2DCubicBezier::B2DCubicBezier(const B2DCubicBezier
&) = default;
330 B2DCubicBezier::B2DCubicBezier() = default;
332 B2DCubicBezier::B2DCubicBezier(const B2DPoint
& rStart
, const B2DPoint
& rControlPointA
, const B2DPoint
& rControlPointB
, const B2DPoint
& rEnd
)
333 : maStartPoint(rStart
),
335 maControlPointA(rControlPointA
),
336 maControlPointB(rControlPointB
)
340 B2DCubicBezier::~B2DCubicBezier() = default;
342 // assignment operator
343 B2DCubicBezier
& B2DCubicBezier::operator=(const B2DCubicBezier
&) = default;
346 bool B2DCubicBezier::operator==(const B2DCubicBezier
& rBezier
) const
349 maStartPoint
== rBezier
.maStartPoint
350 && maEndPoint
== rBezier
.maEndPoint
351 && maControlPointA
== rBezier
.maControlPointA
352 && maControlPointB
== rBezier
.maControlPointB
356 bool B2DCubicBezier::operator!=(const B2DCubicBezier
& rBezier
) const
359 maStartPoint
!= rBezier
.maStartPoint
360 || maEndPoint
!= rBezier
.maEndPoint
361 || maControlPointA
!= rBezier
.maControlPointA
362 || maControlPointB
!= rBezier
.maControlPointB
366 bool B2DCubicBezier::equal(const B2DCubicBezier
& rBezier
) const
369 maStartPoint
.equal(rBezier
.maStartPoint
)
370 && maEndPoint
.equal(rBezier
.maEndPoint
)
371 && maControlPointA
.equal(rBezier
.maControlPointA
)
372 && maControlPointB
.equal(rBezier
.maControlPointB
)
376 // test if vectors are used
377 bool B2DCubicBezier::isBezier() const
379 return maControlPointA
!= maStartPoint
|| maControlPointB
!= maEndPoint
;
382 void B2DCubicBezier::testAndSolveTrivialBezier()
384 if(maControlPointA
== maStartPoint
&& maControlPointB
== maEndPoint
)
387 const B2DVector
aEdge(maEndPoint
- maStartPoint
);
389 // controls parallel to edge can be trivial. No edge -> not parallel -> control can
390 // still not be trivial (e.g. ballon loop)
391 if(aEdge
.equalZero())
394 // get control vectors
395 const B2DVector
aVecA(maControlPointA
- maStartPoint
);
396 const B2DVector
aVecB(maControlPointB
- maEndPoint
);
398 // check if trivial per se
399 bool bAIsTrivial(aVecA
.equalZero());
400 bool bBIsTrivial(aVecB
.equalZero());
402 // #i102241# prepare inverse edge length to normalize cross values;
403 // else the small compare value used in fTools::equalZero
404 // will be length dependent and this detection will work as less
405 // precise as longer the edge is. In principle, the length of the control
406 // vector would need to be used too, but to be trivial it is assumed to
407 // be of roughly equal length to the edge, so edge length can be used
408 // for both. Only needed when one of both is not trivial per se.
409 const double fInverseEdgeLength(bAIsTrivial
&& bBIsTrivial
411 : 1.0 / aEdge
.getLength());
413 // if A is not zero, check if it could be
416 // #i102241# parallel to edge? Check aVecA, aEdge. Use cross() which does what
417 // we need here with the precision we need
418 const double fCross(aVecA
.cross(aEdge
) * fInverseEdgeLength
);
420 if(fTools::equalZero(fCross
))
422 // get scale to edge. Use bigger distance for numeric quality
423 const double fScale(fabs(aEdge
.getX()) > fabs(aEdge
.getY())
424 ? aVecA
.getX() / aEdge
.getX()
425 : aVecA
.getY() / aEdge
.getY());
427 // relative end point of vector in edge range?
428 if (fTools::betweenOrEqualEither(fScale
, 0.0, 1.0))
435 // if B is not zero, check if it could be, but only if A is already trivial;
436 // else solve to trivial will not be possible for whole edge
437 if(bAIsTrivial
&& !bBIsTrivial
)
439 // parallel to edge? Check aVecB, aEdge
440 const double fCross(aVecB
.cross(aEdge
) * fInverseEdgeLength
);
442 if(fTools::equalZero(fCross
))
444 // get scale to edge. Use bigger distance for numeric quality
445 const double fScale(fabs(aEdge
.getX()) > fabs(aEdge
.getY())
446 ? aVecB
.getX() / aEdge
.getX()
447 : aVecB
.getY() / aEdge
.getY());
449 // end point of vector in edge range? Caution: controlB is directed AGAINST edge
450 if (fTools::betweenOrEqualEither(fScale
, -1.0, 0.0))
457 // if both are/can be reduced, do it.
458 // Not possible if only one is/can be reduced (!)
459 if(bAIsTrivial
&& bBIsTrivial
)
461 maControlPointA
= maStartPoint
;
462 maControlPointB
= maEndPoint
;
467 double impGetLength(const B2DCubicBezier
& rEdge
, double fDeviation
, sal_uInt32 nRecursionWatch
)
469 const double fEdgeLength(rEdge
.getEdgeLength());
470 const double fControlPolygonLength(rEdge
.getControlPolygonLength());
471 const double fCurrentDeviation(fTools::equalZero(fControlPolygonLength
) ? 0.0 : 1.0 - (fEdgeLength
/ fControlPolygonLength
));
473 if(!nRecursionWatch
|| fTools:: lessOrEqual(fCurrentDeviation
, fDeviation
))
475 return (fEdgeLength
+ fControlPolygonLength
) * 0.5;
479 B2DCubicBezier aLeft
, aRight
;
480 const double fNewDeviation(fDeviation
* 0.5);
481 const sal_uInt32
nNewRecursionWatch(nRecursionWatch
- 1);
483 rEdge
.split(0.5, &aLeft
, &aRight
);
485 return impGetLength(aLeft
, fNewDeviation
, nNewRecursionWatch
)
486 + impGetLength(aRight
, fNewDeviation
, nNewRecursionWatch
);
491 double B2DCubicBezier::getLength(double fDeviation
) const
495 if(fDeviation
< 0.00000001)
497 fDeviation
= 0.00000001;
500 return impGetLength(*this, fDeviation
, 6);
504 return B2DVector(getEndPoint() - getStartPoint()).getLength();
508 double B2DCubicBezier::getEdgeLength() const
510 const B2DVector
aEdge(maEndPoint
- maStartPoint
);
511 return aEdge
.getLength();
514 double B2DCubicBezier::getControlPolygonLength() const
516 const B2DVector
aVectorA(maControlPointA
- maStartPoint
);
517 const B2DVector
aVectorB(maEndPoint
- maControlPointB
);
519 if(!aVectorA
.equalZero() || !aVectorB
.equalZero())
521 const B2DVector
aTop(maControlPointB
- maControlPointA
);
522 return (aVectorA
.getLength() + aVectorB
.getLength() + aTop
.getLength());
526 return getEdgeLength();
530 void B2DCubicBezier::adaptiveSubdivideByAngle(B2DPolygon
& rTarget
, double fAngleBound
) const
534 // use support method #i37443# and allow unsharpen the criteria
535 ImpSubDivAngleStart(maStartPoint
, maControlPointA
, maControlPointB
, maEndPoint
, rTarget
,
536 deg2rad(fAngleBound
));
540 rTarget
.append(getEndPoint());
544 B2DVector
B2DCubicBezier::getTangent(double t
) const
546 if(fTools::lessOrEqual(t
, 0.0))
548 // tangent in start point
549 B2DVector
aTangent(getControlPointA() - getStartPoint());
551 if(!aTangent
.equalZero())
556 // start point and control vector are the same, fallback
557 // to implicit start vector to control point B
558 aTangent
= (getControlPointB() - getStartPoint()) * 0.3;
560 if(!aTangent
.equalZero())
565 // not a bezier at all, return edge vector
566 return (getEndPoint() - getStartPoint()) * 0.3;
568 else if(fTools::moreOrEqual(t
, 1.0))
570 // tangent in end point
571 B2DVector
aTangent(getEndPoint() - getControlPointB());
573 if(!aTangent
.equalZero())
578 // end point and control vector are the same, fallback
579 // to implicit start vector from control point A
580 aTangent
= (getEndPoint() - getControlPointA()) * 0.3;
582 if(!aTangent
.equalZero())
587 // not a bezier at all, return edge vector
588 return (getEndPoint() - getStartPoint()) * 0.3;
592 // t is in ]0.0 .. 1.0[. Split and extract
593 B2DCubicBezier aRight
;
594 split(t
, nullptr, &aRight
);
596 return aRight
.getControlPointA() - aRight
.getStartPoint();
600 // #i37443# adaptive subdivide by nCount subdivisions
601 void B2DCubicBezier::adaptiveSubdivideByCount(B2DPolygon
& rTarget
, sal_uInt32 nCount
) const
603 const double fLenFact(1.0 / static_cast< double >(nCount
+ 1));
605 for(sal_uInt32
a(1); a
<= nCount
; a
++)
607 const double fPos(static_cast< double >(a
) * fLenFact
);
608 rTarget
.append(interpolatePoint(fPos
));
611 rTarget
.append(getEndPoint());
614 // adaptive subdivide by distance
615 void B2DCubicBezier::adaptiveSubdivideByDistance(B2DPolygon
& rTarget
, double fDistanceBound
) const
619 ImpSubDivDistance(maStartPoint
, maControlPointA
, maControlPointB
, maEndPoint
, rTarget
,
620 fDistanceBound
* fDistanceBound
, std::numeric_limits
<double>::max(), 30);
624 rTarget
.append(getEndPoint());
628 B2DPoint
B2DCubicBezier::interpolatePoint(double t
) const
630 OSL_ENSURE(t
>= 0.0 && t
<= 1.0, "B2DCubicBezier::interpolatePoint: Access out of range (!)");
634 const B2DPoint
aS1L(interpolate(maStartPoint
, maControlPointA
, t
));
635 const B2DPoint
aS1C(interpolate(maControlPointA
, maControlPointB
, t
));
636 const B2DPoint
aS1R(interpolate(maControlPointB
, maEndPoint
, t
));
637 const B2DPoint
aS2L(interpolate(aS1L
, aS1C
, t
));
638 const B2DPoint
aS2R(interpolate(aS1C
, aS1R
, t
));
640 return interpolate(aS2L
, aS2R
, t
);
644 return interpolate(maStartPoint
, maEndPoint
, t
);
648 double B2DCubicBezier::getSmallestDistancePointToBezierSegment(const B2DPoint
& rTestPoint
, double& rCut
) const
650 const sal_uInt32
nInitialDivisions(3);
651 B2DPolygon aInitialPolygon
;
653 // as start make a fix division, creates nInitialDivisions + 2 points
654 aInitialPolygon
.append(getStartPoint());
655 adaptiveSubdivideByCount(aInitialPolygon
, nInitialDivisions
);
657 // now look for the closest point
658 const sal_uInt32
nPointCount(aInitialPolygon
.count());
659 B2DVector
aVector(rTestPoint
- aInitialPolygon
.getB2DPoint(0));
660 double fQuadDist(aVector
.getX() * aVector
.getX() + aVector
.getY() * aVector
.getY());
662 sal_uInt32
nSmallestIndex(0);
664 for(sal_uInt32
a(1); a
< nPointCount
; a
++)
666 aVector
= B2DVector(rTestPoint
- aInitialPolygon
.getB2DPoint(a
));
667 fNewQuadDist
= aVector
.getX() * aVector
.getX() + aVector
.getY() * aVector
.getY();
669 if(fNewQuadDist
< fQuadDist
)
671 fQuadDist
= fNewQuadDist
;
676 // look right and left for even smaller distances
677 double fStepValue(1.0 / static_cast<double>((nPointCount
- 1) * 2)); // half the edge step width
678 double fPosition(static_cast<double>(nSmallestIndex
) / static_cast<double>(nPointCount
- 1));
683 double fPosLeft(fPosition
- fStepValue
);
688 aVector
= B2DVector(rTestPoint
- maStartPoint
);
692 aVector
= B2DVector(rTestPoint
- interpolatePoint(fPosLeft
));
695 fNewQuadDist
= aVector
.getX() * aVector
.getX() + aVector
.getY() * aVector
.getY();
697 if(fTools::less(fNewQuadDist
, fQuadDist
))
699 fQuadDist
= fNewQuadDist
;
700 fPosition
= fPosLeft
;
705 double fPosRight(fPosition
+ fStepValue
);
710 aVector
= B2DVector(rTestPoint
- maEndPoint
);
714 aVector
= B2DVector(rTestPoint
- interpolatePoint(fPosRight
));
717 fNewQuadDist
= aVector
.getX() * aVector
.getX() + aVector
.getY() * aVector
.getY();
719 if(fTools::less(fNewQuadDist
, fQuadDist
))
721 fQuadDist
= fNewQuadDist
;
722 fPosition
= fPosRight
;
726 // not less left or right, done
731 if(fPosition
== 0.0 || fPosition
== 1.0)
733 // if we are completely left or right, we are done
737 // prepare next step value
742 return sqrt(fQuadDist
);
745 void B2DCubicBezier::split(double t
, B2DCubicBezier
* pBezierA
, B2DCubicBezier
* pBezierB
) const
747 OSL_ENSURE(t
>= 0.0 && t
<= 1.0, "B2DCubicBezier::split: Access out of range (!)");
749 if(!pBezierA
&& !pBezierB
)
756 const B2DPoint
aS1L(interpolate(maStartPoint
, maControlPointA
, t
));
757 const B2DPoint
aS1C(interpolate(maControlPointA
, maControlPointB
, t
));
758 const B2DPoint
aS1R(interpolate(maControlPointB
, maEndPoint
, t
));
759 const B2DPoint
aS2L(interpolate(aS1L
, aS1C
, t
));
760 const B2DPoint
aS2R(interpolate(aS1C
, aS1R
, t
));
761 const B2DPoint
aS3C(interpolate(aS2L
, aS2R
, t
));
765 pBezierA
->setStartPoint(maStartPoint
);
766 pBezierA
->setEndPoint(aS3C
);
767 pBezierA
->setControlPointA(aS1L
);
768 pBezierA
->setControlPointB(aS2L
);
773 pBezierB
->setStartPoint(aS3C
);
774 pBezierB
->setEndPoint(maEndPoint
);
775 pBezierB
->setControlPointA(aS2R
);
776 pBezierB
->setControlPointB(aS1R
);
781 const B2DPoint
aSplit(interpolate(maStartPoint
, maEndPoint
, t
));
785 pBezierA
->setStartPoint(maStartPoint
);
786 pBezierA
->setEndPoint(aSplit
);
787 pBezierA
->setControlPointA(maStartPoint
);
788 pBezierA
->setControlPointB(aSplit
);
793 pBezierB
->setStartPoint(aSplit
);
794 pBezierB
->setEndPoint(maEndPoint
);
795 pBezierB
->setControlPointA(aSplit
);
796 pBezierB
->setControlPointB(maEndPoint
);
801 B2DCubicBezier
B2DCubicBezier::snippet(double fStart
, double fEnd
) const
803 B2DCubicBezier aRetval
;
805 if(fTools::more(fStart
, 1.0))
809 else if(fTools::less(fStart
, 0.0))
814 if(fTools::more(fEnd
, 1.0))
818 else if(fTools::less(fEnd
, 0.0))
825 // empty or NULL, create single point at center
826 const double fSplit((fEnd
+ fStart
) * 0.5);
827 const B2DPoint
aPoint(interpolate(getStartPoint(), getEndPoint(), fSplit
));
828 aRetval
.setStartPoint(aPoint
);
829 aRetval
.setEndPoint(aPoint
);
830 aRetval
.setControlPointA(aPoint
);
831 aRetval
.setControlPointB(aPoint
);
837 // copy bezier; cut off right, then cut off left. Do not forget to
838 // adapt cut value when both cuts happen
839 const bool bEndIsOne(fTools::equal(fEnd
, 1.0));
840 const bool bStartIsZero(fTools::equalZero(fStart
));
845 aRetval
.split(fEnd
, &aRetval
, nullptr);
855 aRetval
.split(fStart
, nullptr, &aRetval
);
860 // no bezier, create simple edge
861 const B2DPoint
aPointA(interpolate(getStartPoint(), getEndPoint(), fStart
));
862 const B2DPoint
aPointB(interpolate(getStartPoint(), getEndPoint(), fEnd
));
863 aRetval
.setStartPoint(aPointA
);
864 aRetval
.setEndPoint(aPointB
);
865 aRetval
.setControlPointA(aPointA
);
866 aRetval
.setControlPointB(aPointB
);
873 B2DRange
B2DCubicBezier::getRange() const
875 B2DRange
aRetval(maStartPoint
, maEndPoint
);
877 aRetval
.expand(maControlPointA
);
878 aRetval
.expand(maControlPointB
);
883 bool B2DCubicBezier::getMinimumExtremumPosition(double& rfResult
) const
885 std::vector
< double > aAllResults
;
887 aAllResults
.reserve(4);
888 getAllExtremumPositions(aAllResults
);
890 const sal_uInt32
nCount(aAllResults
.size());
898 rfResult
= aAllResults
[0];
903 rfResult
= *(std::min_element(aAllResults
.begin(), aAllResults
.end()));
910 void impCheckExtremumResult(double fCandidate
, std::vector
< double >& rResult
)
912 // check for range ]0.0 .. 1.0[ with excluding 1.0 and 0.0 clearly
913 // by using the equalZero test, NOT ::more or ::less which will use the
914 // ApproxEqual() which is too exact here
915 if(fCandidate
> 0.0 && !fTools::equalZero(fCandidate
))
917 if(fCandidate
< 1.0 && !fTools::equalZero(fCandidate
- 1.0))
919 rResult
.push_back(fCandidate
);
925 void B2DCubicBezier::getAllExtremumPositions(std::vector
< double >& rResults
) const
929 // calculate the x-extrema parameters by zeroing first x-derivative
930 // of the cubic bezier's parametric formula, which results in a
931 // quadratic equation: dBezier/dt = t*t*fAX - 2*t*fBX + fCX
932 const B2DPoint
aControlDiff( maControlPointA
- maControlPointB
);
933 double fCX
= maControlPointA
.getX() - maStartPoint
.getX();
934 const double fBX
= fCX
+ aControlDiff
.getX();
935 const double fAX
= 3 * aControlDiff
.getX() + (maEndPoint
.getX() - maStartPoint
.getX());
937 if(fTools::equalZero(fCX
))
939 // detect fCX equal zero and truncate to real zero value in that case
943 if( !fTools::equalZero(fAX
) )
945 // derivative is polynomial of order 2 => use binomial formula
946 const double fD
= fBX
*fBX
- fAX
*fCX
;
949 const double fS
= sqrt(fD
);
950 // calculate both roots (avoiding a numerically unstable subtraction)
951 const double fQ
= fBX
+ ((fBX
>= 0) ? +fS
: -fS
);
952 impCheckExtremumResult(fQ
/ fAX
, rResults
);
953 if( !fTools::equalZero(fS
) ) // ignore root multiplicity
954 impCheckExtremumResult(fCX
/ fQ
, rResults
);
957 else if( !fTools::equalZero(fBX
) )
959 // derivative is polynomial of order 1 => one extrema
960 impCheckExtremumResult(fCX
/ (2 * fBX
), rResults
);
963 // calculate the y-extrema parameters by zeroing first y-derivative
964 double fCY
= maControlPointA
.getY() - maStartPoint
.getY();
965 const double fBY
= fCY
+ aControlDiff
.getY();
966 const double fAY
= 3 * aControlDiff
.getY() + (maEndPoint
.getY() - maStartPoint
.getY());
968 if(fTools::equalZero(fCY
))
970 // detect fCY equal zero and truncate to real zero value in that case
974 if( !fTools::equalZero(fAY
) )
976 // derivative is polynomial of order 2 => use binomial formula
977 const double fD
= fBY
*fBY
- fAY
*fCY
;
980 const double fS
= sqrt(fD
);
981 // calculate both roots (avoiding a numerically unstable subtraction)
982 const double fQ
= fBY
+ ((fBY
>= 0) ? +fS
: -fS
);
983 impCheckExtremumResult(fQ
/ fAY
, rResults
);
984 if( !fTools::equalZero(fS
) ) // ignore root multiplicity
985 impCheckExtremumResult(fCY
/ fQ
, rResults
);
988 else if( !fTools::equalZero(fBY
) )
990 // derivative is polynomial of order 1 => one extrema
991 impCheckExtremumResult(fCY
/ (2 * fBY
), rResults
);
995 void B2DCubicBezier::transform(const basegfx::B2DHomMatrix
& rMatrix
)
997 if(rMatrix
.isIdentity())
1000 if(maControlPointA
== maStartPoint
)
1002 maControlPointA
= maStartPoint
= rMatrix
* maStartPoint
;
1006 maStartPoint
*= rMatrix
;
1007 maControlPointA
*= rMatrix
;
1010 if(maControlPointB
== maEndPoint
)
1012 maControlPointB
= maEndPoint
= rMatrix
* maEndPoint
;
1016 maEndPoint
*= rMatrix
;
1017 maControlPointB
*= rMatrix
;
1021 void B2DCubicBezier::fround()
1023 if(maControlPointA
== maStartPoint
)
1025 maControlPointA
= maStartPoint
= basegfx::B2DPoint(
1026 basegfx::fround(maStartPoint
.getX()),
1027 basegfx::fround(maStartPoint
.getY()));
1031 maStartPoint
= basegfx::B2DPoint(
1032 basegfx::fround(maStartPoint
.getX()),
1033 basegfx::fround(maStartPoint
.getY()));
1034 maControlPointA
= basegfx::B2DPoint(
1035 basegfx::fround(maControlPointA
.getX()),
1036 basegfx::fround(maControlPointA
.getY()));
1039 if(maControlPointB
== maEndPoint
)
1041 maControlPointB
= maEndPoint
= basegfx::B2DPoint(
1042 basegfx::fround(maEndPoint
.getX()),
1043 basegfx::fround(maEndPoint
.getY()));
1047 maEndPoint
= basegfx::B2DPoint(
1048 basegfx::fround(maEndPoint
.getX()),
1049 basegfx::fround(maEndPoint
.getY()));
1050 maControlPointB
= basegfx::B2DPoint(
1051 basegfx::fround(maControlPointB
.getX()),
1052 basegfx::fround(maControlPointB
.getY()));
1055 } // end of namespace basegfx
1057 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */