14 if (ratio_left == 0 && ratio_right == 0) {
15 return (left + right) / 2;
17 return (left * ratio_right + right * ratio_left) / (ratio_left + ratio_right);
23 return Point{std::isnan(in.x) ? default_size.width : in.
x,
24 std::isnan(in.y) ? default_size.height : in.y};
42constexpr Scalar kPrecomputedVariables[][2] = {
43 {2.00000000, 1.13276676},
44 {2.18349805, 1.20311921},
45 {2.33888662, 1.28698796},
46 {2.48660575, 1.36351941},
47 {2.62226596, 1.44717976},
48 {2.75148990, 1.53385819},
49 {3.36298265, 1.98288283},
50 {4.08649929, 2.23811846},
51 {4.85481134, 2.47563463},
52 {5.62945551, 2.72948597},
53 {6.43023796, 2.98020421}};
55constexpr Scalar kMinRatio = 2.00;
61constexpr Scalar kFirstStepInverse = 10;
62constexpr Scalar kFirstMaxRatio = 2.50;
63constexpr Scalar kFirstNumRecords = 6;
65constexpr Scalar kSecondStepInverse = 2;
66constexpr Scalar kSecondMaxRatio = 5.00;
68constexpr Scalar kThirdNSlope = 1.559599389;
69constexpr Scalar kThirdKxjSlope = 0.522807185;
71constexpr size_t kNumRecords =
72 sizeof(kPrecomputedVariables) /
sizeof(kPrecomputedVariables[0]);
75std::array<Scalar, 2> ComputeNAndXj(
Scalar ratio) {
76 if (ratio > kSecondMaxRatio) {
77 Scalar n = kThirdNSlope * (ratio - kSecondMaxRatio) +
78 kPrecomputedVariables[kNumRecords - 1][0];
79 Scalar k_xJ = kThirdKxjSlope * (ratio - kSecondMaxRatio) +
80 kPrecomputedVariables[kNumRecords - 1][1];
81 return {n, 1 - 1 / k_xJ};
83 ratio = std::clamp(ratio, kMinRatio, kSecondMaxRatio);
85 if (ratio < kFirstMaxRatio) {
86 steps = (ratio - kMinRatio) * kFirstStepInverse;
89 (ratio - kFirstMaxRatio) * kSecondStepInverse + kFirstNumRecords - 1;
92 size_t left = std::clamp<size_t>(
static_cast<size_t>(std::floor(steps)), 0,
96 Scalar n = (1 - frac) * kPrecomputedVariables[left][0] +
97 frac * kPrecomputedVariables[left + 1][0];
98 Scalar k_xJ = (1 - frac) * kPrecomputedVariables[left][1] +
99 frac * kPrecomputedVariables[left + 1][1];
100 return {n, 1 - 1 / k_xJ};
121 Scalar distance_am = a_to_b.GetLength() / 2;
122 Scalar distance_cm = sqrt(r * r - distance_am * distance_am);
123 return m - distance_cm * c_to_m.Normalize();
128RoundSuperellipseParam::Octant ComputeOctant(
Point center,
153 return RoundSuperellipseParam::Octant{
159 .circle_start = {a, a},
163 Scalar ratio = a * 2 / radius;
166 auto precomputed_vars = ComputeNAndXj(ratio);
167 Scalar n = precomputed_vars[0];
168 Scalar xJ = precomputed_vars[1] * a;
169 Scalar yJ = pow(1 - pow(precomputed_vars[1], n), 1 / n) * a;
170 Scalar max_theta = asinf(pow(precomputed_vars[1], n / 2));
172 Scalar tan_phiJ = pow(xJ / yJ, n - 1);
173 Scalar d = (xJ - tan_phiJ * yJ) / (1 - tan_phiJ);
174 Scalar R = (a -
d - g) * sqrt(2);
176 Point pointM{a - g, a - g};
178 Point circle_center =
179 radius == 0 ? pointM : FindCircleCenter(pointJ, pointM, R);
180 Radians circle_max_angle =
181 radius == 0 ? Radians(0)
182 : (pointM - circle_center).AngleTo(pointJ - circle_center);
184 return RoundSuperellipseParam::Octant{
189 .se_max_theta = max_theta,
191 .circle_start = pointJ,
192 .circle_center = circle_center,
193 .circle_max_angle = circle_max_angle,
207RoundSuperellipseParam::Quadrant ComputeQuadrant(
Point center,
212 Size radii = {std::min(std::abs(in_radii.width), std::abs(corner_vector.x)),
213 std::min(std::abs(in_radii.height), std::abs(corner_vector.y))};
221 Size forward_scale = norm_radius == 0 ?
Size{1, 1} : radii / norm_radius;
222 Point norm_half_size = corner_vector.
Abs() / forward_scale;
224 ReplanceNaNWithDefault(corner_vector / norm_half_size, sign);
231 Scalar c = norm_half_size.
x - norm_half_size.y;
233 return RoundSuperellipseParam::Quadrant{
235 .signed_scale = signed_scale,
236 .top = ComputeOctant(
Point{0, -
c}, norm_half_size.
x, norm_radius),
237 .right = ComputeOctant(
Point{
c, 0}, norm_half_size.
y, norm_radius),
254bool OctantContains(
const RoundSuperellipseParam::Octant& param,
257 if (p.x < 0 || p.y < 0 || p.y < p.x) {
261 if (p.x <= param.circle_start.x) {
262 Point p_se = p / param.se_a;
263 return powf(p_se.x, param.se_n) + powf(p_se.y, param.se_n) <= 1;
267 Point p_circle = p - param.circle_center;
287 bool check_quadrant =
true) {
288 Point norm_point = (p - param.offset) / param.signed_scale;
289 if (check_quadrant) {
290 if (norm_point.x < 0 || norm_point.y < 0) {
294 norm_point = norm_point.Abs();
296 if (param.top.se_n < 2 || param.right.se_n < 2) {
299 Scalar x_delta = param.right.offset.x + param.right.se_a - norm_point.x;
300 Scalar y_delta = param.top.offset.y + param.top.se_a - norm_point.y;
301 bool x_within = x_delta > 0 || (x_delta == 0 && param.signed_scale.x < 0);
302 bool y_within = y_delta > 0 || (y_delta == 0 && param.signed_scale.y < 0);
303 return x_within && y_within;
305 return OctantContains(param.top, norm_point - param.top.offset) &&
306 OctantContains(param.right, Flip(norm_point - param.right.offset));
309class RoundSuperellipseBuilder {
324 explicit RoundSuperellipseBuilder(PathReceiver& receiver)
325 : receiver_(receiver) {}
336 void AddQuadrant(
const RoundSuperellipseParam::Quadrant& param,
341 if (param.top.se_n < 2 || param.right.se_n < 2) {
342 receiver_.LineTo(
transform * (param.top.offset +
343 Point(param.top.se_a, param.top.se_a)));
346 (param.right.offset +
Point(param.right.se_a, 0)));
349 (param.top.offset +
Point(0, param.top.se_a)));
354 AddOctant(param.top,
false,
false,
transform);
355 AddOctant(param.right,
true,
true,
transform);
357 AddOctant(param.right,
false,
true,
transform);
358 AddOctant(param.top,
true,
false,
transform);
363 std::tuple<ConicParam, ConicParam> SuperellipseArcPoints(
364 const RoundSuperellipseParam::Octant& param) {
366 Point A = {0, param.se_a};
367 const Point& J = param.circle_start;
369 std::tuple<Scalar, Scalar, Scalar> factors = SuperellipseBezierFactors(
370 param.se_n, J.x / param.se_a, J.y / param.se_a);
371 Scalar weight1 = std::get<0>(factors);
372 Scalar weight2 = std::get<1>(factors);
373 Scalar yHOverA = std::get<2>(factors);
376 powf(1.f - powf(yHOverA, param.se_n), 1.f / param.se_n) * param.se_a,
377 yHOverA * param.se_a};
380 Scalar kJ = -powf(J.x / J.y, param.se_n - 1.f);
381 Scalar kH = -powf(H.x / H.y, param.se_n - 1.f);
385 return {ConicParam{.p1 = A,
386 .c = Intersection(A, kA, H, kH),
390 .c = Intersection(H, kH, J, kJ),
395 std::array<Point, 4> CircularArcPoints(
396 const RoundSuperellipseParam::Octant& param) {
397 Point start_vector = param.circle_start - param.circle_center;
399 start_vector.
Rotate(Radians(-param.circle_max_angle.radians));
400 Point circle_end = param.circle_center + end_vector;
401 Point start_tangent =
Point{start_vector.
y, -start_vector.x}.Normalize();
402 Point end_tangent =
Point{-end_vector.
y, end_vector.x}.Normalize();
403 Scalar bezier_factor = std::tan(param.circle_max_angle.radians / 4) * 4 / 3;
404 Scalar radius = start_vector.GetLength();
406 return std::array<Point, 4>{
408 param.circle_start + start_tangent * bezier_factor * radius,
409 circle_end + end_tangent * bezier_factor * radius, circle_end};
423 void AddOctant(
const RoundSuperellipseParam::Octant& param,
426 const Matrix& external_transform) {
433 auto circle_points = CircularArcPoints(param);
434 auto se_conics = SuperellipseArcPoints(param);
437 receiver_.ConicTo(
transform * std::get<0>(se_conics).
c,
439 std::get<0>(se_conics).
weight);
440 receiver_.ConicTo(
transform * std::get<1>(se_conics).
c,
442 std::get<1>(se_conics).
weight);
443 receiver_.CubicTo(
transform * circle_points[1],
447 receiver_.CubicTo(
transform * circle_points[2],
450 receiver_.ConicTo(
transform * std::get<1>(se_conics).
c,
452 std::get<1>(se_conics).
weight);
453 receiver_.ConicTo(
transform * std::get<0>(se_conics).
c,
455 std::get<0>(se_conics).
weight);
465 std::tuple<Scalar, Scalar, Scalar> SuperellipseBezierFactors(
Scalar n,
482 constexpr Scalar kPrecomputedVariables[][2] = { {0.7078, 8.3194},
495 constexpr size_t kNumRecords =
496 sizeof(kPrecomputedVariables) /
sizeof(kPrecomputedVariables[0]);
498 constexpr Scalar kMinN = 2.00f;
499 constexpr Scalar kMaxN = kMinN + (kNumRecords - 1) * kStep;
508 Scalar steps = std::clamp<Scalar>((n - kMinN) / kStep, 0, kNumRecords - 1);
509 size_t left = std::clamp<size_t>(
static_cast<size_t>(std::floor(steps)), 0,
513 Scalar weight1 = (1.f - frac) * kPrecomputedVariables[left][0] +
514 frac * kPrecomputedVariables[left + 1][0] * sqrt(n);
515 Scalar weight2 = (1.f - frac) * kPrecomputedVariables[left][1] +
516 frac * kPrecomputedVariables[left + 1][1] * xJOverA;
527 Scalar yH_proportion = sqrt(n);
528 constexpr Scalar yAOverA = 1.0;
530 (yAOverA * yH_proportion + yJOverA) / (yH_proportion + 1.0f);
532 return {weight1, weight2, yHOverA};
539 return (
p1 +
p2) / 2;
552 PathReceiver& receiver_;
556 static constexpr Matrix kFlip = Matrix(
557 0.0f, 1.0f, 0.0f, 0.0f,
558 1.0f, 0.0f, 0.0f, 0.0f,
559 0.0f, 0.0f, 1.0f, 0.0f,
560 0.0f, 0.0f, 0.0f, 1.0f);
571 {radius, radius}, {-1, 1}),
572 .all_corners_same =
true,
585 .all_corners_same =
true,
603 ComputeQuadrant(
Point{bottom_split, right_split},
606 ComputeQuadrant(
Point{bottom_split, left_split},
611 .all_corners_same =
false,
616 RoundSuperellipseBuilder builder(path_receiver);
632 builder.AddQuadrant(
top_left,
true);
636 path_receiver.
Close();
Collection of functions to receive path segments from the underlying path representation via the DlPa...
virtual void LineTo(const Point &p2)=0
virtual void MoveTo(const Point &p2, bool will_be_closed)=0
constexpr float kEhCloseEnough
static bool CornerContains(const Point &p, const Point &corner, const Point &direction, const Size &radii)
static constexpr Matrix MakeTranslation(const Vector3 &t)
static constexpr Matrix MakeTranslateScale(const Vector3 &s, const Vector3 &t)
static RoundSuperellipseParam MakeBoundsRadii(const Rect &bounds, const RoundingRadii &radii)
void Dispatch(PathReceiver &receiver) const
bool Contains(const Point &point) const
static constexpr Scalar kGapFactor
static RoundSuperellipseParam MakeBoundsRadius(const Rect &bounds, Scalar radius)
constexpr bool AreAllCornersSame(Scalar tolerance=kEhCloseEnough) const
constexpr TPoint Abs() const
constexpr TPoint Rotate(const Radians &angle) const
constexpr Type GetDistanceSquared(const TPoint &p) const
constexpr auto GetBottom() const
constexpr auto GetTop() const
constexpr auto GetLeft() const
constexpr TPoint< T > GetLeftTop() const
constexpr auto GetRight() const
constexpr TPoint< T > GetRightBottom() const
constexpr TPoint< T > GetLeftBottom() const
constexpr TPoint< T > GetRightTop() const
constexpr Point GetCenter() const
Get the center point as a |Point|.
constexpr Type MinDimension() const
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.