[2] | 1 | /****************************************************************************
|
---|
| 2 | **
|
---|
[846] | 3 | ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
---|
[561] | 4 | ** All rights reserved.
|
---|
| 5 | ** Contact: Nokia Corporation (qt-info@nokia.com)
|
---|
[2] | 6 | **
|
---|
| 7 | ** This file is part of the QtGui module of the Qt Toolkit.
|
---|
| 8 | **
|
---|
| 9 | ** $QT_BEGIN_LICENSE:LGPL$
|
---|
| 10 | ** Commercial Usage
|
---|
| 11 | ** Licensees holding valid Qt Commercial licenses may use this file in
|
---|
| 12 | ** accordance with the Qt Commercial License Agreement provided with the
|
---|
| 13 | ** Software or, alternatively, in accordance with the terms contained in
|
---|
| 14 | ** a written agreement between you and Nokia.
|
---|
| 15 | **
|
---|
| 16 | ** GNU Lesser General Public License Usage
|
---|
| 17 | ** Alternatively, this file may be used under the terms of the GNU Lesser
|
---|
| 18 | ** General Public License version 2.1 as published by the Free Software
|
---|
| 19 | ** Foundation and appearing in the file LICENSE.LGPL included in the
|
---|
| 20 | ** packaging of this file. Please review the following information to
|
---|
| 21 | ** ensure the GNU Lesser General Public License version 2.1 requirements
|
---|
| 22 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
---|
| 23 | **
|
---|
[561] | 24 | ** In addition, as a special exception, Nokia gives you certain additional
|
---|
| 25 | ** rights. These rights are described in the Nokia Qt LGPL Exception
|
---|
| 26 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
---|
[2] | 27 | **
|
---|
| 28 | ** GNU General Public License Usage
|
---|
| 29 | ** Alternatively, this file may be used under the terms of the GNU
|
---|
| 30 | ** General Public License version 3.0 as published by the Free Software
|
---|
| 31 | ** Foundation and appearing in the file LICENSE.GPL included in the
|
---|
| 32 | ** packaging of this file. Please review the following information to
|
---|
| 33 | ** ensure the GNU General Public License version 3.0 requirements will be
|
---|
| 34 | ** met: http://www.gnu.org/copyleft/gpl.html.
|
---|
| 35 | **
|
---|
[561] | 36 | ** If you have questions regarding the use of this file, please contact
|
---|
| 37 | ** Nokia at qt-info@nokia.com.
|
---|
[2] | 38 | ** $QT_END_LICENSE$
|
---|
| 39 | **
|
---|
| 40 | ****************************************************************************/
|
---|
| 41 |
|
---|
| 42 | #include "qbezier_p.h"
|
---|
| 43 | #include <qdebug.h>
|
---|
| 44 | #include <qline.h>
|
---|
| 45 | #include <qpolygon.h>
|
---|
| 46 | #include <qvector.h>
|
---|
| 47 | #include <qlist.h>
|
---|
| 48 | #include <qmath.h>
|
---|
| 49 |
|
---|
| 50 | #include <private/qnumeric_p.h>
|
---|
| 51 | #include <private/qmath_p.h>
|
---|
| 52 |
|
---|
| 53 | QT_BEGIN_NAMESPACE
|
---|
| 54 |
|
---|
| 55 | //#define QDEBUG_BEZIER
|
---|
| 56 |
|
---|
| 57 | #ifdef FLOAT_ACCURACY
|
---|
| 58 | #define INV_EPS (1L<<23)
|
---|
| 59 | #else
|
---|
| 60 | /* The value of 1.0 / (1L<<14) is enough for most applications */
|
---|
| 61 | #define INV_EPS (1L<<14)
|
---|
| 62 | #endif
|
---|
| 63 |
|
---|
| 64 | #ifndef M_SQRT2
|
---|
| 65 | #define M_SQRT2 1.41421356237309504880
|
---|
| 66 | #endif
|
---|
| 67 |
|
---|
| 68 | #define log2(x) (qLn(x)/qLn(2.))
|
---|
| 69 |
|
---|
| 70 | static inline qreal log4(qreal x)
|
---|
| 71 | {
|
---|
| 72 | return qreal(0.5) * log2(x);
|
---|
| 73 | }
|
---|
| 74 |
|
---|
| 75 | /*!
|
---|
| 76 | \internal
|
---|
| 77 | */
|
---|
| 78 | QBezier QBezier::fromPoints(const QPointF &p1, const QPointF &p2,
|
---|
| 79 | const QPointF &p3, const QPointF &p4)
|
---|
| 80 | {
|
---|
| 81 | QBezier b;
|
---|
| 82 | b.x1 = p1.x();
|
---|
| 83 | b.y1 = p1.y();
|
---|
| 84 | b.x2 = p2.x();
|
---|
| 85 | b.y2 = p2.y();
|
---|
| 86 | b.x3 = p3.x();
|
---|
| 87 | b.y3 = p3.y();
|
---|
| 88 | b.x4 = p4.x();
|
---|
| 89 | b.y4 = p4.y();
|
---|
| 90 | return b;
|
---|
| 91 | }
|
---|
| 92 |
|
---|
| 93 | /*!
|
---|
| 94 | \internal
|
---|
| 95 | */
|
---|
[846] | 96 | QPolygonF QBezier::toPolygon(qreal bezier_flattening_threshold) const
|
---|
[2] | 97 | {
|
---|
| 98 | // flattening is done by splitting the bezier until we can replace the segment by a straight
|
---|
| 99 | // line. We split further until the control points are close enough to the line connecting the
|
---|
| 100 | // boundary points.
|
---|
| 101 | //
|
---|
| 102 | // the Distance of a point p from a line given by the points (a,b) is given by:
|
---|
| 103 | //
|
---|
| 104 | // d = abs( (bx - ax)(ay - py) - (by - ay)(ax - px) ) / line_length
|
---|
| 105 | //
|
---|
| 106 | // We can stop splitting if both control points are close enough to the line.
|
---|
| 107 | // To make the algorithm faster we use the manhattan length of the line.
|
---|
| 108 |
|
---|
| 109 | QPolygonF polygon;
|
---|
| 110 | polygon.append(QPointF(x1, y1));
|
---|
[846] | 111 | addToPolygon(&polygon, bezier_flattening_threshold);
|
---|
[2] | 112 | return polygon;
|
---|
| 113 | }
|
---|
| 114 |
|
---|
[846] | 115 | QBezier QBezier::mapBy(const QTransform &transform) const
|
---|
| 116 | {
|
---|
| 117 | return QBezier::fromPoints(transform.map(pt1()), transform.map(pt2()), transform.map(pt3()), transform.map(pt4()));
|
---|
| 118 | }
|
---|
[2] | 119 |
|
---|
[846] | 120 | QBezier QBezier::getSubRange(qreal t0, qreal t1) const
|
---|
[2] | 121 | {
|
---|
[846] | 122 | QBezier result;
|
---|
| 123 | QBezier temp;
|
---|
[2] | 124 |
|
---|
[846] | 125 | // cut at t1
|
---|
| 126 | if (qFuzzyIsNull(t1 - qreal(1.))) {
|
---|
| 127 | result = *this;
|
---|
| 128 | } else {
|
---|
| 129 | temp = *this;
|
---|
| 130 | temp.parameterSplitLeft(t1, &result);
|
---|
| 131 | }
|
---|
[2] | 132 |
|
---|
[846] | 133 | // cut at t0
|
---|
| 134 | if (!qFuzzyIsNull(t0))
|
---|
| 135 | result.parameterSplitLeft(t0 / t1, &temp);
|
---|
[2] | 136 |
|
---|
[846] | 137 | return result;
|
---|
[2] | 138 | }
|
---|
| 139 |
|
---|
| 140 | static inline int quadraticRoots(qreal a, qreal b, qreal c,
|
---|
| 141 | qreal *x1, qreal *x2)
|
---|
| 142 | {
|
---|
[561] | 143 | if (qFuzzyIsNull(a)) {
|
---|
| 144 | if (qFuzzyIsNull(b))
|
---|
[2] | 145 | return 0;
|
---|
| 146 | *x1 = *x2 = (-c / b);
|
---|
| 147 | return 1;
|
---|
| 148 | } else {
|
---|
| 149 | const qreal det = b * b - 4 * a * c;
|
---|
[561] | 150 | if (qFuzzyIsNull(det)) {
|
---|
[2] | 151 | *x1 = *x2 = -b / (2 * a);
|
---|
| 152 | return 1;
|
---|
| 153 | }
|
---|
| 154 | if (det > 0) {
|
---|
[561] | 155 | if (qFuzzyIsNull(b)) {
|
---|
[2] | 156 | *x2 = qSqrt(-c / a);
|
---|
| 157 | *x1 = -(*x2);
|
---|
| 158 | return 2;
|
---|
| 159 | }
|
---|
| 160 | const qreal stableA = b / (2 * a);
|
---|
| 161 | const qreal stableB = c / (a * stableA * stableA);
|
---|
| 162 | const qreal stableC = -1 - qSqrt(1 - stableB);
|
---|
| 163 | *x2 = stableA * stableC;
|
---|
| 164 | *x1 = (stableA * stableB) / stableC;
|
---|
| 165 | return 2;
|
---|
| 166 | } else
|
---|
| 167 | return 0;
|
---|
| 168 | }
|
---|
| 169 | }
|
---|
| 170 |
|
---|
| 171 | static inline bool findInflections(qreal a, qreal b, qreal c,
|
---|
| 172 | qreal *t1 , qreal *t2, qreal *tCups)
|
---|
| 173 | {
|
---|
| 174 | qreal r1 = 0, r2 = 0;
|
---|
| 175 |
|
---|
| 176 | short rootsCount = quadraticRoots(a, b, c, &r1, &r2);
|
---|
| 177 |
|
---|
| 178 | if (rootsCount >= 1) {
|
---|
| 179 | if (r1 < r2) {
|
---|
| 180 | *t1 = r1;
|
---|
| 181 | *t2 = r2;
|
---|
| 182 | } else {
|
---|
| 183 | *t1 = r2;
|
---|
| 184 | *t2 = r1;
|
---|
| 185 | }
|
---|
[561] | 186 | if (!qFuzzyIsNull(a))
|
---|
[2] | 187 | *tCups = 0.5 * (-b / a);
|
---|
| 188 | else
|
---|
| 189 | *tCups = 2;
|
---|
| 190 |
|
---|
| 191 | return true;
|
---|
| 192 | }
|
---|
| 193 |
|
---|
| 194 | return false;
|
---|
| 195 | }
|
---|
| 196 |
|
---|
| 197 |
|
---|
[846] | 198 | void QBezier::addToPolygon(QPolygonF *polygon, qreal bezier_flattening_threshold) const
|
---|
[2] | 199 | {
|
---|
| 200 | QBezier beziers[32];
|
---|
| 201 | beziers[0] = *this;
|
---|
| 202 | QBezier *b = beziers;
|
---|
| 203 |
|
---|
| 204 | while (b >= beziers) {
|
---|
| 205 | // check if we can pop the top bezier curve from the stack
|
---|
| 206 | qreal y4y1 = b->y4 - b->y1;
|
---|
| 207 | qreal x4x1 = b->x4 - b->x1;
|
---|
| 208 | qreal l = qAbs(x4x1) + qAbs(y4y1);
|
---|
| 209 | qreal d;
|
---|
| 210 | if (l > 1.) {
|
---|
| 211 | d = qAbs( (x4x1)*(b->y1 - b->y2) - (y4y1)*(b->x1 - b->x2) )
|
---|
| 212 | + qAbs( (x4x1)*(b->y1 - b->y3) - (y4y1)*(b->x1 - b->x3) );
|
---|
| 213 | } else {
|
---|
| 214 | d = qAbs(b->x1 - b->x2) + qAbs(b->y1 - b->y2) +
|
---|
| 215 | qAbs(b->x1 - b->x3) + qAbs(b->y1 - b->y3);
|
---|
| 216 | l = 1.;
|
---|
| 217 | }
|
---|
[846] | 218 | if (d < bezier_flattening_threshold*l || b == beziers + 31) {
|
---|
[2] | 219 | // good enough, we pop it off and add the endpoint
|
---|
| 220 | polygon->append(QPointF(b->x4, b->y4));
|
---|
| 221 | --b;
|
---|
| 222 | } else {
|
---|
| 223 | // split, second half of the polygon goes lower into the stack
|
---|
| 224 | b->split(b+1, b);
|
---|
| 225 | ++b;
|
---|
| 226 | }
|
---|
| 227 | }
|
---|
| 228 | }
|
---|
| 229 |
|
---|
| 230 | QRectF QBezier::bounds() const
|
---|
| 231 | {
|
---|
| 232 | qreal xmin = x1;
|
---|
| 233 | qreal xmax = x1;
|
---|
| 234 | if (x2 < xmin)
|
---|
| 235 | xmin = x2;
|
---|
| 236 | else if (x2 > xmax)
|
---|
| 237 | xmax = x2;
|
---|
| 238 | if (x3 < xmin)
|
---|
| 239 | xmin = x3;
|
---|
| 240 | else if (x3 > xmax)
|
---|
| 241 | xmax = x3;
|
---|
| 242 | if (x4 < xmin)
|
---|
| 243 | xmin = x4;
|
---|
| 244 | else if (x4 > xmax)
|
---|
| 245 | xmax = x4;
|
---|
| 246 |
|
---|
| 247 | qreal ymin = y1;
|
---|
| 248 | qreal ymax = y1;
|
---|
| 249 | if (y2 < ymin)
|
---|
| 250 | ymin = y2;
|
---|
| 251 | else if (y2 > ymax)
|
---|
| 252 | ymax = y2;
|
---|
| 253 | if (y3 < ymin)
|
---|
| 254 | ymin = y3;
|
---|
| 255 | else if (y3 > ymax)
|
---|
| 256 | ymax = y3;
|
---|
| 257 | if (y4 < ymin)
|
---|
| 258 | ymin = y4;
|
---|
| 259 | else if (y4 > ymax)
|
---|
| 260 | ymax = y4;
|
---|
| 261 | return QRectF(xmin, ymin, xmax-xmin, ymax-ymin);
|
---|
| 262 | }
|
---|
| 263 |
|
---|
| 264 |
|
---|
| 265 | enum ShiftResult {
|
---|
| 266 | Ok,
|
---|
| 267 | Discard,
|
---|
| 268 | Split,
|
---|
| 269 | Circle
|
---|
| 270 | };
|
---|
| 271 |
|
---|
| 272 | static ShiftResult good_offset(const QBezier *b1, const QBezier *b2, qreal offset, qreal threshold)
|
---|
| 273 | {
|
---|
| 274 | const qreal o2 = offset*offset;
|
---|
| 275 | const qreal max_dist_line = threshold*offset*offset;
|
---|
| 276 | const qreal max_dist_normal = threshold*offset;
|
---|
| 277 | const qreal spacing = 0.25;
|
---|
| 278 | for (qreal i = spacing; i < 0.99; i += spacing) {
|
---|
| 279 | QPointF p1 = b1->pointAt(i);
|
---|
| 280 | QPointF p2 = b2->pointAt(i);
|
---|
| 281 | qreal d = (p1.x() - p2.x())*(p1.x() - p2.x()) + (p1.y() - p2.y())*(p1.y() - p2.y());
|
---|
| 282 | if (qAbs(d - o2) > max_dist_line)
|
---|
| 283 | return Split;
|
---|
| 284 |
|
---|
| 285 | QPointF normalPoint = b1->normalVector(i);
|
---|
| 286 | qreal l = qAbs(normalPoint.x()) + qAbs(normalPoint.y());
|
---|
| 287 | if (l != 0.) {
|
---|
| 288 | d = qAbs( normalPoint.x()*(p1.y() - p2.y()) - normalPoint.y()*(p1.x() - p2.x()) ) / l;
|
---|
| 289 | if (d > max_dist_normal)
|
---|
| 290 | return Split;
|
---|
| 291 | }
|
---|
| 292 | }
|
---|
| 293 | return Ok;
|
---|
| 294 | }
|
---|
| 295 |
|
---|
| 296 | static ShiftResult shift(const QBezier *orig, QBezier *shifted, qreal offset, qreal threshold)
|
---|
| 297 | {
|
---|
| 298 | int map[4];
|
---|
| 299 | bool p1_p2_equal = (orig->x1 == orig->x2 && orig->y1 == orig->y2);
|
---|
| 300 | bool p2_p3_equal = (orig->x2 == orig->x3 && orig->y2 == orig->y3);
|
---|
| 301 | bool p3_p4_equal = (orig->x3 == orig->x4 && orig->y3 == orig->y4);
|
---|
| 302 |
|
---|
| 303 | QPointF points[4];
|
---|
| 304 | int np = 0;
|
---|
| 305 | points[np] = QPointF(orig->x1, orig->y1);
|
---|
| 306 | map[0] = 0;
|
---|
| 307 | ++np;
|
---|
| 308 | if (!p1_p2_equal) {
|
---|
| 309 | points[np] = QPointF(orig->x2, orig->y2);
|
---|
| 310 | ++np;
|
---|
| 311 | }
|
---|
| 312 | map[1] = np - 1;
|
---|
| 313 | if (!p2_p3_equal) {
|
---|
| 314 | points[np] = QPointF(orig->x3, orig->y3);
|
---|
| 315 | ++np;
|
---|
| 316 | }
|
---|
| 317 | map[2] = np - 1;
|
---|
| 318 | if (!p3_p4_equal) {
|
---|
| 319 | points[np] = QPointF(orig->x4, orig->y4);
|
---|
| 320 | ++np;
|
---|
| 321 | }
|
---|
| 322 | map[3] = np - 1;
|
---|
| 323 | if (np == 1)
|
---|
| 324 | return Discard;
|
---|
| 325 |
|
---|
| 326 | QRectF b = orig->bounds();
|
---|
| 327 | if (np == 4 && b.width() < .1*offset && b.height() < .1*offset) {
|
---|
| 328 | qreal l = (orig->x1 - orig->x2)*(orig->x1 - orig->x2) +
|
---|
| 329 | (orig->y1 - orig->y2)*(orig->y1 - orig->y1) *
|
---|
| 330 | (orig->x3 - orig->x4)*(orig->x3 - orig->x4) +
|
---|
| 331 | (orig->y3 - orig->y4)*(orig->y3 - orig->y4);
|
---|
| 332 | qreal dot = (orig->x1 - orig->x2)*(orig->x3 - orig->x4) +
|
---|
| 333 | (orig->y1 - orig->y2)*(orig->y3 - orig->y4);
|
---|
| 334 | if (dot < 0 && dot*dot < 0.8*l)
|
---|
| 335 | // the points are close and reverse dirction. Approximate the whole
|
---|
| 336 | // thing by a semi circle
|
---|
| 337 | return Circle;
|
---|
| 338 | }
|
---|
| 339 |
|
---|
| 340 | QPointF points_shifted[4];
|
---|
| 341 |
|
---|
| 342 | QLineF prev = QLineF(QPointF(), points[1] - points[0]);
|
---|
| 343 | QPointF prev_normal = prev.normalVector().unitVector().p2();
|
---|
| 344 |
|
---|
| 345 | points_shifted[0] = points[0] + offset * prev_normal;
|
---|
| 346 |
|
---|
| 347 | for (int i = 1; i < np - 1; ++i) {
|
---|
| 348 | QLineF next = QLineF(QPointF(), points[i + 1] - points[i]);
|
---|
| 349 | QPointF next_normal = next.normalVector().unitVector().p2();
|
---|
| 350 |
|
---|
| 351 | QPointF normal_sum = prev_normal + next_normal;
|
---|
| 352 |
|
---|
| 353 | qreal r = 1.0 + prev_normal.x() * next_normal.x()
|
---|
| 354 | + prev_normal.y() * next_normal.y();
|
---|
| 355 |
|
---|
[561] | 356 | if (qFuzzyIsNull(r)) {
|
---|
[2] | 357 | points_shifted[i] = points[i] + offset * prev_normal;
|
---|
| 358 | } else {
|
---|
| 359 | qreal k = offset / r;
|
---|
| 360 | points_shifted[i] = points[i] + k * normal_sum;
|
---|
| 361 | }
|
---|
| 362 |
|
---|
| 363 | prev_normal = next_normal;
|
---|
| 364 | }
|
---|
| 365 |
|
---|
| 366 | points_shifted[np - 1] = points[np - 1] + offset * prev_normal;
|
---|
| 367 |
|
---|
| 368 | *shifted = QBezier::fromPoints(points_shifted[map[0]], points_shifted[map[1]],
|
---|
| 369 | points_shifted[map[2]], points_shifted[map[3]]);
|
---|
| 370 |
|
---|
| 371 | return good_offset(orig, shifted, offset, threshold);
|
---|
| 372 | }
|
---|
| 373 |
|
---|
| 374 | // This value is used to determine the length of control point vectors
|
---|
| 375 | // when approximating arc segments as curves. The factor is multiplied
|
---|
| 376 | // with the radius of the circle.
|
---|
| 377 | #define KAPPA 0.5522847498
|
---|
| 378 |
|
---|
| 379 |
|
---|
| 380 | static bool addCircle(const QBezier *b, qreal offset, QBezier *o)
|
---|
| 381 | {
|
---|
| 382 | QPointF normals[3];
|
---|
| 383 |
|
---|
| 384 | normals[0] = QPointF(b->y2 - b->y1, b->x1 - b->x2);
|
---|
| 385 | qreal dist = qSqrt(normals[0].x()*normals[0].x() + normals[0].y()*normals[0].y());
|
---|
[561] | 386 | if (qFuzzyIsNull(dist))
|
---|
[2] | 387 | return false;
|
---|
| 388 | normals[0] /= dist;
|
---|
| 389 | normals[2] = QPointF(b->y4 - b->y3, b->x3 - b->x4);
|
---|
| 390 | dist = qSqrt(normals[2].x()*normals[2].x() + normals[2].y()*normals[2].y());
|
---|
[561] | 391 | if (qFuzzyIsNull(dist))
|
---|
[2] | 392 | return false;
|
---|
| 393 | normals[2] /= dist;
|
---|
| 394 |
|
---|
| 395 | normals[1] = QPointF(b->x1 - b->x2 - b->x3 + b->x4, b->y1 - b->y2 - b->y3 + b->y4);
|
---|
| 396 | normals[1] /= -1*qSqrt(normals[1].x()*normals[1].x() + normals[1].y()*normals[1].y());
|
---|
| 397 |
|
---|
| 398 | qreal angles[2];
|
---|
| 399 | qreal sign = 1.;
|
---|
| 400 | for (int i = 0; i < 2; ++i) {
|
---|
| 401 | qreal cos_a = normals[i].x()*normals[i+1].x() + normals[i].y()*normals[i+1].y();
|
---|
| 402 | if (cos_a > 1.)
|
---|
| 403 | cos_a = 1.;
|
---|
| 404 | if (cos_a < -1.)
|
---|
| 405 | cos_a = -1;
|
---|
[561] | 406 | angles[i] = qAcos(cos_a)/Q_PI;
|
---|
[2] | 407 | }
|
---|
| 408 |
|
---|
| 409 | if (angles[0] + angles[1] > 1.) {
|
---|
| 410 | // more than 180 degrees
|
---|
| 411 | normals[1] = -normals[1];
|
---|
| 412 | angles[0] = 1. - angles[0];
|
---|
| 413 | angles[1] = 1. - angles[1];
|
---|
| 414 | sign = -1.;
|
---|
| 415 |
|
---|
| 416 | }
|
---|
| 417 |
|
---|
| 418 | QPointF circle[3];
|
---|
| 419 | circle[0] = QPointF(b->x1, b->y1) + normals[0]*offset;
|
---|
| 420 | circle[1] = QPointF(0.5*(b->x1 + b->x4), 0.5*(b->y1 + b->y4)) + normals[1]*offset;
|
---|
| 421 | circle[2] = QPointF(b->x4, b->y4) + normals[2]*offset;
|
---|
| 422 |
|
---|
| 423 | for (int i = 0; i < 2; ++i) {
|
---|
| 424 | qreal kappa = 2.*KAPPA * sign * offset * angles[i];
|
---|
| 425 |
|
---|
| 426 | o->x1 = circle[i].x();
|
---|
| 427 | o->y1 = circle[i].y();
|
---|
| 428 | o->x2 = circle[i].x() - normals[i].y()*kappa;
|
---|
| 429 | o->y2 = circle[i].y() + normals[i].x()*kappa;
|
---|
| 430 | o->x3 = circle[i+1].x() + normals[i+1].y()*kappa;
|
---|
| 431 | o->y3 = circle[i+1].y() - normals[i+1].x()*kappa;
|
---|
| 432 | o->x4 = circle[i+1].x();
|
---|
| 433 | o->y4 = circle[i+1].y();
|
---|
| 434 |
|
---|
| 435 | ++o;
|
---|
| 436 | }
|
---|
| 437 | return true;
|
---|
| 438 | }
|
---|
| 439 |
|
---|
| 440 | int QBezier::shifted(QBezier *curveSegments, int maxSegments, qreal offset, float threshold) const
|
---|
| 441 | {
|
---|
| 442 | Q_ASSERT(curveSegments);
|
---|
| 443 | Q_ASSERT(maxSegments > 0);
|
---|
| 444 |
|
---|
| 445 | if (x1 == x2 && x1 == x3 && x1 == x4 &&
|
---|
| 446 | y1 == y2 && y1 == y3 && y1 == y4)
|
---|
| 447 | return 0;
|
---|
| 448 |
|
---|
| 449 | --maxSegments;
|
---|
| 450 | QBezier beziers[10];
|
---|
| 451 | redo:
|
---|
| 452 | beziers[0] = *this;
|
---|
| 453 | QBezier *b = beziers;
|
---|
| 454 | QBezier *o = curveSegments;
|
---|
| 455 |
|
---|
| 456 | while (b >= beziers) {
|
---|
| 457 | int stack_segments = b - beziers + 1;
|
---|
| 458 | if ((stack_segments == 10) || (o - curveSegments == maxSegments - stack_segments)) {
|
---|
| 459 | threshold *= 1.5;
|
---|
| 460 | if (threshold > 2.)
|
---|
| 461 | goto give_up;
|
---|
| 462 | goto redo;
|
---|
| 463 | }
|
---|
| 464 | ShiftResult res = shift(b, o, offset, threshold);
|
---|
| 465 | if (res == Discard) {
|
---|
| 466 | --b;
|
---|
| 467 | } else if (res == Ok) {
|
---|
| 468 | ++o;
|
---|
| 469 | --b;
|
---|
| 470 | continue;
|
---|
| 471 | } else if (res == Circle && maxSegments - (o - curveSegments) >= 2) {
|
---|
| 472 | // add semi circle
|
---|
| 473 | if (addCircle(b, offset, o))
|
---|
| 474 | o += 2;
|
---|
| 475 | --b;
|
---|
| 476 | } else {
|
---|
| 477 | b->split(b+1, b);
|
---|
| 478 | ++b;
|
---|
| 479 | }
|
---|
| 480 | }
|
---|
| 481 |
|
---|
| 482 | give_up:
|
---|
| 483 | while (b >= beziers) {
|
---|
| 484 | ShiftResult res = shift(b, o, offset, threshold);
|
---|
| 485 |
|
---|
| 486 | // if res isn't Ok or Split then *o is undefined
|
---|
| 487 | if (res == Ok || res == Split)
|
---|
| 488 | ++o;
|
---|
| 489 |
|
---|
| 490 | --b;
|
---|
| 491 | }
|
---|
| 492 |
|
---|
| 493 | Q_ASSERT(o - curveSegments <= maxSegments);
|
---|
| 494 | return o - curveSegments;
|
---|
| 495 | }
|
---|
| 496 |
|
---|
| 497 | #ifdef QDEBUG_BEZIER
|
---|
| 498 | static QDebug operator<<(QDebug dbg, const QBezier &bz)
|
---|
| 499 | {
|
---|
[561] | 500 | dbg << '[' << bz.x1<< ", " << bz.y1 << "], "
|
---|
| 501 | << '[' << bz.x2 <<", " << bz.y2 << "], "
|
---|
| 502 | << '[' << bz.x3 <<", " << bz.y3 << "], "
|
---|
| 503 | << '[' << bz.x4 <<", " << bz.y4 << ']';
|
---|
[2] | 504 | return dbg;
|
---|
| 505 | }
|
---|
| 506 | #endif
|
---|
| 507 |
|
---|
| 508 | static inline void splitBezierAt(const QBezier &bez, qreal t,
|
---|
| 509 | QBezier *left, QBezier *right)
|
---|
| 510 | {
|
---|
| 511 | left->x1 = bez.x1;
|
---|
| 512 | left->y1 = bez.y1;
|
---|
| 513 |
|
---|
| 514 | left->x2 = bez.x1 + t * ( bez.x2 - bez.x1 );
|
---|
| 515 | left->y2 = bez.y1 + t * ( bez.y2 - bez.y1 );
|
---|
| 516 |
|
---|
| 517 | left->x3 = bez.x2 + t * ( bez.x3 - bez.x2 ); // temporary holding spot
|
---|
| 518 | left->y3 = bez.y2 + t * ( bez.y3 - bez.y2 ); // temporary holding spot
|
---|
| 519 |
|
---|
| 520 | right->x3 = bez.x3 + t * ( bez.x4 - bez.x3 );
|
---|
| 521 | right->y3 = bez.y3 + t * ( bez.y4 - bez.y3 );
|
---|
| 522 |
|
---|
| 523 | right->x2 = left->x3 + t * ( right->x3 - left->x3);
|
---|
| 524 | right->y2 = left->y3 + t * ( right->y3 - left->y3);
|
---|
| 525 |
|
---|
| 526 | left->x3 = left->x2 + t * ( left->x3 - left->x2 );
|
---|
| 527 | left->y3 = left->y2 + t * ( left->y3 - left->y2 );
|
---|
| 528 |
|
---|
| 529 | left->x4 = right->x1 = left->x3 + t * (right->x2 - left->x3);
|
---|
| 530 | left->y4 = right->y1 = left->y3 + t * (right->y2 - left->y3);
|
---|
| 531 |
|
---|
| 532 | right->x4 = bez.x4;
|
---|
| 533 | right->y4 = bez.y4;
|
---|
| 534 | }
|
---|
| 535 |
|
---|
| 536 | qreal QBezier::length(qreal error) const
|
---|
| 537 | {
|
---|
| 538 | qreal length = 0.0;
|
---|
| 539 |
|
---|
| 540 | addIfClose(&length, error);
|
---|
| 541 |
|
---|
| 542 | return length;
|
---|
| 543 | }
|
---|
| 544 |
|
---|
| 545 | void QBezier::addIfClose(qreal *length, qreal error) const
|
---|
| 546 | {
|
---|
| 547 | QBezier left, right; /* bez poly splits */
|
---|
| 548 |
|
---|
| 549 | qreal len = 0.0; /* arc length */
|
---|
| 550 | qreal chord; /* chord length */
|
---|
| 551 |
|
---|
| 552 | len = len + QLineF(QPointF(x1, y1),QPointF(x2, y2)).length();
|
---|
| 553 | len = len + QLineF(QPointF(x2, y2),QPointF(x3, y3)).length();
|
---|
| 554 | len = len + QLineF(QPointF(x3, y3),QPointF(x4, y4)).length();
|
---|
| 555 |
|
---|
| 556 | chord = QLineF(QPointF(x1, y1),QPointF(x4, y4)).length();
|
---|
| 557 |
|
---|
| 558 | if((len-chord) > error) {
|
---|
| 559 | split(&left, &right); /* split in two */
|
---|
| 560 | left.addIfClose(length, error); /* try left side */
|
---|
| 561 | right.addIfClose(length, error); /* try right side */
|
---|
| 562 | return;
|
---|
| 563 | }
|
---|
| 564 |
|
---|
| 565 | *length = *length + len;
|
---|
| 566 |
|
---|
| 567 | return;
|
---|
| 568 | }
|
---|
| 569 |
|
---|
| 570 | qreal QBezier::tForY(qreal t0, qreal t1, qreal y) const
|
---|
| 571 | {
|
---|
| 572 | qreal py0 = pointAt(t0).y();
|
---|
| 573 | qreal py1 = pointAt(t1).y();
|
---|
| 574 |
|
---|
| 575 | if (py0 > py1) {
|
---|
| 576 | qSwap(py0, py1);
|
---|
| 577 | qSwap(t0, t1);
|
---|
| 578 | }
|
---|
| 579 |
|
---|
| 580 | Q_ASSERT(py0 <= py1);
|
---|
| 581 |
|
---|
| 582 | if (py0 >= y)
|
---|
| 583 | return t0;
|
---|
| 584 | else if (py1 <= y)
|
---|
| 585 | return t1;
|
---|
| 586 |
|
---|
| 587 | Q_ASSERT(py0 < y && y < py1);
|
---|
| 588 |
|
---|
| 589 | qreal lt = t0;
|
---|
| 590 | qreal dt;
|
---|
| 591 | do {
|
---|
| 592 | qreal t = 0.5 * (t0 + t1);
|
---|
| 593 |
|
---|
| 594 | qreal a, b, c, d;
|
---|
| 595 | QBezier::coefficients(t, a, b, c, d);
|
---|
| 596 | qreal yt = a * y1 + b * y2 + c * y3 + d * y4;
|
---|
| 597 |
|
---|
| 598 | if (yt < y) {
|
---|
| 599 | t0 = t;
|
---|
| 600 | py0 = yt;
|
---|
| 601 | } else {
|
---|
| 602 | t1 = t;
|
---|
| 603 | py1 = yt;
|
---|
| 604 | }
|
---|
| 605 | dt = lt - t;
|
---|
| 606 | lt = t;
|
---|
| 607 | } while (qAbs(dt) > 1e-7);
|
---|
| 608 |
|
---|
| 609 | return t0;
|
---|
| 610 | }
|
---|
| 611 |
|
---|
| 612 | int QBezier::stationaryYPoints(qreal &t0, qreal &t1) const
|
---|
| 613 | {
|
---|
| 614 | // y(t) = (1 - t)^3 * y1 + 3 * (1 - t)^2 * t * y2 + 3 * (1 - t) * t^2 * y3 + t^3 * y4
|
---|
| 615 | // y'(t) = 3 * (-(1-2t+t^2) * y1 + (1 - 4 * t + 3 * t^2) * y2 + (2 * t - 3 * t^2) * y3 + t^2 * y4)
|
---|
| 616 | // y'(t) = 3 * ((-y1 + 3 * y2 - 3 * y3 + y4)t^2 + (2 * y1 - 4 * y2 + 2 * y3)t + (-y1 + y2))
|
---|
| 617 |
|
---|
| 618 | const qreal a = -y1 + 3 * y2 - 3 * y3 + y4;
|
---|
| 619 | const qreal b = 2 * y1 - 4 * y2 + 2 * y3;
|
---|
| 620 | const qreal c = -y1 + y2;
|
---|
| 621 |
|
---|
[846] | 622 | if (qFuzzyIsNull(a)) {
|
---|
| 623 | if (qFuzzyIsNull(b))
|
---|
| 624 | return 0;
|
---|
| 625 |
|
---|
| 626 | t0 = -c / b;
|
---|
| 627 | return t0 > 0 && t0 < 1;
|
---|
| 628 | }
|
---|
| 629 |
|
---|
[2] | 630 | qreal reciprocal = b * b - 4 * a * c;
|
---|
| 631 |
|
---|
[561] | 632 | if (qFuzzyIsNull(reciprocal)) {
|
---|
[2] | 633 | t0 = -b / (2 * a);
|
---|
[846] | 634 | return t0 > 0 && t0 < 1;
|
---|
[2] | 635 | } else if (reciprocal > 0) {
|
---|
| 636 | qreal temp = qSqrt(reciprocal);
|
---|
| 637 |
|
---|
| 638 | t0 = (-b - temp)/(2*a);
|
---|
| 639 | t1 = (-b + temp)/(2*a);
|
---|
| 640 |
|
---|
| 641 | if (t1 < t0)
|
---|
| 642 | qSwap(t0, t1);
|
---|
| 643 |
|
---|
| 644 | int count = 0;
|
---|
| 645 | qreal t[2] = { 0, 1 };
|
---|
| 646 |
|
---|
| 647 | if (t0 > 0 && t0 < 1)
|
---|
| 648 | t[count++] = t0;
|
---|
| 649 | if (t1 > 0 && t1 < 1)
|
---|
| 650 | t[count++] = t1;
|
---|
| 651 |
|
---|
| 652 | t0 = t[0];
|
---|
| 653 | t1 = t[1];
|
---|
| 654 |
|
---|
| 655 | return count;
|
---|
| 656 | }
|
---|
| 657 |
|
---|
| 658 | return 0;
|
---|
| 659 | }
|
---|
| 660 |
|
---|
| 661 | qreal QBezier::tAtLength(qreal l) const
|
---|
| 662 | {
|
---|
| 663 | qreal len = length();
|
---|
| 664 | qreal t = 1.0;
|
---|
| 665 | const qreal error = (qreal)0.01;
|
---|
| 666 | if (l > len || qFuzzyCompare(l, len))
|
---|
| 667 | return t;
|
---|
| 668 |
|
---|
| 669 | t *= 0.5;
|
---|
| 670 | //int iters = 0;
|
---|
| 671 | //qDebug()<<"LEN is "<<l<<len;
|
---|
| 672 | qreal lastBigger = 1.;
|
---|
| 673 | while (1) {
|
---|
| 674 | //qDebug()<<"\tt is "<<t;
|
---|
| 675 | QBezier right = *this;
|
---|
| 676 | QBezier left;
|
---|
| 677 | right.parameterSplitLeft(t, &left);
|
---|
| 678 | qreal lLen = left.length();
|
---|
| 679 | if (qAbs(lLen - l) < error)
|
---|
| 680 | break;
|
---|
| 681 |
|
---|
| 682 | if (lLen < l) {
|
---|
| 683 | t += (lastBigger - t)*.5;
|
---|
| 684 | } else {
|
---|
| 685 | lastBigger = t;
|
---|
| 686 | t -= t*.5;
|
---|
| 687 | }
|
---|
| 688 | //++iters;
|
---|
| 689 | }
|
---|
| 690 | //qDebug()<<"number of iters is "<<iters;
|
---|
| 691 | return t;
|
---|
| 692 | }
|
---|
| 693 |
|
---|
| 694 | QBezier QBezier::bezierOnInterval(qreal t0, qreal t1) const
|
---|
| 695 | {
|
---|
| 696 | if (t0 == 0 && t1 == 1)
|
---|
| 697 | return *this;
|
---|
| 698 |
|
---|
| 699 | QBezier bezier = *this;
|
---|
| 700 |
|
---|
| 701 | QBezier result;
|
---|
| 702 | bezier.parameterSplitLeft(t0, &result);
|
---|
| 703 | qreal trueT = (t1-t0)/(1-t0);
|
---|
| 704 | bezier.parameterSplitLeft(trueT, &result);
|
---|
| 705 |
|
---|
| 706 | return result;
|
---|
| 707 | }
|
---|
| 708 |
|
---|
| 709 | QT_END_NAMESPACE
|
---|