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);
22 return Point{std::isnan(in.x) ? 1 : in.
x, std::isnan(in.y) ? 1 : in.y};
40 constexpr
Scalar kPrecomputedVariables[][2] = {
41 {2.00000000, 1.13276676},
42 {2.18349805, 1.20311921},
43 {2.33888662, 1.28698796},
44 {2.48660575, 1.36351941},
45 {2.62226596, 1.44717976},
46 {2.75148990, 1.53385819},
47 {3.36298265, 1.98288283},
48 {4.08649929, 2.23811846},
49 {4.85481134, 2.47563463},
50 {5.62945551, 2.72948597},
51 {6.43023796, 2.98020421}};
53 constexpr
Scalar kMinRatio = 2.00;
59 constexpr
Scalar kFirstStepInverse = 10;
60 constexpr
Scalar kFirstMaxRatio = 2.50;
61 constexpr
Scalar kFirstNumRecords = 6;
63 constexpr
Scalar kSecondStepInverse = 2;
64 constexpr
Scalar kSecondMaxRatio = 5.00;
66 constexpr
Scalar kThirdNSlope = 1.559599389;
67 constexpr
Scalar kThirdKxjSlope = 0.522807185;
69 constexpr
size_t kNumRecords =
70 sizeof(kPrecomputedVariables) /
sizeof(kPrecomputedVariables[0]);
73 std::array<Scalar, 2> ComputeNAndXj(
Scalar ratio) {
74 if (ratio > kSecondMaxRatio) {
75 Scalar n = kThirdNSlope * (ratio - kSecondMaxRatio) +
76 kPrecomputedVariables[kNumRecords - 1][0];
77 Scalar k_xJ = kThirdKxjSlope * (ratio - kSecondMaxRatio) +
78 kPrecomputedVariables[kNumRecords - 1][1];
79 return {n, 1 - 1 / k_xJ};
81 ratio = std::clamp(ratio, kMinRatio, kSecondMaxRatio);
83 if (ratio < kFirstMaxRatio) {
84 steps = (ratio - kMinRatio) * kFirstStepInverse;
87 (ratio - kFirstMaxRatio) * kSecondStepInverse + kFirstNumRecords - 1;
90 size_t left = std::clamp<size_t>(
static_cast<size_t>(std::floor(steps)), 0,
92 Scalar frac = steps - left;
94 Scalar n = (1 - frac) * kPrecomputedVariables[left][0] +
95 frac * kPrecomputedVariables[left + 1][0];
96 Scalar k_xJ = (1 - frac) * kPrecomputedVariables[left][1] +
97 frac * kPrecomputedVariables[left + 1][1];
98 return {n, 1 - 1 / k_xJ};
119 Scalar distance_am = a_to_b.GetLength() / 2;
120 Scalar distance_cm = sqrt(r * r - distance_am * distance_am);
121 return m - distance_cm * c_to_m.Normalize();
126 RoundSuperellipseParam::Octant ComputeOctant(
Point center,
149 return RoundSuperellipseParam::Octant{
157 Scalar ratio = a * 2 / radius;
160 auto precomputed_vars = ComputeNAndXj(ratio);
161 Scalar n = precomputed_vars[0];
162 Scalar xJ = precomputed_vars[1] * a;
163 Scalar yJ = pow(1 - pow(precomputed_vars[1], n), 1 / n) * a;
164 Scalar max_theta = asinf(pow(precomputed_vars[1], n / 2));
166 Scalar tan_phiJ = pow(xJ / yJ, n - 1);
167 Scalar d = (xJ - tan_phiJ * yJ) / (1 - tan_phiJ);
168 Scalar R = (a - d - g) * sqrt(2);
170 Point pointM{a - g, a - g};
172 Point circle_center =
173 radius == 0 ? pointM : FindCircleCenter(pointJ, pointM, R);
174 Radians circle_max_angle =
175 radius == 0 ? Radians(0)
176 : (pointM - circle_center).AngleTo(pointJ - circle_center);
178 return RoundSuperellipseParam::Octant{
183 .se_max_theta = max_theta,
185 .circle_start = pointJ,
186 .circle_center = circle_center,
187 .circle_max_angle = circle_max_angle,
197 RoundSuperellipseParam::Quadrant ComputeQuadrant(
Point center,
200 Point corner_vector = corner - center;
207 Scalar norm_radius = radii.MinDimension();
208 Size forward_scale = norm_radius == 0 ?
Size{1, 1} : radii / norm_radius;
209 Point norm_half_size = corner_vector.
Abs() / forward_scale;
210 Point signed_scale = ReplanceNaNWithOne(corner_vector / norm_half_size);
217 Scalar c = norm_half_size.x - norm_half_size.y;
219 return RoundSuperellipseParam::Quadrant{
221 .signed_scale = signed_scale,
222 .top = ComputeOctant(
Point{0, -c}, norm_half_size.
x, norm_radius),
223 .right = ComputeOctant(
Point{c, 0}, norm_half_size.
y, norm_radius),
240 bool OctantContains(
const RoundSuperellipseParam::Octant& param,
243 if (p.x < 0 || p.y < 0 || p.y < p.x) {
247 if (p.x <= param.circle_start.x) {
248 Point p_se = p / param.se_a;
249 return powf(p_se.x, param.se_n) + powf(p_se.y, param.se_n) <= 1;
252 param.circle_start.GetDistanceSquared(param.circle_center);
253 Point p_circle = p - param.circle_center;
271 bool CornerContains(
const RoundSuperellipseParam::Quadrant& param,
273 bool check_quadrant =
true) {
274 Point norm_point = (p - param.offset) / param.signed_scale;
275 if (check_quadrant) {
276 if (norm_point.x < 0 || norm_point.y < 0) {
280 norm_point = norm_point.
Abs();
282 if (param.top.se_n < 2 || param.right.se_n < 2) {
285 Scalar x_delta = param.right.offset.x + param.right.se_a - norm_point.x;
286 Scalar y_delta = param.top.offset.y + param.top.se_a - norm_point.y;
287 bool x_within = x_delta > 0 || (x_delta == 0 && param.signed_scale.x < 0);
288 bool y_within = y_delta > 0 || (y_delta == 0 && param.signed_scale.y < 0);
289 return x_within && y_within;
291 return OctantContains(param.top, norm_point - param.top.offset) &&
292 OctantContains(param.right, Flip(norm_point - param.right.offset));
295 class RoundSuperellipseBuilder {
297 explicit RoundSuperellipseBuilder(PathBuilder& builder) : builder_(builder) {}
304 void AddQuadrant(
const RoundSuperellipseParam::Quadrant& param,
309 if (param.top.se_n < 2 || param.right.se_n < 2) {
310 builder_.LineTo(
transform * (param.top.offset +
311 Point(param.top.se_a, param.top.se_a)));
314 (param.top.offset +
Point(param.top.se_a, 0)));
317 (param.top.offset +
Point(0, param.top.se_a)));
322 AddOctant(param.top,
false,
false,
transform);
323 AddOctant(param.right,
true,
true,
transform);
325 AddOctant(param.right,
false,
true,
transform);
326 AddOctant(param.top,
true,
false,
transform);
331 std::array<Point, 4> SuperellipseArcPoints(
332 const RoundSuperellipseParam::Octant& param) {
333 Point start = {0, param.se_a};
334 const Point& end = param.circle_start;
335 constexpr
Point start_tangent = {1, 0};
336 Point circle_start_vector = param.circle_start - param.circle_center;
338 Point{-circle_start_vector.
y, circle_start_vector.x}.Normalize();
340 std::array<Scalar, 2> factors = SuperellipseBezierFactors(param.se_n);
342 return std::array<Point, 4>{
343 start, start + start_tangent * factors[0] * param.se_a,
344 end + end_tangent * factors[1] * param.se_a, end};
347 std::array<Point, 4> CircularArcPoints(
348 const RoundSuperellipseParam::Octant& param) {
349 Point start_vector = param.circle_start - param.circle_center;
351 start_vector.
Rotate(Radians(-param.circle_max_angle.radians));
352 Point circle_end = param.circle_center + end_vector;
353 Point start_tangent =
Point{start_vector.
y, -start_vector.x}.Normalize();
354 Point end_tangent =
Point{-end_vector.
y, end_vector.x}.Normalize();
355 Scalar bezier_factor = std::tan(param.circle_max_angle.radians / 4) * 4 / 3;
356 Scalar radius = start_vector.GetLength();
358 return std::array<Point, 4>{
360 param.circle_start + start_tangent * bezier_factor * radius,
361 circle_end + end_tangent * bezier_factor * radius, circle_end};
375 void AddOctant(
const RoundSuperellipseParam::Octant& param,
378 const Matrix& external_transform) {
385 auto circle_points = CircularArcPoints(param);
386 auto se_points = SuperellipseArcPoints(param);
391 builder_.CubicCurveTo(
transform * circle_points[1],
395 builder_.CubicCurveTo(
transform * circle_points[2],
410 std::array<Scalar, 2> SuperellipseBezierFactors(
Scalar n) {
411 constexpr
Scalar kPrecomputedVariables[][2] = {
412 {0.01339448, 0.05994973},
413 {0.13664115, 0.13592082},
414 {0.24545546, 0.14099516},
415 {0.32353151, 0.12808021},
416 {0.39093068, 0.11726264},
417 {0.44847800, 0.10808278},
418 {0.49817452, 0.10026175},
419 {0.54105583, 0.09344429},
420 {0.57812578, 0.08748984},
421 {0.61050961, 0.08224722},
422 {0.63903989, 0.07759639},
423 {0.66416338, 0.07346530},
424 {0.68675338, 0.06974996},
425 {0.70678034, 0.06529512}};
426 constexpr
size_t kNumRecords =
427 sizeof(kPrecomputedVariables) /
sizeof(kPrecomputedVariables[0]);
428 constexpr
Scalar kStep = 1.00f;
429 constexpr
Scalar kMinN = 2.00f;
430 constexpr
Scalar kMaxN = kMinN + (kNumRecords - 1) * kStep;
434 return {1.07f - expf(1.307649835) * powf(n, -0.8568516731),
435 -0.01f + expf(-0.9287690322) * powf(n, -0.6120901398)};
438 Scalar steps = std::clamp<Scalar>((n - kMinN) / kStep, 0, kNumRecords - 1);
439 size_t left = std::clamp<size_t>(
static_cast<size_t>(std::floor(steps)), 0,
441 Scalar frac = steps - left;
443 return std::array<Scalar, 2>{(1 - frac) * kPrecomputedVariables[left][0] +
444 frac * kPrecomputedVariables[left + 1][0],
445 (1 - frac) * kPrecomputedVariables[left][1] +
446 frac * kPrecomputedVariables[left + 1][1]};
449 PathBuilder& builder_;
453 static constexpr Matrix kFlip = Matrix(
454 0.0f, 1.0f, 0.0f, 0.0f,
455 1.0f, 0.0f, 0.0f, 0.0f,
456 0.0f, 0.0f, 1.0f, 0.0f,
457 0.0f, 0.0f, 0.0f, 1.0f);
472 .all_corners_same =
true,
489 ComputeQuadrant(
Point{bottom_split, right_split},
491 .bottom_left = ComputeQuadrant(
Point{bottom_split, left_split},
493 .top_left = ComputeQuadrant(
Point{top_split, left_split},
495 .all_corners_same =
false,
500 RoundSuperellipseBuilder builder(path_builder);
505 path_builder.
MoveTo(start);
516 builder.AddQuadrant(
top_left,
true);
519 path_builder.
LineTo(start);
520 path_builder.
Close();
PathBuilder & LineTo(Point point, bool relative=false)
Insert a line from the current position to point.
PathBuilder & MoveTo(Point point, bool relative=false)
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 AddToPath(PathBuilder &path) const
bool Contains(const Point &point) const
static constexpr Scalar kGapFactor
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 auto GetRight() const
constexpr TPoint< T > GetLeftBottom() const
constexpr TPoint< T > GetRightTop() const
constexpr TPoint< T > GetRightBottom() const
constexpr Point GetCenter() const
Get the center point as a |Point|.
constexpr TPoint< T > GetLeftTop() const
constexpr TSize Abs() const
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.