mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
964e86929d
Differential Revision: https://phabricator.services.mozilla.com/D158752
327 lines
11 KiB
C++
327 lines
11 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "BezierUtils.h"
|
|
|
|
#include "PathHelpers.h"
|
|
|
|
namespace mozilla {
|
|
namespace gfx {
|
|
|
|
Point GetBezierPoint(const Bezier& aBezier, Float t) {
|
|
Float s = 1.0f - t;
|
|
|
|
return Point(aBezier.mPoints[0].x * s * s * s +
|
|
3.0f * aBezier.mPoints[1].x * t * s * s +
|
|
3.0f * aBezier.mPoints[2].x * t * t * s +
|
|
aBezier.mPoints[3].x * t * t * t,
|
|
aBezier.mPoints[0].y * s * s * s +
|
|
3.0f * aBezier.mPoints[1].y * t * s * s +
|
|
3.0f * aBezier.mPoints[2].y * t * t * s +
|
|
aBezier.mPoints[3].y * t * t * t);
|
|
}
|
|
|
|
Point GetBezierDifferential(const Bezier& aBezier, Float t) {
|
|
// Return P'(t).
|
|
|
|
Float s = 1.0f - t;
|
|
|
|
return Point(
|
|
-3.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s * s +
|
|
2.0f * (aBezier.mPoints[1].x - aBezier.mPoints[2].x) * t * s +
|
|
(aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t * t),
|
|
-3.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s * s +
|
|
2.0f * (aBezier.mPoints[1].y - aBezier.mPoints[2].y) * t * s +
|
|
(aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t * t));
|
|
}
|
|
|
|
Point GetBezierDifferential2(const Bezier& aBezier, Float t) {
|
|
// Return P''(t).
|
|
|
|
Float s = 1.0f - t;
|
|
|
|
return Point(6.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s -
|
|
(aBezier.mPoints[1].x - aBezier.mPoints[2].x) * (s - t) -
|
|
(aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t),
|
|
6.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s -
|
|
(aBezier.mPoints[1].y - aBezier.mPoints[2].y) * (s - t) -
|
|
(aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t));
|
|
}
|
|
|
|
Float GetBezierLength(const Bezier& aBezier, Float a, Float b) {
|
|
if (a < 0.5f && b > 0.5f) {
|
|
// To increase the accuracy, split into two parts.
|
|
return GetBezierLength(aBezier, a, 0.5f) +
|
|
GetBezierLength(aBezier, 0.5f, b);
|
|
}
|
|
|
|
// Calculate length of simple bezier curve with Simpson's rule.
|
|
// _
|
|
// / b
|
|
// length = | |P'(x)| dx
|
|
// _/ a
|
|
//
|
|
// b - a a + b
|
|
// = ----- [ |P'(a)| + 4 |P'(-----)| + |P'(b)| ]
|
|
// 6 2
|
|
|
|
Float fa = GetBezierDifferential(aBezier, a).Length();
|
|
Float fab = GetBezierDifferential(aBezier, (a + b) / 2.0f).Length();
|
|
Float fb = GetBezierDifferential(aBezier, b).Length();
|
|
|
|
return (b - a) / 6.0f * (fa + 4.0f * fab + fb);
|
|
}
|
|
|
|
static void SplitBezierA(Bezier* aSubBezier, const Bezier& aBezier, Float t) {
|
|
// Split bezier curve into [0,t] and [t,1] parts, and return [0,t] part.
|
|
|
|
Float s = 1.0f - t;
|
|
|
|
Point tmp1;
|
|
Point tmp2;
|
|
|
|
aSubBezier->mPoints[0] = aBezier.mPoints[0];
|
|
|
|
aSubBezier->mPoints[1] = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t;
|
|
tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t;
|
|
tmp2 = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t;
|
|
|
|
aSubBezier->mPoints[2] = aSubBezier->mPoints[1] * s + tmp1 * t;
|
|
tmp1 = tmp1 * s + tmp2 * t;
|
|
|
|
aSubBezier->mPoints[3] = aSubBezier->mPoints[2] * s + tmp1 * t;
|
|
}
|
|
|
|
static void SplitBezierB(Bezier* aSubBezier, const Bezier& aBezier, Float t) {
|
|
// Split bezier curve into [0,t] and [t,1] parts, and return [t,1] part.
|
|
|
|
Float s = 1.0f - t;
|
|
|
|
Point tmp1;
|
|
Point tmp2;
|
|
|
|
aSubBezier->mPoints[3] = aBezier.mPoints[3];
|
|
|
|
aSubBezier->mPoints[2] = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t;
|
|
tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t;
|
|
tmp2 = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t;
|
|
|
|
aSubBezier->mPoints[1] = tmp1 * s + aSubBezier->mPoints[2] * t;
|
|
tmp1 = tmp2 * s + tmp1 * t;
|
|
|
|
aSubBezier->mPoints[0] = tmp1 * s + aSubBezier->mPoints[1] * t;
|
|
}
|
|
|
|
void GetSubBezier(Bezier* aSubBezier, const Bezier& aBezier, Float t1,
|
|
Float t2) {
|
|
Bezier tmp;
|
|
SplitBezierB(&tmp, aBezier, t1);
|
|
|
|
Float range = 1.0f - t1;
|
|
if (range == 0.0f) {
|
|
*aSubBezier = tmp;
|
|
} else {
|
|
SplitBezierA(aSubBezier, tmp, (t2 - t1) / range);
|
|
}
|
|
}
|
|
|
|
static Point BisectBezierNearestPoint(const Bezier& aBezier,
|
|
const Point& aTarget, Float* aT) {
|
|
// Find a nearest point on bezier curve with Binary search.
|
|
// Called from FindBezierNearestPoint.
|
|
|
|
Float lower = 0.0f;
|
|
Float upper = 1.0f;
|
|
Float t;
|
|
|
|
Point P, lastP;
|
|
const size_t MAX_LOOP = 32;
|
|
const Float DIST_MARGIN = 0.1f;
|
|
const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN;
|
|
const Float DIFF = 0.0001f;
|
|
for (size_t i = 0; i < MAX_LOOP; i++) {
|
|
t = (upper + lower) / 2.0f;
|
|
P = GetBezierPoint(aBezier, t);
|
|
|
|
// Check if it converged.
|
|
if (i > 0 && (lastP - P).LengthSquare() < DIST_MARGIN_SQUARE) {
|
|
break;
|
|
}
|
|
|
|
Float distSquare = (P - aTarget).LengthSquare();
|
|
if ((GetBezierPoint(aBezier, t + DIFF) - aTarget).LengthSquare() <
|
|
distSquare) {
|
|
lower = t;
|
|
} else if ((GetBezierPoint(aBezier, t - DIFF) - aTarget).LengthSquare() <
|
|
distSquare) {
|
|
upper = t;
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
lastP = P;
|
|
}
|
|
|
|
if (aT) {
|
|
*aT = t;
|
|
}
|
|
|
|
return P;
|
|
}
|
|
|
|
Point FindBezierNearestPoint(const Bezier& aBezier, const Point& aTarget,
|
|
Float aInitialT, Float* aT) {
|
|
// Find a nearest point on bezier curve with Newton's method.
|
|
// It converges within 4 iterations in most cases.
|
|
//
|
|
// f(t_n)
|
|
// t_{n+1} = t_n - ---------
|
|
// f'(t_n)
|
|
//
|
|
// d 2
|
|
// f(t) = ---- | P(t) - aTarget |
|
|
// dt
|
|
|
|
Float t = aInitialT;
|
|
Point P;
|
|
Point lastP = GetBezierPoint(aBezier, t);
|
|
|
|
const size_t MAX_LOOP = 4;
|
|
const Float DIST_MARGIN = 0.1f;
|
|
const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN;
|
|
for (size_t i = 0; i <= MAX_LOOP; i++) {
|
|
Point dP = GetBezierDifferential(aBezier, t);
|
|
Point ddP = GetBezierDifferential2(aBezier, t);
|
|
Float f = 2.0f * (lastP.DotProduct(dP) - aTarget.DotProduct(dP));
|
|
Float df = 2.0f * (dP.DotProduct(dP) + lastP.DotProduct(ddP) -
|
|
aTarget.DotProduct(ddP));
|
|
t = t - f / df;
|
|
P = GetBezierPoint(aBezier, t);
|
|
if ((P - lastP).LengthSquare() < DIST_MARGIN_SQUARE) {
|
|
break;
|
|
}
|
|
lastP = P;
|
|
|
|
if (i == MAX_LOOP) {
|
|
// If aInitialT is too bad, it won't converge in a few iterations,
|
|
// fallback to binary search.
|
|
return BisectBezierNearestPoint(aBezier, aTarget, aT);
|
|
}
|
|
}
|
|
|
|
if (aT) {
|
|
*aT = t;
|
|
}
|
|
|
|
return P;
|
|
}
|
|
|
|
void GetBezierPointsForCorner(Bezier* aBezier, Corner aCorner,
|
|
const Point& aCornerPoint,
|
|
const Size& aCornerSize) {
|
|
// Calculate bezier control points for elliptic arc.
|
|
|
|
const Float signsList[4][2] = {
|
|
{+1.0f, +1.0f}, {-1.0f, +1.0f}, {-1.0f, -1.0f}, {+1.0f, -1.0f}};
|
|
const Float(&signs)[2] = signsList[aCorner];
|
|
|
|
aBezier->mPoints[0] = aCornerPoint;
|
|
aBezier->mPoints[0].x += signs[0] * aCornerSize.width;
|
|
|
|
aBezier->mPoints[1] = aBezier->mPoints[0];
|
|
aBezier->mPoints[1].x -= signs[0] * aCornerSize.width * kKappaFactor;
|
|
|
|
aBezier->mPoints[3] = aCornerPoint;
|
|
aBezier->mPoints[3].y += signs[1] * aCornerSize.height;
|
|
|
|
aBezier->mPoints[2] = aBezier->mPoints[3];
|
|
aBezier->mPoints[2].y -= signs[1] * aCornerSize.height * kKappaFactor;
|
|
}
|
|
|
|
Float GetQuarterEllipticArcLength(Float a, Float b) {
|
|
// Calculate the approximate length of a quarter elliptic arc formed by radii
|
|
// (a, b), by Ramanujan's approximation of the perimeter p of an ellipse.
|
|
// _ _
|
|
// | 2 |
|
|
// | 3 * (a - b) |
|
|
// p = PI | (a + b) + ------------------------------------------- |
|
|
// | 2 2 |
|
|
// |_ 10 * (a + b) + sqrt(a + 14 * a * b + b ) _|
|
|
//
|
|
// _ _
|
|
// | 2 |
|
|
// | 3 * (a - b) |
|
|
// = PI | (a + b) + -------------------------------------------------- |
|
|
// | 2 2 |
|
|
// |_ 10 * (a + b) + sqrt(4 * (a + b) - 3 * (a - b) ) _|
|
|
//
|
|
// _ _
|
|
// | 2 |
|
|
// | 3 * S |
|
|
// = PI | A + -------------------------------------- |
|
|
// | 2 2 |
|
|
// |_ 10 * A + sqrt(4 * A - 3 * S ) _|
|
|
//
|
|
// where A = a + b, S = a - b
|
|
|
|
Float A = a + b, S = a - b;
|
|
Float A2 = A * A, S2 = S * S;
|
|
Float p = M_PI * (A + 3.0f * S2 / (10.0f * A + sqrt(4.0f * A2 - 3.0f * S2)));
|
|
return p / 4.0f;
|
|
}
|
|
|
|
Float CalculateDistanceToEllipticArc(const Point& P, const Point& normal,
|
|
const Point& origin, Float width,
|
|
Float height) {
|
|
// Solve following equations with n and return smaller n.
|
|
//
|
|
// / (x, y) = P + n * normal
|
|
// |
|
|
// < _ _ 2 _ _ 2
|
|
// | | x - origin.x | | y - origin.y |
|
|
// | | ------------ | + | ------------ | = 1
|
|
// \ |_ width _| |_ height _|
|
|
|
|
Float a = (P.x - origin.x) / width;
|
|
Float b = normal.x / width;
|
|
Float c = (P.y - origin.y) / height;
|
|
Float d = normal.y / height;
|
|
|
|
Float A = b * b + d * d;
|
|
// In the quadratic formulat B would be 2*(a*b+c*d), however we factor the 2
|
|
// out Here which cancels out later.
|
|
Float B = a * b + c * d;
|
|
Float C = a * a + c * c - 1.0;
|
|
|
|
Float signB = 1.0;
|
|
if (B < 0.0) {
|
|
signB = -1.0;
|
|
}
|
|
|
|
// 2nd degree polynomials are typically computed using the formulae
|
|
// r1 = -(B - sqrt(delta)) / (2 * A)
|
|
// r2 = -(B + sqrt(delta)) / (2 * A)
|
|
// However B - sqrt(delta) can be an inportant source of precision loss for
|
|
// one of the roots when computing the difference between two similar and
|
|
// large numbers. To avoid that we pick the root with no precision loss in r1
|
|
// and compute r2 using the Citardauq formula.
|
|
// Factoring out 2 from B earlier let
|
|
Float S = B + signB * sqrt(B * B - A * C);
|
|
Float r1 = -S / A;
|
|
Float r2 = -C / S;
|
|
|
|
#ifdef DEBUG
|
|
Float epsilon = (Float)0.001;
|
|
MOZ_ASSERT(r1 >= -epsilon);
|
|
MOZ_ASSERT(r2 >= -epsilon);
|
|
#endif
|
|
|
|
return std::max((r1 < r2 ? r1 : r2), (Float)0.0);
|
|
}
|
|
|
|
} // namespace gfx
|
|
} // namespace mozilla
|