nss: upgrade to release 3.73
[LibreOffice.git] / basegfx / source / curve / b2dcubicbezier.cxx
blob046f0cf30e179e1beea529e2c191e7a8ab267c4e
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
28 #include <limits>
30 // #i37443#
31 #define FACTOR_FOR_UNSHARPEN (1.6)
32 #ifdef DBG_UTIL
33 const double fMultFactUnsharpen = FACTOR_FOR_UNSHARPEN;
34 #endif
36 namespace basegfx
38 namespace
40 void ImpSubDivAngle(
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)
52 // do angle test
53 B2DVector aLeft(rfEA - rfPA);
54 B2DVector aRight(rfEB - rfPB);
56 // #i72104#
57 if(aLeft.equalZero())
59 aLeft = rfEB - rfPA;
62 if(aRight.equalZero())
64 aRight = rfEA - rfPB;
67 const double fCurrentAngle(aLeft.angle(aRight));
69 if(fabs(fCurrentAngle) > (F_PI - fAngleBound))
71 // end recursion
72 nMaxRecursionDepth = 0;
74 else
76 if(bAllowUnsharpen)
78 // #i37443# unsharpen criteria
79 #ifdef DBG_UTIL
80 fAngleBound *= fMultFactUnsharpen;
81 #else
82 fAngleBound *= FACTOR_FOR_UNSHARPEN;
83 #endif
88 if(nMaxRecursionDepth)
90 // divide at 0.5
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));
98 // left recursion
99 ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1);
101 // right recursion
102 ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1);
104 else
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;
129 else
131 const B2DVector aBase(rfPB - rfPA);
132 const bool bBaseEqualZero(aBase.equalZero()); // #i72104#
134 if(!bBaseEqualZero)
136 const bool bLeftParallel(bLeftEqualZero || areParallel(aLeft, aBase));
137 const bool bRightParallel(bRightEqualZero || areParallel(aRight, aBase));
139 if(bLeftParallel && bRightParallel)
141 bAllParallel = true;
143 if(!bLeftEqualZero)
145 double fFactor;
147 if(fabs(aBase.getX()) > fabs(aBase.getY()))
149 fFactor = aLeft.getX() / aBase.getX();
151 else
153 fFactor = aLeft.getY() / aBase.getY();
156 if(fFactor >= 0.0 && fFactor <= 1.0)
158 bLeftEqualZero = true;
162 if(!bRightEqualZero)
164 double fFactor;
166 if(fabs(aBase.getX()) > fabs(aBase.getY()))
168 fFactor = aRight.getX() / -aBase.getX();
170 else
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));
199 // test left
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));
209 // test right
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;
224 else
226 // left
227 if(bAngleIsSmallerLeft)
229 rTarget.append(aS3C);
231 else
233 ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, rfAngleBound, true/*bAllowUnsharpen*/, nMaxRecursionDepth);
236 // right
237 if(bAngleIsSmallerRight)
239 rTarget.append(rfPB);
241 else
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)||
273 // 0<=j<=n
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);
290 if(bFurtherDivision)
292 // remember last error value
293 fLastDistanceError2 = fDistanceError2;
295 else
297 // stop recursion
298 nMaxRecursionDepth = 0;
302 if(nMaxRecursionDepth)
304 // divide at 0.5
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));
312 // left recursion
313 ImpSubDivDistance(rfPA, aS1L, aS2L, aS3C, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1);
315 // right recursion
316 ImpSubDivDistance(aS3C, aS2R, aS1R, rfPB, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1);
318 else
320 rTarget.append(rfPB);
323 } // end of anonymous namespace
324 } // end of namespace basegfx
326 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),
334 maEndPoint(rEnd),
335 maControlPointA(rControlPointA),
336 maControlPointB(rControlPointB)
340 B2DCubicBezier::~B2DCubicBezier() = default;
342 // assignment operator
343 B2DCubicBezier& B2DCubicBezier::operator=(const B2DCubicBezier&) = default;
345 // compare operators
346 bool B2DCubicBezier::operator==(const B2DCubicBezier& rBezier) const
348 return (
349 maStartPoint == rBezier.maStartPoint
350 && maEndPoint == rBezier.maEndPoint
351 && maControlPointA == rBezier.maControlPointA
352 && maControlPointB == rBezier.maControlPointB
356 bool B2DCubicBezier::operator!=(const B2DCubicBezier& rBezier) const
358 return (
359 maStartPoint != rBezier.maStartPoint
360 || maEndPoint != rBezier.maEndPoint
361 || maControlPointA != rBezier.maControlPointA
362 || maControlPointB != rBezier.maControlPointB
366 bool B2DCubicBezier::equal(const B2DCubicBezier& rBezier) const
368 return (
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)
385 return;
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())
392 return;
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
410 ? 1.0
411 : 1.0 / aEdge.getLength());
413 // if A is not zero, check if it could be
414 if(!bAIsTrivial)
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))
430 bAIsTrivial = true;
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))
452 bBIsTrivial = true;
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;
466 namespace {
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;
477 else
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
493 if(isBezier())
495 if(fDeviation < 0.00000001)
497 fDeviation = 0.00000001;
500 return impGetLength(*this, fDeviation, 6);
502 else
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());
524 else
526 return getEdgeLength();
530 void B2DCubicBezier::adaptiveSubdivideByAngle(B2DPolygon& rTarget, double fAngleBound) const
532 if(isBezier())
534 // use support method #i37443# and allow unsharpen the criteria
535 ImpSubDivAngleStart(maStartPoint, maControlPointA, maControlPointB, maEndPoint, rTarget,
536 deg2rad(fAngleBound));
538 else
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())
553 return aTangent;
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())
562 return aTangent;
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())
575 return aTangent;
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())
584 return aTangent;
587 // not a bezier at all, return edge vector
588 return (getEndPoint() - getStartPoint()) * 0.3;
590 else
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
617 if(isBezier())
619 ImpSubDivDistance(maStartPoint, maControlPointA, maControlPointB, maEndPoint, rTarget,
620 fDistanceBound * fDistanceBound, std::numeric_limits<double>::max(), 30);
622 else
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 (!)");
632 if(isBezier())
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);
642 else
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());
661 double fNewQuadDist;
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;
672 nSmallestIndex = a;
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));
680 while(true)
682 // test left
683 double fPosLeft(fPosition - fStepValue);
685 if(fPosLeft < 0.0)
687 fPosLeft = 0.0;
688 aVector = B2DVector(rTestPoint - maStartPoint);
690 else
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;
702 else
704 // test right
705 double fPosRight(fPosition + fStepValue);
707 if(fPosRight > 1.0)
709 fPosRight = 1.0;
710 aVector = B2DVector(rTestPoint - maEndPoint);
712 else
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;
724 else
726 // not less left or right, done
727 break;
731 if(fPosition == 0.0 || fPosition == 1.0)
733 // if we are completely left or right, we are done
734 break;
737 // prepare next step value
738 fStepValue /= 2.0;
741 rCut = fPosition;
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)
751 return;
754 if(isBezier())
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));
763 if(pBezierA)
765 pBezierA->setStartPoint(maStartPoint);
766 pBezierA->setEndPoint(aS3C);
767 pBezierA->setControlPointA(aS1L);
768 pBezierA->setControlPointB(aS2L);
771 if(pBezierB)
773 pBezierB->setStartPoint(aS3C);
774 pBezierB->setEndPoint(maEndPoint);
775 pBezierB->setControlPointA(aS2R);
776 pBezierB->setControlPointB(aS1R);
779 else
781 const B2DPoint aSplit(interpolate(maStartPoint, maEndPoint, t));
783 if(pBezierA)
785 pBezierA->setStartPoint(maStartPoint);
786 pBezierA->setEndPoint(aSplit);
787 pBezierA->setControlPointA(maStartPoint);
788 pBezierA->setControlPointB(aSplit);
791 if(pBezierB)
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))
807 fStart = 1.0;
809 else if(fTools::less(fStart, 0.0))
811 fStart = 0.0;
814 if(fTools::more(fEnd, 1.0))
816 fEnd = 1.0;
818 else if(fTools::less(fEnd, 0.0))
820 fEnd = 0.0;
823 if(fEnd <= fStart)
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);
833 else
835 if(isBezier())
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));
841 aRetval = *this;
843 if(!bEndIsOne)
845 aRetval.split(fEnd, &aRetval, nullptr);
847 if(!bStartIsZero)
849 fStart /= fEnd;
853 if(!bStartIsZero)
855 aRetval.split(fStart, nullptr, &aRetval);
858 else
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);
870 return aRetval;
873 B2DRange B2DCubicBezier::getRange() const
875 B2DRange aRetval(maStartPoint, maEndPoint);
877 aRetval.expand(maControlPointA);
878 aRetval.expand(maControlPointB);
880 return aRetval;
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());
892 if(!nCount)
894 return false;
896 else if(nCount == 1)
898 rfResult = aAllResults[0];
899 return true;
901 else
903 rfResult = *(std::min_element(aAllResults.begin(), aAllResults.end()));
904 return true;
908 namespace
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
927 rResults.clear();
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
940 fCX = 0.0;
943 if( !fTools::equalZero(fAX) )
945 // derivative is polynomial of order 2 => use binomial formula
946 const double fD = fBX*fBX - fAX*fCX;
947 if( fD >= 0.0 )
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
971 fCY = 0.0;
974 if( !fTools::equalZero(fAY) )
976 // derivative is polynomial of order 2 => use binomial formula
977 const double fD = fBY*fBY - fAY*fCY;
978 if( fD >= 0.0 )
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())
998 return;
1000 if(maControlPointA == maStartPoint)
1002 maControlPointA = maStartPoint = rMatrix * maStartPoint;
1004 else
1006 maStartPoint *= rMatrix;
1007 maControlPointA *= rMatrix;
1010 if(maControlPointB == maEndPoint)
1012 maControlPointB = maEndPoint = rMatrix * maEndPoint;
1014 else
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()));
1029 else
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()));
1045 else
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: */