From a7996e128210f7d5b6785867397e296846dfc47c Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 19 Nov 2024 01:40:58 -0800 Subject: [PATCH 01/29] Compilable --- ci/licenses_golden/licenses_flutter | 4 ++++ impeller/entity/BUILD.gn | 2 ++ impeller/entity/entity_unittests.cc | 37 +++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c8b2aeae37e3e..4014d769087e5 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -43041,6 +43041,8 @@ ORIGIN: ../../../flutter/impeller/entity/geometry/rect_geometry.cc + ../../../fl ORIGIN: ../../../flutter/impeller/entity/geometry/rect_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/round_rect_geometry.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/round_rect_geometry.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/geometry/round_superellipse_geometry.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/geometry/round_superellipse_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/superellipse_geometry.cc + ../../../flutter/LICENSE @@ -45910,6 +45912,8 @@ FILE: ../../../flutter/impeller/entity/geometry/rect_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/rect_geometry.h FILE: ../../../flutter/impeller/entity/geometry/round_rect_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/round_rect_geometry.h +FILE: ../../../flutter/impeller/entity/geometry/round_superellipse_geometry.cc +FILE: ../../../flutter/impeller/entity/geometry/round_superellipse_geometry.h FILE: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.h FILE: ../../../flutter/impeller/entity/geometry/superellipse_geometry.cc diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 48bcb2bc33075..ecaf5d06a11b0 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -199,6 +199,8 @@ impeller_component("entity") { "geometry/rect_geometry.h", "geometry/round_rect_geometry.cc", "geometry/round_rect_geometry.h", + "geometry/round_superellipse_geometry.cc", + "geometry/round_superellipse_geometry.h", "geometry/stroke_path_geometry.cc", "geometry/stroke_path_geometry.h", "geometry/superellipse_geometry.cc", diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 954662d30a3ca..482718fec80cc 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -37,6 +37,7 @@ #include "impeller/entity/entity_playground.h" #include "impeller/entity/geometry/geometry.h" #include "impeller/entity/geometry/point_field_geometry.h" +#include "impeller/entity/geometry/round_superellipse_geometry.h" #include "impeller/entity/geometry/stroke_path_geometry.h" #include "impeller/entity/geometry/superellipse_geometry.h" #include "impeller/geometry/color.h" @@ -2336,6 +2337,42 @@ TEST_P(EntityTest, DrawSuperEllipse) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_P(EntityTest, DrawRoundSuperEllipse) { + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + // UI state. + static float center_x = 100; + static float center_y = 100; + static float width = 100; + static float height = 80; + static float corner_radius = 30; + static Color color = Color::Red(); + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::SliderFloat("Center X", ¢er_x, 0, 500); + ImGui::SliderFloat("Center Y", ¢er_y, 0, 500); + ImGui::SliderFloat("Width", &width, 1, 400); + ImGui::SliderFloat("Height", &height, 1, 400); + ImGui::SliderFloat("Corner radius", &corner_radius, 1, 250); + ImGui::End(); + + auto contents = std::make_shared(); + static std::unique_ptr geom = + std::make_unique( + Rect::MakeLTRB(center_x - width / 2, center_y - height / 2, + center_x + width / 2, center_y + height / 2), + corner_radius); + contents->SetColor(color); + contents->SetGeometry(geom.get()); + + Entity entity; + entity.SetContents(contents); + + return entity.Render(context, pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + TEST_P(EntityTest, SolidColorApplyColorFilter) { auto contents = SolidColorContents(); contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75)); From a77a504e60a84a1b10f271a16aa927bf580c0794 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 19 Nov 2024 01:54:17 -0800 Subject: [PATCH 02/29] Add proper file --- impeller/entity/entity_unittests.cc | 4 +- .../geometry/round_superellipse_geometry.cc | 237 ++++++++++++++++++ .../geometry/round_superellipse_geometry.h | 55 ++++ 3 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 impeller/entity/geometry/round_superellipse_geometry.cc create mode 100644 impeller/entity/geometry/round_superellipse_geometry.h diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 482718fec80cc..f0605dc3832f7 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2339,6 +2339,7 @@ TEST_P(EntityTest, DrawSuperEllipse) { TEST_P(EntityTest, DrawRoundSuperEllipse) { auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + // printf("One draw\n"); // UI state. static float center_x = 100; static float center_y = 100; @@ -2358,8 +2359,7 @@ TEST_P(EntityTest, DrawRoundSuperEllipse) { auto contents = std::make_shared(); static std::unique_ptr geom = std::make_unique( - Rect::MakeLTRB(center_x - width / 2, center_y - height / 2, - center_x + width / 2, center_y + height / 2), + Rect::MakeOriginSize({center_x, center_y}, {width, height}), corner_radius); contents->SetColor(color); contents->SetGeometry(geom.get()); diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc new file mode 100644 index 0000000000000..123af1f1e1ee4 --- /dev/null +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -0,0 +1,237 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "flutter/impeller/entity/geometry/round_superellipse_geometry.h" + +#include "impeller/geometry/constants.h" + +namespace impeller { + +static constexpr Scalar kRatio_N_DOverA_Theta[][4] = { + {2.000, 2.00000, 0.00000, 0.26000}, + {2.020, 2.03300, 0.01441, 0.23845}, + {2.040, 2.06500, 0.02568, 0.20310}, + {2.060, 2.09800, 0.03655, 0.18593}, + {2.080, 2.13200, 0.04701, 0.17341}, + {2.100, 2.17800, 0.05596, 0.14049}, + {2.120, 2.19300, 0.06805, 0.17417}, + {2.140, 2.23000, 0.07733, 0.16145}, + {2.160, 2.26400, 0.08677, 0.15649}, + {2.180, 2.30500, 0.09529, 0.14374}, + {2.200, 2.32900, 0.10530, 0.15212}, + {2.220, 2.38300, 0.11230, 0.12974}, + {2.240, 2.39800, 0.12257, 0.14433}, + {2.260, 2.41800, 0.13236, 0.15439}, + {2.280, 2.47200, 0.13867, 0.13431}, + {2.300, 2.50900, 0.14649, 0.13021} +}; + +static constexpr size_t NUM_RECORDS = sizeof(kRatio_N_DOverA_Theta) / sizeof(kRatio_N_DOverA_Theta[0]); +static constexpr Scalar MAX_RATIO = kRatio_N_DOverA_Theta[NUM_RECORDS-1][0]; +static constexpr Scalar RATIO_STEP = kRatio_N_DOverA_Theta[1][0] - kRatio_N_DOverA_Theta[0][0]; + +static constexpr Scalar gap(Scalar corner_radius) { + return 0.2924303407 * corner_radius; +} + +struct ExpandedVariables { + Scalar n; + Scalar d; + Scalar R; + Scalar x0; + Scalar y0; +}; + +// Result will be assigned with [n, d_over_a, theta] +static ExpandedVariables ExpandVariables(Scalar ratio, Scalar a, Scalar g) { + constexpr Scalar MIN_RATIO = kRatio_N_DOverA_Theta[0][0]; + Scalar steps = std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS); + size_t lo = std::min((size_t)std::floor(steps), NUM_RECORDS - 1); + size_t hi = lo + 1; + Scalar pos = steps - lo; + + Scalar n = pos * kRatio_N_DOverA_Theta[lo][1] + (1-pos) * kRatio_N_DOverA_Theta[hi][1]; + Scalar d = (pos * kRatio_N_DOverA_Theta[lo][2] + (1-pos) * kRatio_N_DOverA_Theta[hi][2]) * a; + Scalar R = a - d - g; + Scalar theta = pos * kRatio_N_DOverA_Theta[lo][3] + (1-pos) * kRatio_N_DOverA_Theta[hi][3]; + Scalar x0 = d + R * sin(theta); + Scalar y0 = pow(pow(a, n) - pow(x0, n), 1 / n); + return ExpandedVariables{ + .n = n, + .d = d, + .R = R, + .x0 = x0, + .y0 = y0, + }; +} + +static void DrawCircularArc(Point start, Point end, Scalar r) { + // TODO +} + +RoundSuperellipseGeometry::RoundSuperellipseGeometry(const Rect& bounds, + Scalar corner_radius) + : bounds_(bounds), + corner_radius_(corner_radius) {} + +RoundSuperellipseGeometry::~RoundSuperellipseGeometry() {} + +GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + Size size = bounds_.GetSize(); + Point center = bounds_.GetCenter(); + // printf("Center %.2f, %.2f\n", center.x, center.y); + Scalar r = std::min(corner_radius_, std::min(size.width / 2, size.height / 2)); + + // Derive critical variables + Size ratio_wh = {std::min(size.width / r, MAX_RATIO), std::min(size.height / r, MAX_RATIO)}; + Size ab = ratio_wh * r; + Size s_wh = size / 2 - ab; + Scalar g = gap(corner_radius_); + + ExpandedVariables var_w = ExpandVariables(ratio_wh.width, ab.width, g); + ExpandedVariables var_h = ExpandVariables(ratio_wh.height, ab.height, g); + Scalar c = (ab.width - size.height) / 2; + + /* Generate the points for the top right quadrant, and then mirror to the other + * quadrants. The following figure shows the top 1/8 arc (from 0 to pi/4), which + * is a square-like squircle offset by (0, -c). + * + * straight superelipse + * ↓ ↓ + * A B J ↙ circular arc + * -------------__、 + * | | / ヽ M + * | | / ⟋ \ + * | | / ⟋ \ + * | | ᨀ | + * O + | / D | + * | |/ | + * E--------|------------| + * S + * + * Ignore the central offset until the last step, and assume point O, the + * origin, is (0, 0), + * + * A = (0, h/2) + * B = (s_w, h/2) + * J = (x0_w, y0_w - c) + * M = (w/2 - g, h/2 - g) + * + * The next 1/8 arc (from pi/4 to pi/2) has mirrored points: + * + * J' = (y0_h + c, x0_h) + * B' = (w/2, s_h) + * A' = (w/2, 0) + */ + + // TODO(dkwingsmt): determine parameter values based on scaling factor. + Scalar step = kPi / 80; + + std::vector points; + points.reserve(41); + Point pointM {size.width / 2 - g, size.height / 2 - g}; + + // A + points.emplace_back(0, size.height / 2); + // B + points.emplace_back(s_wh.width, size.height / 2); + // Arc BJ (both ends exclusive) + Scalar angle_jsb = atan((var_w.x0 - s_wh.width) / var_w.y0); + for (Scalar angle = 0 + step; angle < angle_jsb; angle += step) { + // printf("Angle %.2f\n", angle); + Scalar x = ab.width * pow(abs(sin(angle)), 2 / var_w.n); + Scalar y = ab.width * pow(abs(cos(angle)), 2 / var_w.n) - c; + points.emplace_back(x, y); + } + // J + points.emplace_back(var_w.x0, var_w.y0 - c); + // Arc JM (both ends exclusive) + DrawCircularArc({var_w.x0, var_w.y0 - c}, pointM, var_w.R); + // M + points.push_back(pointM); + // Arc MJ' (both ends exclusive) + DrawCircularArc(pointM, {var_h.y0 + c, var_h.x0}, var_h.R); + // J' + points.emplace_back(var_h.y0 + c, var_w.x0); + // Arc BJ (both ends exclusive) + Scalar angle_bsj = atan((var_h.x0 - s_wh.height) / var_h.y0); + for (Scalar angle = angle_bsj - step; angle > 0; angle -= step) { + // printf("Angle %.2f\n", angle); + Scalar x = ab.height * pow(abs(cos(angle)), 2 / var_h.n) + c; + Scalar y = ab.height * pow(abs(sin(angle)), 2 / var_h.n); + points.emplace_back(x, y); + } + // A' + points.emplace_back(size.width / 2, 0); + + static constexpr Point reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; + + // Reflect into the 4 quadrants and generate the tessellated mesh. The + // iteration order is reversed so that the trianges are continuous from + // quadrant to quadrant. + std::vector geometry; + geometry.reserve(1 + 4 * points.size()); + geometry.push_back(center); + // All arcs include the starting point and exclude the ending point. + for (auto i = 0u; i < points.size() - 1; i++) { + geometry.push_back(center + (reflection[0] * points[i])); + } + for (auto i = points.size() - 1; i >= 1; i--) { + geometry.push_back(center + (reflection[1] * points[i])); + } + for (auto i = 0u; i < points.size() - 1; i++) { + geometry.push_back(center + (reflection[2] * points[i])); + } + for (auto i = points.size() - 1; i >= 1; i--) { + geometry.push_back(center + (reflection[3] * points[i])); + } + geometry.push_back(center + points[0]); + + std::vector indices; + indices.reserve(geometry.size() * 3); + for (auto i = 2u; i < geometry.size(); i++) { + indices.push_back(0); + indices.push_back(i - 1); + indices.push_back(i); + } + + auto& host_buffer = renderer.GetTransientsBuffer(); + return GeometryResult{ + .type = PrimitiveType::kTriangle, + .vertex_buffer = + { + .vertex_buffer = host_buffer.Emplace( + geometry.data(), geometry.size() * sizeof(Point), + alignof(Point)), + .index_buffer = host_buffer.Emplace( + indices.data(), indices.size() * sizeof(uint16_t), + alignof(uint16_t)), + .vertex_count = indices.size(), + .index_type = IndexType::k16bit, + }, + .transform = entity.GetShaderTransform(pass), + }; +} + +std::optional RoundSuperellipseGeometry::GetCoverage( + const Matrix& transform) const { + return bounds_.TransformBounds(transform); +} + +bool RoundSuperellipseGeometry::CoversArea(const Matrix& transform, + const Rect& rect) const { + return false; +} + +bool RoundSuperellipseGeometry::IsAxisAlignedRect() const { + return false; +} + +} // namespace impeller diff --git a/impeller/entity/geometry/round_superellipse_geometry.h b/impeller/entity/geometry/round_superellipse_geometry.h new file mode 100644 index 0000000000000..a015342130f56 --- /dev/null +++ b/impeller/entity/geometry/round_superellipse_geometry.h @@ -0,0 +1,55 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_ENTITY_GEOMETRY_ROUND_SUPERELLIPSE_GEOMETRY_H_ +#define FLUTTER_IMPELLER_ENTITY_GEOMETRY_ROUND_SUPERELLIPSE_GEOMETRY_H_ + +#include "impeller/entity/geometry/geometry.h" + +namespace impeller { + +/// Geometry class that can generate vertices for a rounded superellipse. +/// +/// A Superellipse is an ellipse-like shape that is defined by the parameters N, +/// alpha, and beta: +/// +/// 1 = |x / b| ^n + |y / a| ^n +/// +/// The radius and center apply a uniform scaling and offset that is separate +/// from alpha or beta. When n = 4, the shape is referred to as a rectellipse. +/// +/// See also: https://en.wikipedia.org/wiki/Superellipse +class RoundSuperellipseGeometry final : public Geometry { + public: + explicit RoundSuperellipseGeometry(const Rect& bounds, + Scalar corner_radius); + + ~RoundSuperellipseGeometry() override; + + // |Geometry| + bool CoversArea(const Matrix& transform, const Rect& rect) const override; + + // |Geometry| + bool IsAxisAlignedRect() const override; + + private: + // |Geometry| + GeometryResult GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + // |Geometry| + std::optional GetCoverage(const Matrix& transform) const override; + + const Rect bounds_; + Scalar corner_radius_; + + RoundSuperellipseGeometry(const RoundSuperellipseGeometry&) = delete; + + RoundSuperellipseGeometry& operator=(const RoundSuperellipseGeometry&) = delete; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_ENTITY_GEOMETRY_ROUND_SUPERELLIPSE_GEOMETRY_H_ From d6e7d0062cd9331348708725e80f12e8908a9ee2 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 19 Nov 2024 15:07:43 -0800 Subject: [PATCH 03/29] Debugged the SE part --- impeller/entity/entity_unittests.cc | 6 +- .../geometry/round_superellipse_geometry.cc | 124 +++++++++++------- .../geometry/round_superellipse_geometry.h | 8 +- 3 files changed, 80 insertions(+), 58 deletions(-) diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index f0605dc3832f7..2ff26b2d40749 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2343,9 +2343,9 @@ TEST_P(EntityTest, DrawRoundSuperEllipse) { // UI state. static float center_x = 100; static float center_y = 100; - static float width = 100; - static float height = 80; - static float corner_radius = 30; + static float width = 900; + static float height = 900; + static float corner_radius = 391.30; static Color color = Color::Red(); ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 123af1f1e1ee4..c8826b25f89fd 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -11,7 +11,7 @@ namespace impeller { -static constexpr Scalar kRatio_N_DOverA_Theta[][4] = { +static constexpr double kRatio_N_DOverA_Theta[][4] = { {2.000, 2.00000, 0.00000, 0.26000}, {2.020, 2.03300, 0.01441, 0.23845}, {2.040, 2.06500, 0.02568, 0.20310}, @@ -31,35 +31,37 @@ static constexpr Scalar kRatio_N_DOverA_Theta[][4] = { }; static constexpr size_t NUM_RECORDS = sizeof(kRatio_N_DOverA_Theta) / sizeof(kRatio_N_DOverA_Theta[0]); -static constexpr Scalar MAX_RATIO = kRatio_N_DOverA_Theta[NUM_RECORDS-1][0]; -static constexpr Scalar RATIO_STEP = kRatio_N_DOverA_Theta[1][0] - kRatio_N_DOverA_Theta[0][0]; +static constexpr double MAX_RATIO = kRatio_N_DOverA_Theta[NUM_RECORDS-1][0]; +static constexpr double RATIO_STEP = kRatio_N_DOverA_Theta[1][0] - kRatio_N_DOverA_Theta[0][0]; -static constexpr Scalar gap(Scalar corner_radius) { +static constexpr double gap(double corner_radius) { return 0.2924303407 * corner_radius; } +typedef TSize DSize; +typedef TPoint DPoint; struct ExpandedVariables { - Scalar n; - Scalar d; - Scalar R; - Scalar x0; - Scalar y0; + double n; + double d; + double R; + double x0; + double y0; }; // Result will be assigned with [n, d_over_a, theta] -static ExpandedVariables ExpandVariables(Scalar ratio, Scalar a, Scalar g) { +static ExpandedVariables ExpandVariables(double ratio, double a, double g) { constexpr Scalar MIN_RATIO = kRatio_N_DOverA_Theta[0][0]; - Scalar steps = std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS); - size_t lo = std::min((size_t)std::floor(steps), NUM_RECORDS - 1); + double steps = std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS - 1); + size_t lo = std::clamp((size_t)std::floor(steps), 0, NUM_RECORDS - 2); size_t hi = lo + 1; - Scalar pos = steps - lo; - - Scalar n = pos * kRatio_N_DOverA_Theta[lo][1] + (1-pos) * kRatio_N_DOverA_Theta[hi][1]; - Scalar d = (pos * kRatio_N_DOverA_Theta[lo][2] + (1-pos) * kRatio_N_DOverA_Theta[hi][2]) * a; - Scalar R = a - d - g; - Scalar theta = pos * kRatio_N_DOverA_Theta[lo][3] + (1-pos) * kRatio_N_DOverA_Theta[hi][3]; - Scalar x0 = d + R * sin(theta); - Scalar y0 = pow(pow(a, n) - pow(x0, n), 1 / n); + double pos = steps - lo; + + double n = (1-pos) * kRatio_N_DOverA_Theta[lo][1] + pos * kRatio_N_DOverA_Theta[hi][1]; + double d = ((1-pos) * kRatio_N_DOverA_Theta[lo][2] + pos * kRatio_N_DOverA_Theta[hi][2]) * a; + double R = (a - d - g) * sqrt(2); + double theta = (1-pos) * kRatio_N_DOverA_Theta[lo][3] + pos * kRatio_N_DOverA_Theta[hi][3]; + double x0 = d + R * sin(theta); + double y0 = pow(pow(a, n) - pow(x0, n), 1 / n); return ExpandedVariables{ .n = n, .d = d, @@ -69,7 +71,11 @@ static ExpandedVariables ExpandVariables(Scalar ratio, Scalar a, Scalar g) { }; } -static void DrawCircularArc(Point start, Point end, Scalar r) { +static Point operator+(Point a, DPoint b) { + return Point{static_cast(a.x + b.x), static_cast(a.y + b.y)}; +} + +static void DrawCircularArc(DPoint start, DPoint end, double r) { // TODO } @@ -84,20 +90,20 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - Size size = bounds_.GetSize(); + DSize size {bounds_.GetWidth(), bounds_.GetHeight()}; Point center = bounds_.GetCenter(); // printf("Center %.2f, %.2f\n", center.x, center.y); - Scalar r = std::min(corner_radius_, std::min(size.width / 2, size.height / 2)); + const double r = std::min(corner_radius_, std::min(size.width / 2, size.height / 2)); // Derive critical variables - Size ratio_wh = {std::min(size.width / r, MAX_RATIO), std::min(size.height / r, MAX_RATIO)}; - Size ab = ratio_wh * r; - Size s_wh = size / 2 - ab; - Scalar g = gap(corner_radius_); + const DSize ratio_wh = {std::min(size.width / r, MAX_RATIO), std::min(size.height / r, MAX_RATIO)}; + const DSize ab = ratio_wh * r / 2; + const DSize s_wh = size / 2 - ab; + const double g = gap(corner_radius_); - ExpandedVariables var_w = ExpandVariables(ratio_wh.width, ab.width, g); - ExpandedVariables var_h = ExpandVariables(ratio_wh.height, ab.height, g); - Scalar c = (ab.width - size.height) / 2; + const ExpandedVariables var_w = ExpandVariables(ratio_wh.width, ab.width, g); + const ExpandedVariables var_h = ExpandVariables(ratio_wh.height, ab.height, g); + const double c = ab.width - size.height / 2; /* Generate the points for the top right quadrant, and then mirror to the other * quadrants. The following figure shows the top 1/8 arc (from 0 to pi/4), which @@ -132,46 +138,62 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( */ // TODO(dkwingsmt): determine parameter values based on scaling factor. - Scalar step = kPi / 80; + double step = kPi / 80; - std::vector points; + std::vector points; points.reserve(41); - Point pointM {size.width / 2 - g, size.height / 2 - g}; + const DPoint pointM {size.width / 2 - g, size.height / 2 - g}; // A points.emplace_back(0, size.height / 2); // B points.emplace_back(s_wh.width, size.height / 2); // Arc BJ (both ends exclusive) - Scalar angle_jsb = atan((var_w.x0 - s_wh.width) / var_w.y0); - for (Scalar angle = 0 + step; angle < angle_jsb; angle += step) { - // printf("Angle %.2f\n", angle); - Scalar x = ab.width * pow(abs(sin(angle)), 2 / var_w.n); - Scalar y = ab.width * pow(abs(cos(angle)), 2 / var_w.n) - c; - points.emplace_back(x, y); + { + const double target_slope = var_w.y0 / var_w.x0; + for (double angle = 0 + step; ; angle += step) { + const double x = ab.width * pow(abs(sin(angle)), 2 / var_w.n); + const double y = ab.width * pow(abs(cos(angle)), 2 / var_w.n); + if (y / x <= target_slope) { + break; + } + points.emplace_back(x + s_wh.width, y - c); + } } // J - points.emplace_back(var_w.x0, var_w.y0 - c); + points.emplace_back(var_w.x0 + s_wh.width, var_w.y0 - c); // Arc JM (both ends exclusive) - DrawCircularArc({var_w.x0, var_w.y0 - c}, pointM, var_w.R); + DrawCircularArc({var_w.x0 + s_wh.width, var_w.y0 - c}, pointM, var_w.R); // M points.push_back(pointM); // Arc MJ' (both ends exclusive) - DrawCircularArc(pointM, {var_h.y0 + c, var_h.x0}, var_h.R); + DrawCircularArc(pointM, {var_h.y0 + c, var_h.x0 + s_wh.height}, var_h.R); // J' - points.emplace_back(var_h.y0 + c, var_w.x0); - // Arc BJ (both ends exclusive) - Scalar angle_bsj = atan((var_h.x0 - s_wh.height) / var_h.y0); - for (Scalar angle = angle_bsj - step; angle > 0; angle -= step) { - // printf("Angle %.2f\n", angle); - Scalar x = ab.height * pow(abs(cos(angle)), 2 / var_h.n) + c; - Scalar y = ab.height * pow(abs(sin(angle)), 2 / var_h.n); - points.emplace_back(x, y); + points.emplace_back(var_h.y0 + c, var_w.x0 + s_wh.height); + // Arc J'B' (both ends exclusive) + { + const double target_slope = var_h.y0 / var_h.x0; + std::vector points_bsj; + for (double angle = 0; ; angle += step) { + const double x = ab.height * pow(abs(sin(angle)), 2 / var_h.n); + const double y = ab.height * pow(abs(cos(angle)), 2 / var_h.n); + if (y / x <= target_slope) { + break; + } + // The coordinates are inverted because this half of arc is mirrowed by the + // 45deg line. + points_bsj.emplace_back(y + c, x + s_wh.height); + } + for (size_t i = 0; i < points_bsj.size(); i++) { + points.push_back(points_bsj[points_bsj.size() - i - 1]); + } } + // B + points.emplace_back(size.width / 2, s_wh.height); // A' points.emplace_back(size.width / 2, 0); - static constexpr Point reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; + static constexpr DPoint reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; // Reflect into the 4 quadrants and generate the tessellated mesh. The // iteration order is reversed so that the trianges are continuous from diff --git a/impeller/entity/geometry/round_superellipse_geometry.h b/impeller/entity/geometry/round_superellipse_geometry.h index a015342130f56..c8644c1e62e7e 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.h +++ b/impeller/entity/geometry/round_superellipse_geometry.h @@ -22,8 +22,7 @@ namespace impeller { /// See also: https://en.wikipedia.org/wiki/Superellipse class RoundSuperellipseGeometry final : public Geometry { public: - explicit RoundSuperellipseGeometry(const Rect& bounds, - Scalar corner_radius); + explicit RoundSuperellipseGeometry(const Rect& bounds, Scalar corner_radius); ~RoundSuperellipseGeometry() override; @@ -43,11 +42,12 @@ class RoundSuperellipseGeometry final : public Geometry { std::optional GetCoverage(const Matrix& transform) const override; const Rect bounds_; - Scalar corner_radius_; + double corner_radius_; RoundSuperellipseGeometry(const RoundSuperellipseGeometry&) = delete; - RoundSuperellipseGeometry& operator=(const RoundSuperellipseGeometry&) = delete; + RoundSuperellipseGeometry& operator=(const RoundSuperellipseGeometry&) = + delete; }; } // namespace impeller From 1e17d18a151b5f5b53484e0d00972fe3f107fca5 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 19 Nov 2024 23:09:29 -0800 Subject: [PATCH 04/29] Did it! --- .../geometry/round_superellipse_geometry.cc | 186 +++++++++++------- 1 file changed, 116 insertions(+), 70 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index c8826b25f89fd..9673f51802599 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -12,27 +12,29 @@ namespace impeller { static constexpr double kRatio_N_DOverA_Theta[][4] = { - {2.000, 2.00000, 0.00000, 0.26000}, - {2.020, 2.03300, 0.01441, 0.23845}, - {2.040, 2.06500, 0.02568, 0.20310}, - {2.060, 2.09800, 0.03655, 0.18593}, - {2.080, 2.13200, 0.04701, 0.17341}, - {2.100, 2.17800, 0.05596, 0.14049}, - {2.120, 2.19300, 0.06805, 0.17417}, - {2.140, 2.23000, 0.07733, 0.16145}, - {2.160, 2.26400, 0.08677, 0.15649}, - {2.180, 2.30500, 0.09529, 0.14374}, - {2.200, 2.32900, 0.10530, 0.15212}, - {2.220, 2.38300, 0.11230, 0.12974}, - {2.240, 2.39800, 0.12257, 0.14433}, - {2.260, 2.41800, 0.13236, 0.15439}, - {2.280, 2.47200, 0.13867, 0.13431}, - {2.300, 2.50900, 0.14649, 0.13021} + {2.000, 2.00000, 0.00000, 0.26000}, // + {2.020, 2.03300, 0.01441, 0.23845}, // + {2.040, 2.06500, 0.02568, 0.20310}, // + {2.060, 2.09800, 0.03655, 0.18593}, // + {2.080, 2.13200, 0.04701, 0.17341}, // + {2.100, 2.17800, 0.05596, 0.14049}, // + {2.120, 2.19300, 0.06805, 0.17417}, // + {2.140, 2.23000, 0.07733, 0.16145}, // + {2.160, 2.26400, 0.08677, 0.15649}, // + {2.180, 2.30500, 0.09529, 0.14374}, // + {2.200, 2.32900, 0.10530, 0.15212}, // + {2.220, 2.38300, 0.11230, 0.12974}, // + {2.240, 2.39800, 0.12257, 0.14433}, // + {2.260, 2.41800, 0.13236, 0.15439}, // + {2.280, 2.47200, 0.13867, 0.13431}, // + {2.300, 2.50900, 0.14649, 0.13021} // }; -static constexpr size_t NUM_RECORDS = sizeof(kRatio_N_DOverA_Theta) / sizeof(kRatio_N_DOverA_Theta[0]); -static constexpr double MAX_RATIO = kRatio_N_DOverA_Theta[NUM_RECORDS-1][0]; -static constexpr double RATIO_STEP = kRatio_N_DOverA_Theta[1][0] - kRatio_N_DOverA_Theta[0][0]; +static constexpr size_t NUM_RECORDS = + sizeof(kRatio_N_DOverA_Theta) / sizeof(kRatio_N_DOverA_Theta[0]); +static constexpr double MAX_RATIO = kRatio_N_DOverA_Theta[NUM_RECORDS - 1][0]; +static constexpr double RATIO_STEP = + kRatio_N_DOverA_Theta[1][0] - kRatio_N_DOverA_Theta[0][0]; static constexpr double gap(double corner_radius) { return 0.2924303407 * corner_radius; @@ -51,23 +53,28 @@ struct ExpandedVariables { // Result will be assigned with [n, d_over_a, theta] static ExpandedVariables ExpandVariables(double ratio, double a, double g) { constexpr Scalar MIN_RATIO = kRatio_N_DOverA_Theta[0][0]; - double steps = std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS - 1); + double steps = + std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS - 1); size_t lo = std::clamp((size_t)std::floor(steps), 0, NUM_RECORDS - 2); size_t hi = lo + 1; double pos = steps - lo; - double n = (1-pos) * kRatio_N_DOverA_Theta[lo][1] + pos * kRatio_N_DOverA_Theta[hi][1]; - double d = ((1-pos) * kRatio_N_DOverA_Theta[lo][2] + pos * kRatio_N_DOverA_Theta[hi][2]) * a; + double n = (1 - pos) * kRatio_N_DOverA_Theta[lo][1] + + pos * kRatio_N_DOverA_Theta[hi][1]; + double d = ((1 - pos) * kRatio_N_DOverA_Theta[lo][2] + + pos * kRatio_N_DOverA_Theta[hi][2]) * + a; double R = (a - d - g) * sqrt(2); - double theta = (1-pos) * kRatio_N_DOverA_Theta[lo][3] + pos * kRatio_N_DOverA_Theta[hi][3]; + double theta = (1 - pos) * kRatio_N_DOverA_Theta[lo][3] + + pos * kRatio_N_DOverA_Theta[hi][3]; double x0 = d + R * sin(theta); double y0 = pow(pow(a, n) - pow(x0, n), 1 / n); return ExpandedVariables{ - .n = n, - .d = d, - .R = R, - .x0 = x0, - .y0 = y0, + .n = n, + .d = d, + .R = R, + .x0 = x0, + .y0 = y0, }; } @@ -75,14 +82,44 @@ static Point operator+(Point a, DPoint b) { return Point{static_cast(a.x + b.x), static_cast(a.y + b.y)}; } -static void DrawCircularArc(DPoint start, DPoint end, double r) { - // TODO +// Draw a circular arc from `start` to `end` with a radius of `r`. +// +// It is assumed that `start` is north-west to `end`, and the center +// of the circle is south-west to both points. +static void DrawCircularArc(std::vector& output, + DPoint start, + DPoint end, + double r) { + /* Denote the middle point of S and E as M. The key is to find the center of + * the circle. + * S --__ + * / ⟍ `、 + * / M ⟍ \ E + * / ⟋ + * / ⟋ + * / ⟋ + * / ⟋ + * ⟋ C + */ + + const DPoint s_to_e = end - start; + const DPoint m = (start + end) / 2; + const DPoint normalized_c_to_m = DPoint(-s_to_e.y, s_to_e.x).Normalize(); + const double distance_sm = s_to_e.GetLength() / 2; + const double distance_cm = sqrt(r * r - distance_sm * distance_sm); + const DPoint c = m - distance_cm * normalized_c_to_m; + const Scalar angle_sce = asinf(distance_sm / r) * 2; + // TODO(dkwingsmt): determine parameter values based on scaling factor. + Scalar step = kPi / 80; + const DPoint c_to_s = start - c; + for (Scalar angle = step; angle < angle_sce; angle += step) { + output.push_back(c_to_s.Rotate(Radians(-angle)) + c); + } } RoundSuperellipseGeometry::RoundSuperellipseGeometry(const Rect& bounds, - Scalar corner_radius) - : bounds_(bounds), - corner_radius_(corner_radius) {} + Scalar corner_radius) + : bounds_(bounds), corner_radius_(corner_radius) {} RoundSuperellipseGeometry::~RoundSuperellipseGeometry() {} @@ -90,33 +127,38 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - DSize size {bounds_.GetWidth(), bounds_.GetHeight()}; + DSize size{bounds_.GetWidth(), bounds_.GetHeight()}; Point center = bounds_.GetCenter(); // printf("Center %.2f, %.2f\n", center.x, center.y); - const double r = std::min(corner_radius_, std::min(size.width / 2, size.height / 2)); + const double r = std::min(corner_radius_, + std::min(size.width / 2, size.height / 2)); // Derive critical variables - const DSize ratio_wh = {std::min(size.width / r, MAX_RATIO), std::min(size.height / r, MAX_RATIO)}; + const DSize ratio_wh = {std::min(size.width / r, MAX_RATIO), + std::min(size.height / r, MAX_RATIO)}; const DSize ab = ratio_wh * r / 2; const DSize s_wh = size / 2 - ab; const double g = gap(corner_radius_); - const ExpandedVariables var_w = ExpandVariables(ratio_wh.width, ab.width, g); - const ExpandedVariables var_h = ExpandVariables(ratio_wh.height, ab.height, g); const double c = ab.width - size.height / 2; - /* Generate the points for the top right quadrant, and then mirror to the other - * quadrants. The following figure shows the top 1/8 arc (from 0 to pi/4), which + // Points for the first quadrant. + std::vector points; + points.reserve(41); + // TODO(dkwingsmt): determine parameter values based on scaling factor. + double step = kPi / 80; + + /* Generate the points for the top 1/8 arc (from 0 to pi/4), which * is a square-like squircle offset by (0, -c). * * straight superelipse * ↓ ↓ * A B J ↙ circular arc - * -------------__、 - * | | / ヽ M + * ------------..._ + * | | / `、 M * | | / ⟋ \ * | | / ⟋ \ - * | | ᨀ | + * | | .⟋ | * O + | / D | * | |/ | * E--------|------------| @@ -129,32 +171,22 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( * B = (s_w, h/2) * J = (x0_w, y0_w - c) * M = (w/2 - g, h/2 - g) - * - * The next 1/8 arc (from pi/4 to pi/2) has mirrored points: - * - * J' = (y0_h + c, x0_h) - * B' = (w/2, s_h) - * A' = (w/2, 0) */ - // TODO(dkwingsmt): determine parameter values based on scaling factor. - double step = kPi / 80; - - std::vector points; - points.reserve(41); - const DPoint pointM {size.width / 2 - g, size.height / 2 - g}; + const DPoint pointM{size.width / 2 - g, size.height / 2 - g}; + const ExpandedVariables var_w = ExpandVariables(ratio_wh.width, ab.width, g); // A points.emplace_back(0, size.height / 2); // B points.emplace_back(s_wh.width, size.height / 2); - // Arc BJ (both ends exclusive) + // Superellipsoid arc BJ (both ends exclusive) { const double target_slope = var_w.y0 / var_w.x0; - for (double angle = 0 + step; ; angle += step) { + for (double angle = 0 + step;; angle += step) { const double x = ab.width * pow(abs(sin(angle)), 2 / var_w.n); const double y = ab.width * pow(abs(cos(angle)), 2 / var_w.n); - if (y / x <= target_slope) { + if (y <= target_slope * x) { break; } points.emplace_back(x + s_wh.width, y - c); @@ -162,26 +194,40 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( } // J points.emplace_back(var_w.x0 + s_wh.width, var_w.y0 - c); - // Arc JM (both ends exclusive) - DrawCircularArc({var_w.x0 + s_wh.width, var_w.y0 - c}, pointM, var_w.R); + // Circular arc JM (both ends exclusive) + DrawCircularArc(points, {var_w.x0 + s_wh.width, var_w.y0 - c}, pointM, + var_w.R); // M points.push_back(pointM); - // Arc MJ' (both ends exclusive) - DrawCircularArc(pointM, {var_h.y0 + c, var_h.x0 + s_wh.height}, var_h.R); + + /* Now generate the next 1/8 arc, i.e. from pi/4 to pi/2. It is similar to the + * first 1/8 arc but mirrored according to the 45deg line: + * + * M = (w/2 - g, h/2 - g) + * J' = (y0_h + c, x0_h) + * B' = (w/2, s_h) + * A' = (w/2, 0) + */ + + const ExpandedVariables var_h = + ExpandVariables(ratio_wh.height, ab.height, g); + // Circular arc MJ' (both ends exclusive) + DrawCircularArc(points, pointM, {var_h.y0 + c, var_h.x0 + s_wh.height}, + var_h.R); // J' points.emplace_back(var_h.y0 + c, var_w.x0 + s_wh.height); - // Arc J'B' (both ends exclusive) + // Superellipsoid arc J'B' (both ends exclusive) { const double target_slope = var_h.y0 / var_h.x0; std::vector points_bsj; - for (double angle = 0; ; angle += step) { + for (double angle = 0;; angle += step) { const double x = ab.height * pow(abs(sin(angle)), 2 / var_h.n); const double y = ab.height * pow(abs(cos(angle)), 2 / var_h.n); - if (y / x <= target_slope) { + if (y <= target_slope * x) { break; } - // The coordinates are inverted because this half of arc is mirrowed by the - // 45deg line. + // The coordinates are inverted because this half of arc is mirrowed by + // the 45deg line. points_bsj.emplace_back(y + c, x + s_wh.height); } for (size_t i = 0; i < points_bsj.size(); i++) { @@ -195,8 +241,8 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( static constexpr DPoint reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; - // Reflect into the 4 quadrants and generate the tessellated mesh. The - // iteration order is reversed so that the trianges are continuous from + // Reflect the 1/4 arc into the 4 quadrants and generate the tessellated mesh. + // The iteration order is reversed so that the trianges are continuous from // quadrant to quadrant. std::vector geometry; geometry.reserve(1 + 4 * points.size()); @@ -248,7 +294,7 @@ std::optional RoundSuperellipseGeometry::GetCoverage( } bool RoundSuperellipseGeometry::CoversArea(const Matrix& transform, - const Rect& rect) const { + const Rect& rect) const { return false; } From be8fd8787928552365be5aa845ac1b69f13469a6 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 20 Nov 2024 13:27:39 -0800 Subject: [PATCH 05/29] Fix too large corner_radius --- impeller/entity/entity_unittests.cc | 15 ++++----- .../geometry/round_superellipse_geometry.cc | 33 +++++++++++-------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 2ff26b2d40749..af50fc923165a 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2322,7 +2322,7 @@ TEST_P(EntityTest, DrawSuperEllipse) { ImGui::End(); auto contents = std::make_shared(); - static std::unique_ptr geom = + std::unique_ptr geom = std::make_unique(Point{400, 400}, radius, degree, alpha, beta); contents->SetColor(color); @@ -2339,7 +2339,6 @@ TEST_P(EntityTest, DrawSuperEllipse) { TEST_P(EntityTest, DrawRoundSuperEllipse) { auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { - // printf("One draw\n"); // UI state. static float center_x = 100; static float center_y = 100; @@ -2349,15 +2348,15 @@ TEST_P(EntityTest, DrawRoundSuperEllipse) { static Color color = Color::Red(); ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); - ImGui::SliderFloat("Center X", ¢er_x, 0, 500); - ImGui::SliderFloat("Center Y", ¢er_y, 0, 500); - ImGui::SliderFloat("Width", &width, 1, 400); - ImGui::SliderFloat("Height", &height, 1, 400); - ImGui::SliderFloat("Corner radius", &corner_radius, 1, 250); + ImGui::SliderFloat("Center X", ¢er_x, 0, 1000); + ImGui::SliderFloat("Center Y", ¢er_y, 0, 1000); + ImGui::SliderFloat("Width", &width, 1, 800); + ImGui::SliderFloat("Height", &height, 1, 800); + ImGui::SliderFloat("Corner radius", &corner_radius, 1, 500); ImGui::End(); auto contents = std::make_shared(); - static std::unique_ptr geom = + std::unique_ptr geom = std::make_unique( Rect::MakeOriginSize({center_x, center_y}, {width, height}), corner_radius); diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 9673f51802599..ffb81f824b695 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -93,21 +93,22 @@ static void DrawCircularArc(std::vector& output, /* Denote the middle point of S and E as M. The key is to find the center of * the circle. * S --__ - * / ⟍ `、 - * / M ⟍ \ E - * / ⟋ + * / ⟍ `、 + * / M ⟍\ + * / ⟋ E * / ⟋ * / ⟋ * / ⟋ - * ⟋ C + * C ⟋ */ const DPoint s_to_e = end - start; const DPoint m = (start + end) / 2; - const DPoint normalized_c_to_m = DPoint(-s_to_e.y, s_to_e.x).Normalize(); + const DPoint c_to_m = DPoint(-s_to_e.y, s_to_e.x); const double distance_sm = s_to_e.GetLength() / 2; const double distance_cm = sqrt(r * r - distance_sm * distance_sm); - const DPoint c = m - distance_cm * normalized_c_to_m; + const DPoint c = m - distance_cm * c_to_m.Normalize(); + ; const Scalar angle_sce = asinf(distance_sm / r) * 2; // TODO(dkwingsmt): determine parameter values based on scaling factor. Scalar step = kPi / 80; @@ -117,9 +118,17 @@ static void DrawCircularArc(std::vector& output, } } +// static void DrawOctantSquareLikeSquircle(std::vector& output, DPoint center, double a, double corner_radius) { +// } + +static Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { + return std::min(corner_radius, + std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); +} + RoundSuperellipseGeometry::RoundSuperellipseGeometry(const Rect& bounds, Scalar corner_radius) - : bounds_(bounds), corner_radius_(corner_radius) {} + : bounds_(bounds), corner_radius_(LimitRadius(corner_radius, bounds)) {} RoundSuperellipseGeometry::~RoundSuperellipseGeometry() {} @@ -129,14 +138,10 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( RenderPass& pass) const { DSize size{bounds_.GetWidth(), bounds_.GetHeight()}; Point center = bounds_.GetCenter(); - // printf("Center %.2f, %.2f\n", center.x, center.y); - const double r = std::min(corner_radius_, - std::min(size.width / 2, size.height / 2)); - // Derive critical variables - const DSize ratio_wh = {std::min(size.width / r, MAX_RATIO), - std::min(size.height / r, MAX_RATIO)}; - const DSize ab = ratio_wh * r / 2; + const DSize ratio_wh = {std::min(size.width / corner_radius_, MAX_RATIO), + std::min(size.height / corner_radius_, MAX_RATIO)}; + const DSize ab = ratio_wh * corner_radius_ / 2; const DSize s_wh = size / 2 - ab; const double g = gap(corner_radius_); From 79d8f7a7fcdfc5464f102783baa2508352523077 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 20 Nov 2024 14:20:37 -0800 Subject: [PATCH 06/29] Fixed everything! --- .../geometry/round_superellipse_geometry.cc | 225 +++++++++--------- 1 file changed, 112 insertions(+), 113 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index ffb81f824b695..7c3d9ca098a36 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include +#include #include #include "flutter/impeller/entity/geometry/round_superellipse_geometry.h" @@ -32,6 +33,7 @@ static constexpr double kRatio_N_DOverA_Theta[][4] = { static constexpr size_t NUM_RECORDS = sizeof(kRatio_N_DOverA_Theta) / sizeof(kRatio_N_DOverA_Theta[0]); +static constexpr Scalar MIN_RATIO = kRatio_N_DOverA_Theta[0][0]; static constexpr double MAX_RATIO = kRatio_N_DOverA_Theta[NUM_RECORDS - 1][0]; static constexpr double RATIO_STEP = kRatio_N_DOverA_Theta[1][0] - kRatio_N_DOverA_Theta[0][0]; @@ -50,33 +52,33 @@ struct ExpandedVariables { double y0; }; -// Result will be assigned with [n, d_over_a, theta] -static ExpandedVariables ExpandVariables(double ratio, double a, double g) { - constexpr Scalar MIN_RATIO = kRatio_N_DOverA_Theta[0][0]; - double steps = - std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS - 1); - size_t lo = std::clamp((size_t)std::floor(steps), 0, NUM_RECORDS - 2); - size_t hi = lo + 1; - double pos = steps - lo; - - double n = (1 - pos) * kRatio_N_DOverA_Theta[lo][1] + - pos * kRatio_N_DOverA_Theta[hi][1]; - double d = ((1 - pos) * kRatio_N_DOverA_Theta[lo][2] + - pos * kRatio_N_DOverA_Theta[hi][2]) * - a; - double R = (a - d - g) * sqrt(2); - double theta = (1 - pos) * kRatio_N_DOverA_Theta[lo][3] + - pos * kRatio_N_DOverA_Theta[hi][3]; - double x0 = d + R * sin(theta); - double y0 = pow(pow(a, n) - pow(x0, n), 1 / n); - return ExpandedVariables{ - .n = n, - .d = d, - .R = R, - .x0 = x0, - .y0 = y0, - }; -} +// // Result will be assigned with [n, d_over_a, theta] +// static ExpandedVariables ExpandVariables(double ratio, double a, double g) { +// constexpr Scalar MIN_RATIO = kRatio_N_DOverA_Theta[0][0]; +// double steps = +// std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS - +// 1); +// size_t lo = std::clamp((size_t)std::floor(steps), 0, NUM_RECORDS - +// 2); size_t hi = lo + 1; double pos = steps - lo; + +// double n = (1 - pos) * kRatio_N_DOverA_Theta[lo][1] + +// pos * kRatio_N_DOverA_Theta[hi][1]; +// double d = ((1 - pos) * kRatio_N_DOverA_Theta[lo][2] + +// pos * kRatio_N_DOverA_Theta[hi][2]) * +// a; +// double R = (a - d - g) * sqrt(2); +// double theta = (1 - pos) * kRatio_N_DOverA_Theta[lo][3] + +// pos * kRatio_N_DOverA_Theta[hi][3]; +// double x0 = d + R * sin(theta); +// double y0 = pow(pow(a, n) - pow(x0, n), 1 / n); +// return ExpandedVariables{ +// .n = n, +// .d = d, +// .R = R, +// .x0 = x0, +// .y0 = y0, +// }; +// } static Point operator+(Point a, DPoint b) { return Point{static_cast(a.x + b.x), static_cast(a.y + b.y)}; @@ -118,41 +120,11 @@ static void DrawCircularArc(std::vector& output, } } -// static void DrawOctantSquareLikeSquircle(std::vector& output, DPoint center, double a, double corner_radius) { -// } - -static Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { - return std::min(corner_radius, - std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); -} - -RoundSuperellipseGeometry::RoundSuperellipseGeometry(const Rect& bounds, - Scalar corner_radius) - : bounds_(bounds), corner_radius_(LimitRadius(corner_radius, bounds)) {} - -RoundSuperellipseGeometry::~RoundSuperellipseGeometry() {} - -GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass) const { - DSize size{bounds_.GetWidth(), bounds_.GetHeight()}; - Point center = bounds_.GetCenter(); - // Derive critical variables - const DSize ratio_wh = {std::min(size.width / corner_radius_, MAX_RATIO), - std::min(size.height / corner_radius_, MAX_RATIO)}; - const DSize ab = ratio_wh * corner_radius_ / 2; - const DSize s_wh = size / 2 - ab; - const double g = gap(corner_radius_); - - const double c = ab.width - size.height / 2; - - // Points for the first quadrant. - std::vector points; - points.reserve(41); - // TODO(dkwingsmt): determine parameter values based on scaling factor. - double step = kPi / 80; - +static void DrawOctantSquareLikeSquircle(std::vector& output, + double size, + double corner_radius, + DPoint center, + bool flip) { /* Generate the points for the top 1/8 arc (from 0 to pi/4), which * is a square-like squircle offset by (0, -c). * @@ -178,71 +150,98 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( * M = (w/2 - g, h/2 - g) */ - const DPoint pointM{size.width / 2 - g, size.height / 2 - g}; - const ExpandedVariables var_w = ExpandVariables(ratio_wh.width, ab.width, g); + // Derive critical variables + const double ratio = {std::min(size / corner_radius, MAX_RATIO)}; + const double a = ratio * corner_radius / 2; + const double s = size / 2 - a; + const double g = gap(corner_radius); + + // Use look up table to derive critical variables + double steps = + std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS - 1); + size_t lo = std::clamp((size_t)std::floor(steps), 0, NUM_RECORDS - 2); + size_t hi = lo + 1; + double pos = steps - lo; + + double n = (1 - pos) * kRatio_N_DOverA_Theta[lo][1] + + pos * kRatio_N_DOverA_Theta[hi][1]; + double d = ((1 - pos) * kRatio_N_DOverA_Theta[lo][2] + + pos * kRatio_N_DOverA_Theta[hi][2]) * + a; + double R = (a - d - g) * sqrt(2); + double theta = (1 - pos) * kRatio_N_DOverA_Theta[lo][3] + + pos * kRatio_N_DOverA_Theta[hi][3]; + double x0 = d + R * sin(theta); + double y0 = pow(pow(a, n) - pow(x0, n), 1 / n); + + const DPoint pointM{size / 2 - g, size / 2 - g}; + + // Points without applying `flip` and `center` + std::vector points; // A - points.emplace_back(0, size.height / 2); - // B - points.emplace_back(s_wh.width, size.height / 2); - // Superellipsoid arc BJ (both ends exclusive) + points.emplace_back(0, size / 2); + // Superellipsoid arc BJ (B inclusive, J exclusive) { - const double target_slope = var_w.y0 / var_w.x0; - for (double angle = 0 + step;; angle += step) { - const double x = ab.width * pow(abs(sin(angle)), 2 / var_w.n); - const double y = ab.width * pow(abs(cos(angle)), 2 / var_w.n); + // TODO(dkwingsmt): determine parameter values based on scaling factor. + constexpr Scalar step = kPi / 80; + const Scalar target_slope = y0 / x0; + for (Scalar angle = 0;; angle += step) { + const double x = a * pow(abs(sinf(angle)), 2 / n); + const double y = a * pow(abs(cosf(angle)), 2 / n); if (y <= target_slope * x) { break; } - points.emplace_back(x + s_wh.width, y - c); + points.emplace_back(x + s, y + s); } } // J - points.emplace_back(var_w.x0 + s_wh.width, var_w.y0 - c); + points.emplace_back(x0 + s, y0 + s); // Circular arc JM (both ends exclusive) - DrawCircularArc(points, {var_w.x0 + s_wh.width, var_w.y0 - c}, pointM, - var_w.R); - // M - points.push_back(pointM); - - /* Now generate the next 1/8 arc, i.e. from pi/4 to pi/2. It is similar to the - * first 1/8 arc but mirrored according to the 45deg line: - * - * M = (w/2 - g, h/2 - g) - * J' = (y0_h + c, x0_h) - * B' = (w/2, s_h) - * A' = (w/2, 0) - */ + DrawCircularArc(points, {x0 + s, y0 + s}, pointM, R); - const ExpandedVariables var_h = - ExpandVariables(ratio_wh.height, ab.height, g); - // Circular arc MJ' (both ends exclusive) - DrawCircularArc(points, pointM, {var_h.y0 + c, var_h.x0 + s_wh.height}, - var_h.R); - // J' - points.emplace_back(var_h.y0 + c, var_w.x0 + s_wh.height); - // Superellipsoid arc J'B' (both ends exclusive) - { - const double target_slope = var_h.y0 / var_h.x0; - std::vector points_bsj; - for (double angle = 0;; angle += step) { - const double x = ab.height * pow(abs(sin(angle)), 2 / var_h.n); - const double y = ab.height * pow(abs(cos(angle)), 2 / var_h.n); - if (y <= target_slope * x) { - break; - } - // The coordinates are inverted because this half of arc is mirrowed by - // the 45deg line. - points_bsj.emplace_back(y + c, x + s_wh.height); + if (!flip) { + for (const DPoint& point : points) { + output.push_back(point + center); } - for (size_t i = 0; i < points_bsj.size(); i++) { - points.push_back(points_bsj[points_bsj.size() - i - 1]); + } else { + for (size_t i = 0; i < points.size(); i++) { + const DPoint& point = points[points.size() - i - 1]; + output.emplace_back(point.y + center.x, point.x + center.y); } } - // B - points.emplace_back(size.width / 2, s_wh.height); - // A' - points.emplace_back(size.width / 2, 0); +} + +static Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { + return std::min(corner_radius, + std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); +} + +RoundSuperellipseGeometry::RoundSuperellipseGeometry(const Rect& bounds, + Scalar corner_radius) + : bounds_(bounds), corner_radius_(LimitRadius(corner_radius, bounds)) {} + +RoundSuperellipseGeometry::~RoundSuperellipseGeometry() {} + +GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + DSize size{bounds_.GetWidth(), bounds_.GetHeight()}; + Point center = bounds_.GetCenter(); + + DSize ab = size / 2; + const double c = ab.width - size.height / 2; + + // Points for the first quadrant. + std::vector points; + points.reserve(41); + + DrawOctantSquareLikeSquircle(points, size.width, corner_radius_, + DPoint{0, -c}, false); + points.push_back(DPoint(size / 2) - gap(corner_radius_)); // Point M + DrawOctantSquareLikeSquircle(points, size.height, corner_radius_, + DPoint{c, 0}, true); static constexpr DPoint reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; From b023965b6f313a0bfdf5fae566aefbc4baa5d216 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 20 Nov 2024 17:16:10 -0800 Subject: [PATCH 07/29] Improve doc and structure --- .../geometry/round_superellipse_geometry.cc | 153 ++++++++---------- 1 file changed, 66 insertions(+), 87 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 7c3d9ca098a36..5537a4ab9f1e2 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -38,47 +38,14 @@ static constexpr double MAX_RATIO = kRatio_N_DOverA_Theta[NUM_RECORDS - 1][0]; static constexpr double RATIO_STEP = kRatio_N_DOverA_Theta[1][0] - kRatio_N_DOverA_Theta[0][0]; +static constexpr Scalar STEP = kPi / 80; + static constexpr double gap(double corner_radius) { return 0.2924303407 * corner_radius; } typedef TSize DSize; typedef TPoint DPoint; -struct ExpandedVariables { - double n; - double d; - double R; - double x0; - double y0; -}; - -// // Result will be assigned with [n, d_over_a, theta] -// static ExpandedVariables ExpandVariables(double ratio, double a, double g) { -// constexpr Scalar MIN_RATIO = kRatio_N_DOverA_Theta[0][0]; -// double steps = -// std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS - -// 1); -// size_t lo = std::clamp((size_t)std::floor(steps), 0, NUM_RECORDS - -// 2); size_t hi = lo + 1; double pos = steps - lo; - -// double n = (1 - pos) * kRatio_N_DOverA_Theta[lo][1] + -// pos * kRatio_N_DOverA_Theta[hi][1]; -// double d = ((1 - pos) * kRatio_N_DOverA_Theta[lo][2] + -// pos * kRatio_N_DOverA_Theta[hi][2]) * -// a; -// double R = (a - d - g) * sqrt(2); -// double theta = (1 - pos) * kRatio_N_DOverA_Theta[lo][3] + -// pos * kRatio_N_DOverA_Theta[hi][3]; -// double x0 = d + R * sin(theta); -// double y0 = pow(pow(a, n) - pow(x0, n), 1 / n); -// return ExpandedVariables{ -// .n = n, -// .d = d, -// .R = R, -// .x0 = x0, -// .y0 = y0, -// }; -// } static Point operator+(Point a, DPoint b) { return Point{static_cast(a.x + b.x), static_cast(a.y + b.y)}; @@ -88,6 +55,9 @@ static Point operator+(Point a, DPoint b) { // // It is assumed that `start` is north-west to `end`, and the center // of the circle is south-west to both points. +// +// The resulting points are appended to `output` and include the starting point +// but exclude the ending point. static void DrawCircularArc(std::vector& output, DPoint start, DPoint end, @@ -98,10 +68,10 @@ static void DrawCircularArc(std::vector& output, * / ⟍ `、 * / M ⟍\ * / ⟋ E - * / ⟋ + * / ⟋ ↗ * / ⟋ - * / ⟋ - * C ⟋ + * / ⟋ r + * C ⟋ ↙ */ const DPoint s_to_e = end - start; @@ -110,83 +80,93 @@ static void DrawCircularArc(std::vector& output, const double distance_sm = s_to_e.GetLength() / 2; const double distance_cm = sqrt(r * r - distance_sm * distance_sm); const DPoint c = m - distance_cm * c_to_m.Normalize(); - ; const Scalar angle_sce = asinf(distance_sm / r) * 2; - // TODO(dkwingsmt): determine parameter values based on scaling factor. - Scalar step = kPi / 80; const DPoint c_to_s = start - c; - for (Scalar angle = step; angle < angle_sce; angle += step) { + for (Scalar angle = 0; angle < angle_sce; angle += STEP) { output.push_back(c_to_s.Rotate(Radians(-angle)) + c); } } +static double lerp(size_t item, size_t left, size_t frac) { + return (1 - frac) * kRatio_N_DOverA_Theta[left][item] + + frac * kRatio_N_DOverA_Theta[left + 1][item]; +} + +// Draws an arc representing the top 1/8 segment of a square-like rounded +// superellipse. The arc spans from 0 to pi/4, moving clockwise starting from +// the positive Y-axis. +// +// The full square-like rounded superellipse has a width and height specified by +// `size`. It is centered at `center` and features rounded corners determined by +// `corner_radius`. The `corner_radius` corresponds to the `cornerRadius` +// parameter in SwiftUI, rather than the literal radius of corner circles. +// +// If `flip` is true, the function instead produces the next 1/8 arc, spanning +// from pi/4 to pi/8. Technically, the X and Y coordinates of the arc points +// are swapped before applying `center`, and their order is reversed as well. +// +// The list of the resulting points is appended to `output`, and includes the +// starting point but exclude the ending point. static void DrawOctantSquareLikeSquircle(std::vector& output, double size, double corner_radius, DPoint center, bool flip) { - /* Generate the points for the top 1/8 arc (from 0 to pi/4), which - * is a square-like squircle offset by (0, -c). + /* Ignoring `center` and `flip`, the following figure shows the first quadrant + * of a square-like rounded superellipse. The target arc consists the stretch + * (AB), a superellipsoid arc (BJ), and a circular arc (JM). Assume the + * coordinate of J is (xJ, yJ), and M is (size/2 - g, size/2 - g). * - * straight superelipse - * ↓ ↓ - * A B J ↙ circular arc - * ------------..._ - * | | / `、 M - * | | / ⟋ \ - * | | / ⟋ \ - * | | .⟋ | - * O + | / D | - * | |/ | - * E--------|------------| - * S + * straight superelipse + * ↓ ↓ + * A B J circular arc + * ---------...._↙ + * | | / `⟍ M + * | | / ⟋ ⟍ + * | | /θ ⟋ \ + * | | /◝⟋ | + * | | ᜱ | + * | | / D | + * ↑ +----+ | + * s | | | + * ↓ +----+---------------| + * O S + * ← s → + * ←------ size/2 ------→ * - * Ignore the central offset until the last step, and assume point O, the - * origin, is (0, 0), - * - * A = (0, h/2) - * B = (s_w, h/2) - * J = (x0_w, y0_w - c) - * M = (w/2 - g, h/2 - g) */ - // Derive critical variables const double ratio = {std::min(size / corner_radius, MAX_RATIO)}; const double a = ratio * corner_radius / 2; const double s = size / 2 - a; const double g = gap(corner_radius); // Use look up table to derive critical variables - double steps = + const double steps = std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS - 1); - size_t lo = std::clamp((size_t)std::floor(steps), 0, NUM_RECORDS - 2); - size_t hi = lo + 1; - double pos = steps - lo; - - double n = (1 - pos) * kRatio_N_DOverA_Theta[lo][1] + - pos * kRatio_N_DOverA_Theta[hi][1]; - double d = ((1 - pos) * kRatio_N_DOverA_Theta[lo][2] + - pos * kRatio_N_DOverA_Theta[hi][2]) * - a; - double R = (a - d - g) * sqrt(2); - double theta = (1 - pos) * kRatio_N_DOverA_Theta[lo][3] + - pos * kRatio_N_DOverA_Theta[hi][3]; - double x0 = d + R * sin(theta); - double y0 = pow(pow(a, n) - pow(x0, n), 1 / n); + const size_t left = + std::clamp((size_t)std::floor(steps), 0, NUM_RECORDS - 2); + const double frac = steps - left; + const double n = lerp(1, left, frac); + const double d = lerp(2, left, frac) * a; + const double theta = lerp(3, left, frac); + + const double R = (a - d - g) * sqrt(2); + const double xJ = d + R * sin(theta); + const double yJ = pow(pow(a, n) - pow(xJ, n), 1 / n); const DPoint pointM{size / 2 - g, size / 2 - g}; - // Points without applying `flip` and `center` + // Points without applying `flip` and `center`. std::vector points; + points.reserve(21); // A points.emplace_back(0, size / 2); // Superellipsoid arc BJ (B inclusive, J exclusive) { - // TODO(dkwingsmt): determine parameter values based on scaling factor. - constexpr Scalar step = kPi / 80; - const Scalar target_slope = y0 / x0; - for (Scalar angle = 0;; angle += step) { + const Scalar target_slope = yJ / xJ; + for (Scalar angle = 0;; angle += STEP) { const double x = a * pow(abs(sinf(angle)), 2 / n); const double y = a * pow(abs(cosf(angle)), 2 / n); if (y <= target_slope * x) { @@ -196,9 +176,8 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, } } // J - points.emplace_back(x0 + s, y0 + s); - // Circular arc JM (both ends exclusive) - DrawCircularArc(points, {x0 + s, y0 + s}, pointM, R); + // Circular arc JM (B inclusive, M exclusive) + DrawCircularArc(points, {xJ + s, yJ + s}, pointM, R); if (!flip) { for (const DPoint& point : points) { From 3f64371d3bdf9110fdfd15d18b1fa63a31600bd8 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 20 Nov 2024 18:06:50 -0800 Subject: [PATCH 08/29] Format --- .../geometry/round_superellipse_geometry.cc | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 5537a4ab9f1e2..ba2f8ab2d200d 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -12,7 +12,20 @@ namespace impeller { -static constexpr double kRatio_N_DOverA_Theta[][4] = { +typedef TSize DSize; +typedef TPoint DPoint; + +// A look up table with precomputed variables. +// +// The columns represent the following variabls respectively: +// +// * ratio = size / a +// * n +// * d / a +// * theta +// +// For definition of the variables, see DrawOctantSquareLikeSquircle. +static constexpr double kPrecomputedVariables[][4] = { {2.000, 2.00000, 0.00000, 0.26000}, // {2.020, 2.03300, 0.01441, 0.23845}, // {2.040, 2.06500, 0.02568, 0.20310}, // @@ -32,11 +45,11 @@ static constexpr double kRatio_N_DOverA_Theta[][4] = { }; static constexpr size_t NUM_RECORDS = - sizeof(kRatio_N_DOverA_Theta) / sizeof(kRatio_N_DOverA_Theta[0]); -static constexpr Scalar MIN_RATIO = kRatio_N_DOverA_Theta[0][0]; -static constexpr double MAX_RATIO = kRatio_N_DOverA_Theta[NUM_RECORDS - 1][0]; + sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]); +static constexpr Scalar MIN_RATIO = kPrecomputedVariables[0][0]; +static constexpr double MAX_RATIO = kPrecomputedVariables[NUM_RECORDS - 1][0]; static constexpr double RATIO_STEP = - kRatio_N_DOverA_Theta[1][0] - kRatio_N_DOverA_Theta[0][0]; + kPrecomputedVariables[1][0] - kPrecomputedVariables[0][0]; static constexpr Scalar STEP = kPi / 80; @@ -44,9 +57,6 @@ static constexpr double gap(double corner_radius) { return 0.2924303407 * corner_radius; } -typedef TSize DSize; -typedef TPoint DPoint; - static Point operator+(Point a, DPoint b) { return Point{static_cast(a.x + b.x), static_cast(a.y + b.y)}; } @@ -88,8 +98,8 @@ static void DrawCircularArc(std::vector& output, } static double lerp(size_t item, size_t left, size_t frac) { - return (1 - frac) * kRatio_N_DOverA_Theta[left][item] + - frac * kRatio_N_DOverA_Theta[left + 1][item]; + return (1 - frac) * kPrecomputedVariables[left][item] + + frac * kPrecomputedVariables[left + 1][item]; } // Draws an arc representing the top 1/8 segment of a square-like rounded @@ -206,8 +216,8 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - DSize size{bounds_.GetWidth(), bounds_.GetHeight()}; - Point center = bounds_.GetCenter(); + const DSize size{bounds_.GetWidth(), bounds_.GetHeight()}; + const Point center = bounds_.GetCenter(); DSize ab = size / 2; const double c = ab.width - size.height / 2; From 3e45cb78c5716ece55bf051985c1e80a24b6d0ce Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 20 Nov 2024 18:26:05 -0800 Subject: [PATCH 09/29] Better docs --- .../geometry/round_superellipse_geometry.cc | 122 +++++++++--------- .../geometry/round_superellipse_geometry.h | 11 +- 2 files changed, 69 insertions(+), 64 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index ba2f8ab2d200d..653a2931a8ed1 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -12,9 +12,6 @@ namespace impeller { -typedef TSize DSize; -typedef TPoint DPoint; - // A look up table with precomputed variables. // // The columns represent the following variabls respectively: @@ -25,7 +22,7 @@ typedef TPoint DPoint; // * theta // // For definition of the variables, see DrawOctantSquareLikeSquircle. -static constexpr double kPrecomputedVariables[][4] = { +static constexpr Scalar kPrecomputedVariables[][4] = { {2.000, 2.00000, 0.00000, 0.26000}, // {2.020, 2.03300, 0.01441, 0.23845}, // {2.040, 2.06500, 0.02568, 0.20310}, // @@ -47,20 +44,16 @@ static constexpr double kPrecomputedVariables[][4] = { static constexpr size_t NUM_RECORDS = sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]); static constexpr Scalar MIN_RATIO = kPrecomputedVariables[0][0]; -static constexpr double MAX_RATIO = kPrecomputedVariables[NUM_RECORDS - 1][0]; -static constexpr double RATIO_STEP = +static constexpr Scalar MAX_RATIO = kPrecomputedVariables[NUM_RECORDS - 1][0]; +static constexpr Scalar RATIO_STEP = kPrecomputedVariables[1][0] - kPrecomputedVariables[0][0]; static constexpr Scalar STEP = kPi / 80; -static constexpr double gap(double corner_radius) { +static constexpr Scalar gap(Scalar corner_radius) { return 0.2924303407 * corner_radius; } -static Point operator+(Point a, DPoint b) { - return Point{static_cast(a.x + b.x), static_cast(a.y + b.y)}; -} - // Draw a circular arc from `start` to `end` with a radius of `r`. // // It is assumed that `start` is north-west to `end`, and the center @@ -68,10 +61,10 @@ static Point operator+(Point a, DPoint b) { // // The resulting points are appended to `output` and include the starting point // but exclude the ending point. -static void DrawCircularArc(std::vector& output, - DPoint start, - DPoint end, - double r) { +static void DrawCircularArc(std::vector& output, + Point start, + Point end, + Scalar r) { /* Denote the middle point of S and E as M. The key is to find the center of * the circle. * S --__ @@ -84,20 +77,20 @@ static void DrawCircularArc(std::vector& output, * C ⟋ ↙ */ - const DPoint s_to_e = end - start; - const DPoint m = (start + end) / 2; - const DPoint c_to_m = DPoint(-s_to_e.y, s_to_e.x); - const double distance_sm = s_to_e.GetLength() / 2; - const double distance_cm = sqrt(r * r - distance_sm * distance_sm); - const DPoint c = m - distance_cm * c_to_m.Normalize(); + const Point s_to_e = end - start; + const Point m = (start + end) / 2; + const Point c_to_m = Point(-s_to_e.y, s_to_e.x); + const Scalar distance_sm = s_to_e.GetLength() / 2; + const Scalar distance_cm = sqrt(r * r - distance_sm * distance_sm); + const Point c = m - distance_cm * c_to_m.Normalize(); const Scalar angle_sce = asinf(distance_sm / r) * 2; - const DPoint c_to_s = start - c; + const Point c_to_s = start - c; for (Scalar angle = 0; angle < angle_sce; angle += STEP) { output.push_back(c_to_s.Rotate(Radians(-angle)) + c); } } -static double lerp(size_t item, size_t left, size_t frac) { +static Scalar lerp(size_t item, size_t left, size_t frac) { return (1 - frac) * kPrecomputedVariables[left][item] + frac * kPrecomputedVariables[left + 1][item]; } @@ -117,15 +110,18 @@ static double lerp(size_t item, size_t left, size_t frac) { // // The list of the resulting points is appended to `output`, and includes the // starting point but exclude the ending point. -static void DrawOctantSquareLikeSquircle(std::vector& output, - double size, - double corner_radius, - DPoint center, +static void DrawOctantSquareLikeSquircle(std::vector& output, + Scalar size, + Scalar corner_radius, + Point center, bool flip) { /* Ignoring `center` and `flip`, the following figure shows the first quadrant * of a square-like rounded superellipse. The target arc consists the stretch - * (AB), a superellipsoid arc (BJ), and a circular arc (JM). Assume the - * coordinate of J is (xJ, yJ), and M is (size/2 - g, size/2 - g). + * (AB), a superellipsoid arc (BJ), and a circular arc (JM). + * + * Define gap (g) as the distance between point M and the bounding box, + * therefore point M is at (size/2 - g, size/2 - g). Assume the coordinate of + * J is (xJ, yJ). * * straight superelipse * ↓ ↓ @@ -143,32 +139,31 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, * O S * ← s → * ←------ size/2 ------→ - * */ - const double ratio = {std::min(size / corner_radius, MAX_RATIO)}; - const double a = ratio * corner_radius / 2; - const double s = size / 2 - a; - const double g = gap(corner_radius); + const Scalar ratio = {std::min(size / corner_radius, MAX_RATIO)}; + const Scalar a = ratio * corner_radius / 2; + const Scalar s = size / 2 - a; + const Scalar g = gap(corner_radius); // Use look up table to derive critical variables - const double steps = + const Scalar steps = std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS - 1); const size_t left = std::clamp((size_t)std::floor(steps), 0, NUM_RECORDS - 2); - const double frac = steps - left; - const double n = lerp(1, left, frac); - const double d = lerp(2, left, frac) * a; - const double theta = lerp(3, left, frac); + const Scalar frac = steps - left; + const Scalar n = lerp(1, left, frac); + const Scalar d = lerp(2, left, frac) * a; + const Scalar theta = lerp(3, left, frac); - const double R = (a - d - g) * sqrt(2); - const double xJ = d + R * sin(theta); - const double yJ = pow(pow(a, n) - pow(xJ, n), 1 / n); + const Scalar R = (a - d - g) * sqrt(2); + const Scalar xJ = d + R * sin(theta); + const Scalar yJ = pow(pow(a, n) - pow(xJ, n), 1 / n); - const DPoint pointM{size / 2 - g, size / 2 - g}; + const Point pointM{size / 2 - g, size / 2 - g}; // Points without applying `flip` and `center`. - std::vector points; + std::vector points; points.reserve(21); // A @@ -177,8 +172,8 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, { const Scalar target_slope = yJ / xJ; for (Scalar angle = 0;; angle += STEP) { - const double x = a * pow(abs(sinf(angle)), 2 / n); - const double y = a * pow(abs(cosf(angle)), 2 / n); + const Scalar x = a * pow(abs(sinf(angle)), 2 / n); + const Scalar y = a * pow(abs(cosf(angle)), 2 / n); if (y <= target_slope * x) { break; } @@ -190,12 +185,12 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, DrawCircularArc(points, {xJ + s, yJ + s}, pointM, R); if (!flip) { - for (const DPoint& point : points) { + for (const Point& point : points) { output.push_back(point + center); } } else { for (size_t i = 0; i < points.size(); i++) { - const DPoint& point = points[points.size() - i - 1]; + const Point& point = points[points.size() - i - 1]; output.emplace_back(point.y + center.x, point.x + center.y); } } @@ -216,23 +211,30 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - const DSize size{bounds_.GetWidth(), bounds_.GetHeight()}; + const Size size = bounds_.GetSize(); const Point center = bounds_.GetCenter(); - DSize ab = size / 2; - const double c = ab.width - size.height / 2; - - // Points for the first quadrant. - std::vector points; + // The full shape is divided into 4 segments: the top and bottom edges come + // from two square-like rounded superellipses (width-aligned), while the left + // and right squircles come from another two (height-aligned). + // + // Denote the distance from the center of the square-like squircles to the + // origin as `c`. The width-aligned square-like squircle and the + // height-aligned one have the same offset in different directions. + const Scalar c = (size.width - size.height) / 2; + + // Draw the first quadrant of the shape and store in `points`. It will be + // mirrored to other quadrants later. + std::vector points; points.reserve(41); - DrawOctantSquareLikeSquircle(points, size.width, corner_radius_, - DPoint{0, -c}, false); - points.push_back(DPoint(size / 2) - gap(corner_radius_)); // Point M - DrawOctantSquareLikeSquircle(points, size.height, corner_radius_, - DPoint{c, 0}, true); + DrawOctantSquareLikeSquircle(points, size.width, corner_radius_, Point{0, -c}, + false); + points.push_back(Point(size / 2) - gap(corner_radius_)); // Point M + DrawOctantSquareLikeSquircle(points, size.height, corner_radius_, Point{c, 0}, + true); - static constexpr DPoint reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; + static constexpr Point reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; // Reflect the 1/4 arc into the 4 quadrants and generate the tessellated mesh. // The iteration order is reversed so that the trianges are continuous from diff --git a/impeller/entity/geometry/round_superellipse_geometry.h b/impeller/entity/geometry/round_superellipse_geometry.h index c8644c1e62e7e..c5d5c7bfc9965 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.h +++ b/impeller/entity/geometry/round_superellipse_geometry.h @@ -11,15 +11,18 @@ namespace impeller { /// Geometry class that can generate vertices for a rounded superellipse. /// -/// A Superellipse is an ellipse-like shape that is defined by the parameters N, +/// A superellipse is an ellipse-like shape that is defined by the parameters N, /// alpha, and beta: /// /// 1 = |x / b| ^n + |y / a| ^n /// -/// The radius and center apply a uniform scaling and offset that is separate -/// from alpha or beta. When n = 4, the shape is referred to as a rectellipse. +/// A rounded superellipse is a square-like superellipse (a=b) with its four +/// corners replaced by circular arcs. It replicates the `RoundedRectangle` +/// shape in SwiftUI with corner style `.continuous`. /// -/// See also: https://en.wikipedia.org/wiki/Superellipse +/// The `bounds` defines the position and size of the shape. The `corner_radius` +/// corresponds to SwiftUI's `cornerRadius` parameter, which is close to, but +/// not exactly equals to, the radius of the corner circles. class RoundSuperellipseGeometry final : public Geometry { public: explicit RoundSuperellipseGeometry(const Rect& bounds, Scalar corner_radius); From f8e32bf2a7ddedd4c81c8d763a1fc08f73f9965f Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 21 Nov 2024 18:04:04 -0800 Subject: [PATCH 10/29] Lint --- .../geometry/round_superellipse_geometry.cc | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 653a2931a8ed1..26d8bf3b21326 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -41,14 +41,14 @@ static constexpr Scalar kPrecomputedVariables[][4] = { {2.300, 2.50900, 0.14649, 0.13021} // }; -static constexpr size_t NUM_RECORDS = +static constexpr size_t kNumRecords = sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]); -static constexpr Scalar MIN_RATIO = kPrecomputedVariables[0][0]; -static constexpr Scalar MAX_RATIO = kPrecomputedVariables[NUM_RECORDS - 1][0]; -static constexpr Scalar RATIO_STEP = +static constexpr Scalar kMinRatio = kPrecomputedVariables[0][0]; +static constexpr Scalar kMaxRatio = kPrecomputedVariables[kNumRecords - 1][0]; +static constexpr Scalar kRatioStep = kPrecomputedVariables[1][0] - kPrecomputedVariables[0][0]; -static constexpr Scalar STEP = kPi / 80; +static constexpr Scalar kAngleStep = kPi / 80; static constexpr Scalar gap(Scalar corner_radius) { return 0.2924303407 * corner_radius; @@ -85,14 +85,17 @@ static void DrawCircularArc(std::vector& output, const Point c = m - distance_cm * c_to_m.Normalize(); const Scalar angle_sce = asinf(distance_sm / r) * 2; const Point c_to_s = start - c; - for (Scalar angle = 0; angle < angle_sce; angle += STEP) { + + Scalar angle = 0; + while (angle < angle_sce) { output.push_back(c_to_s.Rotate(Radians(-angle)) + c); + angle += kAngleStep; } } -static Scalar lerp(size_t item, size_t left, size_t frac) { - return (1 - frac) * kPrecomputedVariables[left][item] + - frac * kPrecomputedVariables[left + 1][item]; +static Scalar lerp(size_t column, size_t left, size_t frac) { + return (1 - frac) * kPrecomputedVariables[left][column] + + frac * kPrecomputedVariables[left + 1][column]; } // Draws an arc representing the top 1/8 segment of a square-like rounded @@ -141,16 +144,16 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, * ←------ size/2 ------→ */ - const Scalar ratio = {std::min(size / corner_radius, MAX_RATIO)}; + const Scalar ratio = {std::min(size / corner_radius, kMaxRatio)}; const Scalar a = ratio * corner_radius / 2; const Scalar s = size / 2 - a; const Scalar g = gap(corner_radius); // Use look up table to derive critical variables const Scalar steps = - std::clamp((ratio - MIN_RATIO) / RATIO_STEP, 0, NUM_RECORDS - 1); + std::clamp((ratio - kMinRatio) / kRatioStep, 0, kNumRecords - 1); const size_t left = - std::clamp((size_t)std::floor(steps), 0, NUM_RECORDS - 2); + std::clamp((size_t)std::floor(steps), 0, kNumRecords - 2); const Scalar frac = steps - left; const Scalar n = lerp(1, left, frac); const Scalar d = lerp(2, left, frac) * a; @@ -171,13 +174,15 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, // Superellipsoid arc BJ (B inclusive, J exclusive) { const Scalar target_slope = yJ / xJ; - for (Scalar angle = 0;; angle += STEP) { + Scalar angle = 0; + while (true) { const Scalar x = a * pow(abs(sinf(angle)), 2 / n); const Scalar y = a * pow(abs(cosf(angle)), 2 / n); if (y <= target_slope * x) { break; } points.emplace_back(x + s, y + s); + angle += kAngleStep; } } // J From 183484f7646c6a94048a4bff74f35eb9f403eed1 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 21 Nov 2024 18:09:21 -0800 Subject: [PATCH 11/29] Fix doc --- impeller/entity/geometry/round_superellipse_geometry.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 26d8bf3b21326..94ea84d8bfcfd 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -119,8 +119,8 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, Point center, bool flip) { /* Ignoring `center` and `flip`, the following figure shows the first quadrant - * of a square-like rounded superellipse. The target arc consists the stretch - * (AB), a superellipsoid arc (BJ), and a circular arc (JM). + * of a square-like rounded superellipse. The target arc consists of the + * "stretch" (AB), a superellipsoid arc (BJ), and a circular arc (JM). * * Define gap (g) as the distance between point M and the bounding box, * therefore point M is at (size/2 - g, size/2 - g). Assume the coordinate of @@ -172,6 +172,7 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, // A points.emplace_back(0, size / 2); // Superellipsoid arc BJ (B inclusive, J exclusive) + // https://math.stackexchange.com/questions/2573746/superellipse-parametric-equation { const Scalar target_slope = yJ / xJ; Scalar angle = 0; @@ -185,10 +186,10 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, angle += kAngleStep; } } - // J // Circular arc JM (B inclusive, M exclusive) DrawCircularArc(points, {xJ + s, yJ + s}, pointM, R); + // Apply `flip` and `center`. if (!flip) { for (const Point& point : points) { output.push_back(point + center); From beb491e7c6053a9a1bfce831db4ac2971af6e476 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 22 Nov 2024 13:36:07 -0800 Subject: [PATCH 12/29] Remove most consts --- .../geometry/round_superellipse_geometry.cc | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 94ea84d8bfcfd..d025a3ae9107c 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -77,14 +77,14 @@ static void DrawCircularArc(std::vector& output, * C ⟋ ↙ */ - const Point s_to_e = end - start; - const Point m = (start + end) / 2; - const Point c_to_m = Point(-s_to_e.y, s_to_e.x); - const Scalar distance_sm = s_to_e.GetLength() / 2; - const Scalar distance_cm = sqrt(r * r - distance_sm * distance_sm); - const Point c = m - distance_cm * c_to_m.Normalize(); - const Scalar angle_sce = asinf(distance_sm / r) * 2; - const Point c_to_s = start - c; + Point s_to_e = end - start; + Point m = (start + end) / 2; + Point c_to_m = Point(-s_to_e.y, s_to_e.x); + Scalar distance_sm = s_to_e.GetLength() / 2; + Scalar distance_cm = sqrt(r * r - distance_sm * distance_sm); + Point c = m - distance_cm * c_to_m.Normalize(); + Scalar angle_sce = asinf(distance_sm / r) * 2; + Point c_to_s = start - c; Scalar angle = 0; while (angle < angle_sce) { @@ -144,26 +144,26 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, * ←------ size/2 ------→ */ - const Scalar ratio = {std::min(size / corner_radius, kMaxRatio)}; - const Scalar a = ratio * corner_radius / 2; - const Scalar s = size / 2 - a; - const Scalar g = gap(corner_radius); + Scalar ratio = {std::min(size / corner_radius, kMaxRatio)}; + Scalar a = ratio * corner_radius / 2; + Scalar s = size / 2 - a; + Scalar g = gap(corner_radius); // Use look up table to derive critical variables - const Scalar steps = + Scalar steps = std::clamp((ratio - kMinRatio) / kRatioStep, 0, kNumRecords - 1); - const size_t left = + size_t left = std::clamp((size_t)std::floor(steps), 0, kNumRecords - 2); - const Scalar frac = steps - left; - const Scalar n = lerp(1, left, frac); - const Scalar d = lerp(2, left, frac) * a; - const Scalar theta = lerp(3, left, frac); + Scalar frac = steps - left; + Scalar n = lerp(1, left, frac); + Scalar d = lerp(2, left, frac) * a; + Scalar theta = lerp(3, left, frac); - const Scalar R = (a - d - g) * sqrt(2); - const Scalar xJ = d + R * sin(theta); - const Scalar yJ = pow(pow(a, n) - pow(xJ, n), 1 / n); + Scalar R = (a - d - g) * sqrt(2); + Scalar xJ = d + R * sin(theta); + Scalar yJ = pow(pow(a, n) - pow(xJ, n), 1 / n); - const Point pointM{size / 2 - g, size / 2 - g}; + Point pointM{size / 2 - g, size / 2 - g}; // Points without applying `flip` and `center`. std::vector points; @@ -177,8 +177,8 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, const Scalar target_slope = yJ / xJ; Scalar angle = 0; while (true) { - const Scalar x = a * pow(abs(sinf(angle)), 2 / n); - const Scalar y = a * pow(abs(cosf(angle)), 2 / n); + Scalar x = a * pow(abs(sinf(angle)), 2 / n); + Scalar y = a * pow(abs(cosf(angle)), 2 / n); if (y <= target_slope * x) { break; } @@ -221,8 +221,8 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( const Point center = bounds_.GetCenter(); // The full shape is divided into 4 segments: the top and bottom edges come - // from two square-like rounded superellipses (width-aligned), while the left - // and right squircles come from another two (height-aligned). + // from two square-like rounded superellipses (called "width-aligned"), while + // the left and right squircles come from another two ("height-aligned"). // // Denote the distance from the center of the square-like squircles to the // origin as `c`. The width-aligned square-like squircle and the From a42a90851ff6fdef27ec4899163d961968a88eca Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 22 Nov 2024 13:40:21 -0800 Subject: [PATCH 13/29] do-while --- .../entity/geometry/round_superellipse_geometry.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index d025a3ae9107c..7f5df2c2199f3 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -176,15 +176,15 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, { const Scalar target_slope = yJ / xJ; Scalar angle = 0; - while (true) { - Scalar x = a * pow(abs(sinf(angle)), 2 / n); - Scalar y = a * pow(abs(cosf(angle)), 2 / n); - if (y <= target_slope * x) { - break; - } + Scalar x; + Scalar y; + // Do-while works here because at least one point (B) is added. + do { + x = a * pow(abs(sinf(angle)), 2 / n); + y = a * pow(abs(cosf(angle)), 2 / n); points.emplace_back(x + s, y + s); angle += kAngleStep; - } + } while (y > target_slope * x); } // Circular arc JM (B inclusive, M exclusive) DrawCircularArc(points, {xJ + s, yJ + s}, pointM, R); From 920314f59f1608ad4d1ccdf4881c289e9ec85b9d Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sat, 23 Nov 2024 00:43:25 -0800 Subject: [PATCH 14/29] Use existing buffer to generate vertex and index buffer --- impeller/display_list/canvas.cc | 5 +- .../geometry/round_superellipse_geometry.cc | 99 +++++++++++++------ 2 files changed, 74 insertions(+), 30 deletions(-) diff --git a/impeller/display_list/canvas.cc b/impeller/display_list/canvas.cc index eae343d2fcd2f..a877efa8bde3d 100644 --- a/impeller/display_list/canvas.cc +++ b/impeller/display_list/canvas.cc @@ -36,6 +36,7 @@ #include "impeller/entity/geometry/point_field_geometry.h" #include "impeller/entity/geometry/rect_geometry.h" #include "impeller/entity/geometry/round_rect_geometry.h" +#include "impeller/entity/geometry/round_superellipse_geometry.h" #include "impeller/entity/geometry/stroke_path_geometry.h" #include "impeller/entity/save_layer_utils.h" #include "impeller/geometry/color.h" @@ -473,7 +474,9 @@ void Canvas::DrawRect(const Rect& rect, const Paint& paint) { entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - RectGeometry geom(rect); + Scalar radius = std::min(rect.GetWidth(), rect.GetHeight()) / 3; + RoundSuperellipseGeometry geom(rect, radius); + // RectGeometry geom(rect); AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); } diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 7f5df2c2199f3..bb8998851c07e 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -12,6 +12,43 @@ namespace impeller { +// Generates an index list to convert vertices from a triangle fan structure +// into a triangle list format. +// +// The generated index list follows the pattern: +// +// [0, 1, 2, 0, 2, 3, 0, 3, 4, ...]. +// +// The initial portion of the index list is always the same, regardless of the +// number of vertices, and only needs to be extended as needed. This makes +// caching efficient, as the code can reuse the existing list and simply extract +// the required segment. +class TriangleFanIndices { + public: + TriangleFanIndices() {} + size_t Ensure(size_t contour_point_count); + const uint16_t* data() { return indices_.data(); } + + static size_t IndexCount(size_t contour_point_count) { + return (contour_point_count - 1) * 3; + } + + private: + std::vector indices_; +}; + +size_t TriangleFanIndices::Ensure(size_t contour_point_count) { + size_t index_count = IndexCount(contour_point_count); + indices_.reserve(index_count); + for (size_t i = indices_.size(); i < index_count; i += 3) { + size_t start_id = i / 3 + 1; + indices_[i] = 0; + indices_[i + 1] = start_id; + indices_[i + 2] = start_id + 1; + } + return index_count; +} + // A look up table with precomputed variables. // // The columns represent the following variabls respectively: @@ -229,8 +266,8 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( // height-aligned one have the same offset in different directions. const Scalar c = (size.width - size.height) / 2; - // Draw the first quadrant of the shape and store in `points`. It will be - // mirrored to other quadrants later. + // Draw the first quadrant of the shape and store in `points`, including both + // ends. It will be mirrored to other quadrants later. std::vector points; points.reserve(41); @@ -240,49 +277,53 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( DrawOctantSquareLikeSquircle(points, size.height, corner_radius_, Point{c, 0}, true); + auto& host_buffer = renderer.GetTransientsBuffer(); + static constexpr Point reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; - // Reflect the 1/4 arc into the 4 quadrants and generate the tessellated mesh. - // The iteration order is reversed so that the trianges are continuous from - // quadrant to quadrant. - std::vector geometry; - geometry.reserve(1 + 4 * points.size()); - geometry.push_back(center); + // Generate the point data of the tessellated mesh. The first point in the + // point buffer is the center. The next `contour_point_count` points are the + // 1/4 arc mirrored into the 4 quadrants. The point data is organized in the + // structure of a triangle fan. + size_t contour_point_count = 4 * (points.size() - 1) + 1; + BufferView vertex_buffer = host_buffer.Emplace( + nullptr, sizeof(Point) * (contour_point_count + 1), alignof(Point)); + Point* vertex_data = + reinterpret_cast(vertex_buffer.GetBuffer()->OnGetContents() + + vertex_buffer.GetRange().offset); + *(vertex_data++) = center; // All arcs include the starting point and exclude the ending point. for (auto i = 0u; i < points.size() - 1; i++) { - geometry.push_back(center + (reflection[0] * points[i])); + *(vertex_data++) = center + (reflection[0] * points[i]); } for (auto i = points.size() - 1; i >= 1; i--) { - geometry.push_back(center + (reflection[1] * points[i])); + *(vertex_data++) = center + (reflection[1] * points[i]); } for (auto i = 0u; i < points.size() - 1; i++) { - geometry.push_back(center + (reflection[2] * points[i])); + *(vertex_data++) = center + (reflection[2] * points[i]); } for (auto i = points.size() - 1; i >= 1; i--) { - geometry.push_back(center + (reflection[3] * points[i])); - } - geometry.push_back(center + points[0]); - - std::vector indices; - indices.reserve(geometry.size() * 3); - for (auto i = 2u; i < geometry.size(); i++) { - indices.push_back(0); - indices.push_back(i - 1); - indices.push_back(i); + *(vertex_data++) = center + (reflection[3] * points[i]); } + *vertex_data = center + points[0]; + + static TriangleFanIndices indices_cache; + size_t index_count = indices_cache.Ensure(contour_point_count); + BufferView index_buffer = host_buffer.Emplace( + nullptr, sizeof(uint16_t) * index_count, alignof(uint16_t)); + uint16_t* index_data = + reinterpret_cast(index_buffer.GetBuffer()->OnGetContents() + + index_buffer.GetRange().offset); + + std::memcpy(index_data, indices_cache.data(), sizeof(uint16_t) * index_count); - auto& host_buffer = renderer.GetTransientsBuffer(); return GeometryResult{ .type = PrimitiveType::kTriangle, .vertex_buffer = { - .vertex_buffer = host_buffer.Emplace( - geometry.data(), geometry.size() * sizeof(Point), - alignof(Point)), - .index_buffer = host_buffer.Emplace( - indices.data(), indices.size() * sizeof(uint16_t), - alignof(uint16_t)), - .vertex_count = indices.size(), + .vertex_buffer = vertex_buffer, + .index_buffer = index_buffer, + .vertex_count = index_count, .index_type = IndexType::k16bit, }, .transform = entity.GetShaderTransform(pass), From 2d282fe0aceb19034530a2d215de6bb38d77d462 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sat, 23 Nov 2024 23:40:34 -0800 Subject: [PATCH 15/29] Fix arc generation --- .../geometry/round_superellipse_geometry.cc | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index bb8998851c07e..934c1918c0cb3 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -213,15 +213,15 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, { const Scalar target_slope = yJ / xJ; Scalar angle = 0; - Scalar x; - Scalar y; - // Do-while works here because at least one point (B) is added. - do { - x = a * pow(abs(sinf(angle)), 2 / n); - y = a * pow(abs(cosf(angle)), 2 / n); + while (true) { + Scalar x = a * pow(abs(sinf(angle)), 2 / n); + Scalar y = a * pow(abs(cosf(angle)), 2 / n); + if (y <= target_slope * x) { + break; + } points.emplace_back(x + s, y + s); angle += kAngleStep; - } while (y > target_slope * x); + } } // Circular arc JM (B inclusive, M exclusive) DrawCircularArc(points, {xJ + s, yJ + s}, pointM, R); @@ -307,6 +307,18 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( } *vertex_data = center + points[0]; + if (renderer.GetDeviceCapabilities().SupportsTriangleFan()) { + return GeometryResult{ + .type = PrimitiveType::kTriangleFan, + .vertex_buffer = + { + .vertex_buffer = vertex_buffer, + .index_type = IndexType::kNone, + }, + .transform = entity.GetShaderTransform(pass), + }; + } + static TriangleFanIndices indices_cache; size_t index_count = indices_cache.Ensure(contour_point_count); BufferView index_buffer = host_buffer.Emplace( From 9fd533a312ff1693a03a11466edcdb11fc9f56aa Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sat, 23 Nov 2024 23:46:29 -0800 Subject: [PATCH 16/29] do-while works --- .../geometry/round_superellipse_geometry.cc | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 934c1918c0cb3..91fa7ade7db89 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -213,15 +213,16 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, { const Scalar target_slope = yJ / xJ; Scalar angle = 0; - while (true) { - Scalar x = a * pow(abs(sinf(angle)), 2 / n); - Scalar y = a * pow(abs(cosf(angle)), 2 / n); - if (y <= target_slope * x) { - break; - } + // The first point, B, should always be added, which happens to work well + // with do-while. + Scalar x = 0; + Scalar y = a; + do { points.emplace_back(x + s, y + s); angle += kAngleStep; - } + x = a * pow(abs(sinf(angle)), 2 / n); + y = a * pow(abs(cosf(angle)), 2 / n); + } while(y > target_slope * x); } // Circular arc JM (B inclusive, M exclusive) DrawCircularArc(points, {xJ + s, yJ + s}, pointM, R); From 9572d39dfdbb9a1db6eaffe3f0ea60578503ef29 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 26 Nov 2024 01:01:29 -0800 Subject: [PATCH 17/29] Change to triangle strips --- .../geometry/round_superellipse_geometry.cc | 190 ++++++++---------- 1 file changed, 84 insertions(+), 106 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 91fa7ade7db89..fc219f6d43b57 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -12,43 +12,7 @@ namespace impeller { -// Generates an index list to convert vertices from a triangle fan structure -// into a triangle list format. -// -// The generated index list follows the pattern: -// -// [0, 1, 2, 0, 2, 3, 0, 3, 4, ...]. -// -// The initial portion of the index list is always the same, regardless of the -// number of vertices, and only needs to be extended as needed. This makes -// caching efficient, as the code can reuse the existing list and simply extract -// the required segment. -class TriangleFanIndices { - public: - TriangleFanIndices() {} - size_t Ensure(size_t contour_point_count); - const uint16_t* data() { return indices_.data(); } - - static size_t IndexCount(size_t contour_point_count) { - return (contour_point_count - 1) * 3; - } - - private: - std::vector indices_; -}; - -size_t TriangleFanIndices::Ensure(size_t contour_point_count) { - size_t index_count = IndexCount(contour_point_count); - indices_.reserve(index_count); - for (size_t i = indices_.size(); i < index_count; i += 3) { - size_t start_id = i / 3 + 1; - indices_[i] = 0; - indices_[i + 1] = start_id; - indices_[i + 2] = start_id + 1; - } - return index_count; -} - +namespace { // A look up table with precomputed variables. // // The columns represent the following variabls respectively: @@ -59,7 +23,7 @@ size_t TriangleFanIndices::Ensure(size_t contour_point_count) { // * theta // // For definition of the variables, see DrawOctantSquareLikeSquircle. -static constexpr Scalar kPrecomputedVariables[][4] = { +constexpr Scalar kPrecomputedVariables[][4] = { {2.000, 2.00000, 0.00000, 0.26000}, // {2.020, 2.03300, 0.01441, 0.23845}, // {2.040, 2.06500, 0.02568, 0.20310}, // @@ -78,16 +42,16 @@ static constexpr Scalar kPrecomputedVariables[][4] = { {2.300, 2.50900, 0.14649, 0.13021} // }; -static constexpr size_t kNumRecords = +constexpr size_t kNumRecords = sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]); -static constexpr Scalar kMinRatio = kPrecomputedVariables[0][0]; -static constexpr Scalar kMaxRatio = kPrecomputedVariables[kNumRecords - 1][0]; -static constexpr Scalar kRatioStep = +constexpr Scalar kMinRatio = kPrecomputedVariables[0][0]; +constexpr Scalar kMaxRatio = kPrecomputedVariables[kNumRecords - 1][0]; +constexpr Scalar kRatioStep = kPrecomputedVariables[1][0] - kPrecomputedVariables[0][0]; -static constexpr Scalar kAngleStep = kPi / 80; +constexpr Scalar kAngleStep = kPi / 80; -static constexpr Scalar gap(Scalar corner_radius) { +constexpr Scalar gap(Scalar corner_radius) { return 0.2924303407 * corner_radius; } @@ -98,10 +62,10 @@ static constexpr Scalar gap(Scalar corner_radius) { // // The resulting points are appended to `output` and include the starting point // but exclude the ending point. -static void DrawCircularArc(std::vector& output, - Point start, - Point end, - Scalar r) { +void DrawCircularArc(std::vector& output, + Point start, + Point end, + Scalar r) { /* Denote the middle point of S and E as M. The key is to find the center of * the circle. * S --__ @@ -130,7 +94,7 @@ static void DrawCircularArc(std::vector& output, } } -static Scalar lerp(size_t column, size_t left, size_t frac) { +Scalar lerp(size_t column, size_t left, size_t frac) { return (1 - frac) * kPrecomputedVariables[left][column] + frac * kPrecomputedVariables[left + 1][column]; } @@ -150,11 +114,11 @@ static Scalar lerp(size_t column, size_t left, size_t frac) { // // The list of the resulting points is appended to `output`, and includes the // starting point but exclude the ending point. -static void DrawOctantSquareLikeSquircle(std::vector& output, - Scalar size, - Scalar corner_radius, - Point center, - bool flip) { +void DrawOctantSquareLikeSquircle(std::vector& output, + Scalar size, + Scalar corner_radius, + Point center, + bool flip) { /* Ignoring `center` and `flip`, the following figure shows the first quadrant * of a square-like rounded superellipse. The target arc consists of the * "stretch" (AB), a superellipsoid arc (BJ), and a circular arc (JM). @@ -222,7 +186,7 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, angle += kAngleStep; x = a * pow(abs(sinf(angle)), 2 / n); y = a * pow(abs(cosf(angle)), 2 / n); - } while(y > target_slope * x); + } while (y > target_slope * x); } // Circular arc JM (B inclusive, M exclusive) DrawCircularArc(points, {xJ + s, yJ + s}, pointM, R); @@ -240,11 +204,64 @@ static void DrawOctantSquareLikeSquircle(std::vector& output, } } -static Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { +Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { return std::min(corner_radius, std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); } +class TriangleStripArranger { + public: + TriangleStripArranger(const Point* quad, size_t quad_length) + : quad_(quad), quad_length_(quad_length) {} + + void Output(Point* output, const Point& center) { + size_t index_count = 0; + + output[index_count++] = GetPoint(0) + center; + + size_t a = 1; + size_t b = quad_length_ * 4 - 1; + while (a < b) { + output[index_count++] = GetPoint(a) + center; + output[index_count++] = GetPoint(b) + center; + a++; + b--; + } + if (a == b) { + output[index_count++] = GetPoint(b) + center; + } + } + + private: + const Point* quad_; + const size_t quad_length_; + + Point GetPoint(size_t i) { + if (i < quad_length_) { + return quad_[i]; + } + i = i - quad_length_; + if (i < quad_length_) { + return quad_[quad_length_ - i] * reflection[1]; + } + i = i - quad_length_; + if (i < quad_length_) { + return quad_[i] * reflection[2]; + } + i = i - quad_length_; + if (i < quad_length_) { + return quad_[quad_length_ - i] * reflection[3]; + } else { + // Unreachable + return Point(); + } + } + + static constexpr Point reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; +}; + +} // namespace + RoundSuperellipseGeometry::RoundSuperellipseGeometry(const Rect& bounds, Scalar corner_radius) : bounds_(bounds), corner_radius_(LimitRadius(corner_radius, bounds)) {} @@ -278,66 +295,27 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( DrawOctantSquareLikeSquircle(points, size.height, corner_radius_, Point{c, 0}, true); - auto& host_buffer = renderer.GetTransientsBuffer(); + // Mirror the arc into 4 quadrants while rearranging the points. - static constexpr Point reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; - - // Generate the point data of the tessellated mesh. The first point in the - // point buffer is the center. The next `contour_point_count` points are the - // 1/4 arc mirrored into the 4 quadrants. The point data is organized in the - // structure of a triangle fan. - size_t contour_point_count = 4 * (points.size() - 1) + 1; - BufferView vertex_buffer = host_buffer.Emplace( - nullptr, sizeof(Point) * (contour_point_count + 1), alignof(Point)); + // The `contour_point_count` include all points on the border. The "-1" comes + // from duplicate ends from the mirrored arcs. + size_t contour_point_count = 4 * (points.size() - 1); + BufferView vertex_buffer = renderer.GetTransientsBuffer().Emplace( + nullptr, sizeof(Point) * contour_point_count, alignof(Point)); Point* vertex_data = reinterpret_cast(vertex_buffer.GetBuffer()->OnGetContents() + vertex_buffer.GetRange().offset); - *(vertex_data++) = center; - // All arcs include the starting point and exclude the ending point. - for (auto i = 0u; i < points.size() - 1; i++) { - *(vertex_data++) = center + (reflection[0] * points[i]); - } - for (auto i = points.size() - 1; i >= 1; i--) { - *(vertex_data++) = center + (reflection[1] * points[i]); - } - for (auto i = 0u; i < points.size() - 1; i++) { - *(vertex_data++) = center + (reflection[2] * points[i]); - } - for (auto i = points.size() - 1; i >= 1; i--) { - *(vertex_data++) = center + (reflection[3] * points[i]); - } - *vertex_data = center + points[0]; - - if (renderer.GetDeviceCapabilities().SupportsTriangleFan()) { - return GeometryResult{ - .type = PrimitiveType::kTriangleFan, - .vertex_buffer = - { - .vertex_buffer = vertex_buffer, - .index_type = IndexType::kNone, - }, - .transform = entity.GetShaderTransform(pass), - }; - } - - static TriangleFanIndices indices_cache; - size_t index_count = indices_cache.Ensure(contour_point_count); - BufferView index_buffer = host_buffer.Emplace( - nullptr, sizeof(uint16_t) * index_count, alignof(uint16_t)); - uint16_t* index_data = - reinterpret_cast(index_buffer.GetBuffer()->OnGetContents() + - index_buffer.GetRange().offset); - std::memcpy(index_data, indices_cache.data(), sizeof(uint16_t) * index_count); + TriangleStripArranger arranger(points.data(), points.size() - 1); + arranger.Output(vertex_data, center); return GeometryResult{ - .type = PrimitiveType::kTriangle, + .type = PrimitiveType::kTriangleStrip, .vertex_buffer = { .vertex_buffer = vertex_buffer, - .index_buffer = index_buffer, - .vertex_count = index_count, - .index_type = IndexType::k16bit, + .vertex_count = contour_point_count, + .index_type = IndexType::kNone, }, .transform = entity.GetShaderTransform(pass), }; From 2ccada0e1557872d3d24cf32680e30a981d1f028 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sun, 1 Dec 2024 17:00:22 -0800 Subject: [PATCH 18/29] MirrorIntoTriangleStrip and better ending audit --- .../geometry/round_superellipse_geometry.cc | 109 +++++++++--------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index fc219f6d43b57..8584d20f199f2 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -112,8 +112,9 @@ Scalar lerp(size_t column, size_t left, size_t frac) { // from pi/4 to pi/8. Technically, the X and Y coordinates of the arc points // are swapped before applying `center`, and their order is reversed as well. // -// The list of the resulting points is appended to `output`, and includes the -// starting point but exclude the ending point. +// The resulting arc, which includes the starting point (the middle of the flat +// side) and excludes the ending point (the x=y point), is applied with `flip`, +// and then appended to `output`. void DrawOctantSquareLikeSquircle(std::vector& output, Scalar size, Scalar corner_radius, @@ -130,7 +131,7 @@ void DrawOctantSquareLikeSquircle(std::vector& output, * straight superelipse * ↓ ↓ * A B J circular arc - * ---------...._↙ + * ---------...._ ↙ * | | / `⟍ M * | | / ⟋ ⟍ * | | /θ ⟋ \ @@ -139,7 +140,7 @@ void DrawOctantSquareLikeSquircle(std::vector& output, * | | / D | * ↑ +----+ | * s | | | - * ↓ +----+---------------| + * ↓ +----+---------------| A' * O S * ← s → * ←------ size/2 ------→ @@ -209,56 +210,63 @@ Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); } -class TriangleStripArranger { - public: - TriangleStripArranger(const Point* quad, size_t quad_length) - : quad_(quad), quad_length_(quad_length) {} +constexpr Point kReflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; - void Output(Point* output, const Point& center) { - size_t index_count = 0; - - output[index_count++] = GetPoint(0) + center; - - size_t a = 1; - size_t b = quad_length_ * 4 - 1; - while (a < b) { - output[index_count++] = GetPoint(a) + center; - output[index_count++] = GetPoint(b) + center; - a++; - b--; - } - if (a == b) { - output[index_count++] = GetPoint(b) + center; - } - } - - private: - const Point* quad_; - const size_t quad_length_; - - Point GetPoint(size_t i) { - if (i < quad_length_) { - return quad_[i]; +// Mirror the point list `quad` into other quadrants and output as a triangle +// strip. +// +// The input arc `quad` should reside in the first quadrant, starting at +// positive Y axis and ending at positive X axis (both ends inclusive), for a +// total of `quad_length` points. This function mirrors the arc into 4 +// quadrants, offset the result by `center`, and rearrange it as a triangle +// strip, which is appended to `output`. +// +// A total of (quad_length - 1) * 4 points will be appended, and `output` must +// have sufficient memory allocated before this call. +void MirrorIntoTriangleStrip(const Point* quad, + size_t quad_length, + const Point& center, + Point* output) { + // The length of 1/4 arc including the starting point but excluding the + // ending point. + const size_t arc_length = quad_length - 1; + auto GetPoint = [quad, arc_length](size_t i) -> Point { + if (i < arc_length) { + return quad[i]; } - i = i - quad_length_; - if (i < quad_length_) { - return quad_[quad_length_ - i] * reflection[1]; + i = i - arc_length; + if (i < arc_length) { + return quad[arc_length - i] * kReflection[1]; } - i = i - quad_length_; - if (i < quad_length_) { - return quad_[i] * reflection[2]; + i = i - arc_length; + if (i < arc_length) { + return quad[i] * kReflection[2]; } - i = i - quad_length_; - if (i < quad_length_) { - return quad_[quad_length_ - i] * reflection[3]; + i = i - arc_length; + if (i < arc_length) { + return quad[arc_length - i] * kReflection[3]; } else { // Unreachable return Point(); } - } + }; - static constexpr Point reflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; -}; + size_t index_count = 0; + + output[index_count++] = GetPoint(0) + center; + + size_t a = 1; + size_t b = arc_length * 4 - 1; + while (a < b) { + output[index_count++] = GetPoint(a) + center; + output[index_count++] = GetPoint(b) + center; + a++; + b--; + } + if (a == b) { + output[index_count++] = GetPoint(b) + center; + } +} } // namespace @@ -295,26 +303,23 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( DrawOctantSquareLikeSquircle(points, size.height, corner_radius_, Point{c, 0}, true); - // Mirror the arc into 4 quadrants while rearranging the points. - // The `contour_point_count` include all points on the border. The "-1" comes // from duplicate ends from the mirrored arcs. - size_t contour_point_count = 4 * (points.size() - 1); + size_t contour_length = 4 * (points.size() - 1); BufferView vertex_buffer = renderer.GetTransientsBuffer().Emplace( - nullptr, sizeof(Point) * contour_point_count, alignof(Point)); + nullptr, sizeof(Point) * contour_length, alignof(Point)); Point* vertex_data = reinterpret_cast(vertex_buffer.GetBuffer()->OnGetContents() + vertex_buffer.GetRange().offset); - TriangleStripArranger arranger(points.data(), points.size() - 1); - arranger.Output(vertex_data, center); + MirrorIntoTriangleStrip(points.data(), points.size(), center, vertex_data); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, .vertex_buffer = { .vertex_buffer = vertex_buffer, - .vertex_count = contour_point_count, + .vertex_count = contour_length, .index_type = IndexType::kNone, }, .transform = entity.GetShaderTransform(pass), From 4f8fd4d5908c54d7e8188c8b9bf9378bf7586cb9 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sun, 1 Dec 2024 17:14:03 -0800 Subject: [PATCH 19/29] CoversArea --- .../geometry/round_superellipse_geometry.cc | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 8584d20f199f2..93353ca5ba7de 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -333,7 +333,20 @@ std::optional RoundSuperellipseGeometry::GetCoverage( bool RoundSuperellipseGeometry::CoversArea(const Matrix& transform, const Rect& rect) const { - return false; + if (!transform.IsTranslationScaleOnly()) { + return false; + } + // Use the rectangle formed by the four 45deg points (point M) as a + // conservative estimate of the inner rectangle. The distance from M to either + // closer edge of the bounding box is `gap`. + Scalar g = gap(corner_radius_); + Rect coverage = Rect::MakeLTRB( + bounds_.GetLeft() + g, + bounds_.GetTop() + g, + bounds_.GetRight() - g, + bounds_.GetBottom() - g + ).TransformBounds(transform); + return coverage.Contains(rect); } bool RoundSuperellipseGeometry::IsAxisAlignedRect() const { From 4af05e0a4fb5f0627a7e3f0c63423a7519484e31 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sun, 1 Dec 2024 22:23:36 -0800 Subject: [PATCH 20/29] Use tesselator cache for temp storage --- .../geometry/round_superellipse_geometry.cc | 142 +++++++++++------- 1 file changed, 86 insertions(+), 56 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 93353ca5ba7de..35df54da783ab 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -62,10 +62,9 @@ constexpr Scalar gap(Scalar corner_radius) { // // The resulting points are appended to `output` and include the starting point // but exclude the ending point. -void DrawCircularArc(std::vector& output, - Point start, - Point end, - Scalar r) { +// +// Returns the number of the +size_t DrawCircularArc(Point* output, Point start, Point end, Scalar r) { /* Denote the middle point of S and E as M. The key is to find the center of * the circle. * S --__ @@ -87,11 +86,13 @@ void DrawCircularArc(std::vector& output, Scalar angle_sce = asinf(distance_sm / r) * 2; Point c_to_s = start - c; + Point* next = output; Scalar angle = 0; while (angle < angle_sce) { - output.push_back(c_to_s.Rotate(Radians(-angle)) + c); + *(next++) = c_to_s.Rotate(Radians(-angle)) + c; angle += kAngleStep; } + return next - output; } Scalar lerp(size_t column, size_t left, size_t frac) { @@ -100,29 +101,25 @@ Scalar lerp(size_t column, size_t left, size_t frac) { } // Draws an arc representing the top 1/8 segment of a square-like rounded -// superellipse. The arc spans from 0 to pi/4, moving clockwise starting from -// the positive Y-axis. +// superellipse. // -// The full square-like rounded superellipse has a width and height specified by -// `size`. It is centered at `center` and features rounded corners determined by -// `corner_radius`. The `corner_radius` corresponds to the `cornerRadius` -// parameter in SwiftUI, rather than the literal radius of corner circles. +// The resulting arc centers at the origin, spanning from 0 to pi/4, moving +// clockwise starting from the positive Y-axis, and includes the starting point +// (the middle of the top flat side) while excluding the ending point (the x=y +// point). // -// If `flip` is true, the function instead produces the next 1/8 arc, spanning -// from pi/4 to pi/8. Technically, the X and Y coordinates of the arc points -// are swapped before applying `center`, and their order is reversed as well. +// The full square-like rounded superellipse has a width and height specified by +// `size` and features rounded corners determined by `corner_radius`. The +// `corner_radius` corresponds to the `cornerRadius` parameter in SwiftUI, +// rather than the literal radius of corner circles. // -// The resulting arc, which includes the starting point (the middle of the flat -// side) and excludes the ending point (the x=y point), is applied with `flip`, -// and then appended to `output`. -void DrawOctantSquareLikeSquircle(std::vector& output, - Scalar size, - Scalar corner_radius, - Point center, - bool flip) { - /* Ignoring `center` and `flip`, the following figure shows the first quadrant - * of a square-like rounded superellipse. The target arc consists of the - * "stretch" (AB), a superellipsoid arc (BJ), and a circular arc (JM). +// Returns the number of points generated. +size_t DrawOctantSquareLikeSquircle(Point* output, + Scalar size, + Scalar corner_radius) { + /* The following figure shows the first quadrant of a square-like rounded + * superellipse. The target arc consists of the "stretch" (AB), a + * superellipsoid arc (BJ), and a circular arc (JM). * * Define gap (g) as the distance between point M and the bounding box, * therefore point M is at (size/2 - g, size/2 - g). Assume the coordinate of @@ -165,14 +162,11 @@ void DrawOctantSquareLikeSquircle(std::vector& output, Scalar xJ = d + R * sin(theta); Scalar yJ = pow(pow(a, n) - pow(xJ, n), 1 / n); - Point pointM{size / 2 - g, size / 2 - g}; - - // Points without applying `flip` and `center`. - std::vector points; - points.reserve(21); + Point pointM(size / 2 - g, size / 2 - g); + Point* next = output; // A - points.emplace_back(0, size / 2); + *(next++) = Point(0, size / 2); // Superellipsoid arc BJ (B inclusive, J exclusive) // https://math.stackexchange.com/questions/2573746/superellipse-parametric-equation { @@ -183,28 +177,45 @@ void DrawOctantSquareLikeSquircle(std::vector& output, Scalar x = 0; Scalar y = a; do { - points.emplace_back(x + s, y + s); + *(next++) = Point(x + s, y + s); angle += kAngleStep; x = a * pow(abs(sinf(angle)), 2 / n); y = a * pow(abs(cosf(angle)), 2 / n); } while (y > target_slope * x); } // Circular arc JM (B inclusive, M exclusive) - DrawCircularArc(points, {xJ + s, yJ + s}, pointM, R); + next += DrawCircularArc(next, {xJ + s, yJ + s}, pointM, R); + return next - output; +} - // Apply `flip` and `center`. +// Optionally `flip` the input points before offsetting it by `center`, and +// append the result to `output`. +// +// If `flip` is true, then the entire input list is reversed, and the x and y +// coordinate of each point is swapped as well. This effectively mirrors the +// input point list by the y=x line. +size_t FlipAndOffset(Point* output, + const Point* input, + size_t input_length, + bool flip, + const Point& center) { if (!flip) { - for (const Point& point : points) { - output.push_back(point + center); + for (size_t i = 0; i < input_length; i++) { + output[i] = input[i] + center; } } else { - for (size_t i = 0; i < points.size(); i++) { - const Point& point = points[points.size() - i - 1]; - output.emplace_back(point.y + center.x, point.x + center.y); + for (size_t i = 0; i < input_length; i++) { + const Point& point = input[input_length - i - 1]; + output[i] = Point(point.y + center.x, point.x + center.y); } } + return input_length; } +// Return the shortest of `corner_radius`, height/2, and width/2. +// +// Corner radii longer than 1/2 of the side length does not make sense, and will +// be limited to the longest possible. Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { return std::min(corner_radius, std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); @@ -292,27 +303,48 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( // height-aligned one have the same offset in different directions. const Scalar c = (size.width - size.height) / 2; - // Draw the first quadrant of the shape and store in `points`, including both - // ends. It will be mirrored to other quadrants later. - std::vector points; - points.reserve(41); + // The cache is allocated as follows: + // + // * The first chunk stores the first quadrant arc. + // * The second chunk stores an octant arc before flipping and translation. + Point* cache = renderer.GetTessellator().GetStrokePointCache().data(); + + // Draw the first quadrant of the shape and store in `quadrant`, including + // both ends. It will be mirrored to other quadrants later. + Point* quadrant = cache; + size_t quadrant_length; + { + Point* next = quadrant; + constexpr size_t kMaxQuadrantLength = kPointArenaSize / 4; + + Point* octant_cache = cache + kMaxQuadrantLength; + size_t octant_length; + + octant_length = + DrawOctantSquareLikeSquircle(octant_cache, size.width, corner_radius_); + next += FlipAndOffset(next, octant_cache, octant_length, /*flip=*/false, + Point(0, -c)); - DrawOctantSquareLikeSquircle(points, size.width, corner_radius_, Point{0, -c}, - false); - points.push_back(Point(size / 2) - gap(corner_radius_)); // Point M - DrawOctantSquareLikeSquircle(points, size.height, corner_radius_, Point{c, 0}, - true); + *(next++) = Point(size / 2) - gap(corner_radius_); // Point M + + octant_length = + DrawOctantSquareLikeSquircle(octant_cache, size.height, corner_radius_); + next += FlipAndOffset(next, octant_cache, octant_length, /*flip=*/true, + Point(c, 0)); + + quadrant_length = next - quadrant; + } // The `contour_point_count` include all points on the border. The "-1" comes // from duplicate ends from the mirrored arcs. - size_t contour_length = 4 * (points.size() - 1); + size_t contour_length = 4 * (quadrant_length - 1); BufferView vertex_buffer = renderer.GetTransientsBuffer().Emplace( nullptr, sizeof(Point) * contour_length, alignof(Point)); Point* vertex_data = reinterpret_cast(vertex_buffer.GetBuffer()->OnGetContents() + vertex_buffer.GetRange().offset); - MirrorIntoTriangleStrip(points.data(), points.size(), center, vertex_data); + MirrorIntoTriangleStrip(quadrant, quadrant_length, center, vertex_data); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, @@ -340,12 +372,10 @@ bool RoundSuperellipseGeometry::CoversArea(const Matrix& transform, // conservative estimate of the inner rectangle. The distance from M to either // closer edge of the bounding box is `gap`. Scalar g = gap(corner_radius_); - Rect coverage = Rect::MakeLTRB( - bounds_.GetLeft() + g, - bounds_.GetTop() + g, - bounds_.GetRight() - g, - bounds_.GetBottom() - g - ).TransformBounds(transform); + Rect coverage = + Rect::MakeLTRB(bounds_.GetLeft() + g, bounds_.GetTop() + g, + bounds_.GetRight() - g, bounds_.GetBottom() - g) + .TransformBounds(transform); return coverage.Contains(rect); } From 993a08af976a6fbefea65bf677ec54bc327cc17c Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sun, 1 Dec 2024 22:45:57 -0800 Subject: [PATCH 21/29] Adjust test constants and function order --- impeller/entity/entity_unittests.cc | 8 ++--- .../geometry/round_superellipse_geometry.cc | 31 ++++++++++--------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 91ea183ffd149..4f2cd9260a740 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2340,15 +2340,15 @@ TEST_P(EntityTest, DrawRoundSuperEllipse) { static float center_y = 100; static float width = 900; static float height = 900; - static float corner_radius = 391.30; + static float corner_radius = 300; static Color color = Color::Red(); ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::SliderFloat("Center X", ¢er_x, 0, 1000); ImGui::SliderFloat("Center Y", ¢er_y, 0, 1000); - ImGui::SliderFloat("Width", &width, 1, 800); - ImGui::SliderFloat("Height", &height, 1, 800); - ImGui::SliderFloat("Corner radius", &corner_radius, 1, 500); + ImGui::SliderFloat("Width", &width, 0, 1000); + ImGui::SliderFloat("Height", &height, 0, 1000); + ImGui::SliderFloat("Corner radius", &corner_radius, 0, 500); ImGui::End(); auto contents = std::make_shared(); diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 35df54da783ab..b8e846c1d38f8 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -49,9 +49,26 @@ constexpr Scalar kMaxRatio = kPrecomputedVariables[kNumRecords - 1][0]; constexpr Scalar kRatioStep = kPrecomputedVariables[1][0] - kPrecomputedVariables[0][0]; +Scalar lerp(size_t column, size_t left, size_t frac) { + return (1 - frac) * kPrecomputedVariables[left][column] + + frac * kPrecomputedVariables[left + 1][column]; +} + +// Return the shortest of `corner_radius`, height/2, and width/2. +// +// Corner radii longer than 1/2 of the side length does not make sense, and will +// be limited to the longest possible. +Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { + return std::min(corner_radius, + std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); +} + constexpr Scalar kAngleStep = kPi / 80; +// The distance from point M (the 45deg point) to either side of the closer +// bounding box is defined as `gap`. constexpr Scalar gap(Scalar corner_radius) { + // Heuristic formula derived from experimentation. return 0.2924303407 * corner_radius; } @@ -95,11 +112,6 @@ size_t DrawCircularArc(Point* output, Point start, Point end, Scalar r) { return next - output; } -Scalar lerp(size_t column, size_t left, size_t frac) { - return (1 - frac) * kPrecomputedVariables[left][column] + - frac * kPrecomputedVariables[left + 1][column]; -} - // Draws an arc representing the top 1/8 segment of a square-like rounded // superellipse. // @@ -212,15 +224,6 @@ size_t FlipAndOffset(Point* output, return input_length; } -// Return the shortest of `corner_radius`, height/2, and width/2. -// -// Corner radii longer than 1/2 of the side length does not make sense, and will -// be limited to the longest possible. -Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { - return std::min(corner_radius, - std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); -} - constexpr Point kReflection[4] = {{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}; // Mirror the point list `quad` into other quadrants and output as a triangle From 147cf82e4b03a34d7ee1087b6b4d8a87805c4fb7 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 2 Dec 2024 17:19:50 -0800 Subject: [PATCH 22/29] Use thetaJ --- .../geometry/round_superellipse_geometry.cc | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index b8e846c1d38f8..74363b6bf0eff 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -20,26 +20,26 @@ namespace { // * ratio = size / a // * n // * d / a -// * theta +// * thetaJ // // For definition of the variables, see DrawOctantSquareLikeSquircle. constexpr Scalar kPrecomputedVariables[][4] = { - {2.000, 2.00000, 0.00000, 0.26000}, // - {2.020, 2.03300, 0.01441, 0.23845}, // - {2.040, 2.06500, 0.02568, 0.20310}, // - {2.060, 2.09800, 0.03655, 0.18593}, // - {2.080, 2.13200, 0.04701, 0.17341}, // - {2.100, 2.17800, 0.05596, 0.14049}, // - {2.120, 2.19300, 0.06805, 0.17417}, // - {2.140, 2.23000, 0.07733, 0.16145}, // - {2.160, 2.26400, 0.08677, 0.15649}, // - {2.180, 2.30500, 0.09529, 0.14374}, // - {2.200, 2.32900, 0.10530, 0.15212}, // - {2.220, 2.38300, 0.11230, 0.12974}, // - {2.240, 2.39800, 0.12257, 0.14433}, // - {2.260, 2.41800, 0.13236, 0.15439}, // - {2.280, 2.47200, 0.13867, 0.13431}, // - {2.300, 2.50900, 0.14649, 0.13021} // + {2.000, 2.00000, 0.00000, 0.24040}, // + {2.020, 2.03340, 0.01447, 0.24040}, // + {2.040, 2.06540, 0.02575, 0.21167}, // + {2.060, 2.09800, 0.03668, 0.20118}, // + {2.080, 2.13160, 0.04719, 0.19367}, // + {2.100, 2.17840, 0.05603, 0.16233}, // + {2.120, 2.19310, 0.06816, 0.20020}, // + {2.140, 2.22990, 0.07746, 0.19131}, // + {2.160, 2.26360, 0.08693, 0.19008}, // + {2.180, 2.30540, 0.09536, 0.17935}, // + {2.200, 2.32900, 0.10541, 0.19136}, // + {2.220, 2.38330, 0.11237, 0.17130}, // + {2.240, 2.39770, 0.12271, 0.18956}, // + {2.260, 2.41770, 0.13251, 0.20254}, // + {2.280, 2.47180, 0.13879, 0.18454}, // + {2.300, 2.50910, 0.14658, 0.18261} // }; constexpr size_t kNumRecords = @@ -69,7 +69,7 @@ constexpr Scalar kAngleStep = kPi / 80; // bounding box is defined as `gap`. constexpr Scalar gap(Scalar corner_radius) { // Heuristic formula derived from experimentation. - return 0.2924303407 * corner_radius; + return 0.2924066406 * corner_radius; } // Draw a circular arc from `start` to `end` with a radius of `r`. @@ -91,7 +91,7 @@ size_t DrawCircularArc(Point* output, Point start, Point end, Scalar r) { * / ⟋ ↗ * / ⟋ * / ⟋ r - * C ⟋ ↙ + * C ᜱ ↙ */ Point s_to_e = end - start; @@ -133,26 +133,32 @@ size_t DrawOctantSquareLikeSquircle(Point* output, * superellipse. The target arc consists of the "stretch" (AB), a * superellipsoid arc (BJ), and a circular arc (JM). * - * Define gap (g) as the distance between point M and the bounding box, - * therefore point M is at (size/2 - g, size/2 - g). Assume the coordinate of - * J is (xJ, yJ). - * * straight superelipse * ↓ ↓ - * A B J circular arc + * A B J circular arc * ---------...._ ↙ * | | / `⟍ M * | | / ⟋ ⟍ - * | | /θ ⟋ \ - * | | /◝⟋ | - * | | ᜱ | - * | | / D | + * | | / ⟋ \ + * | | / ⟋ | + * | | ᜱD | + * | | / | * ↑ +----+ | * s | | | * ↓ +----+---------------| A' * O S * ← s → * ←------ size/2 ------→ + * + * Define gap (g) as the distance between point M and the bounding box, + * therefore point M is at (size/2 - g, size/2 - g). + * + * The superellipsoid curve can be drawn with an implicit parameter θ: + * x = a * sinθ ^ (2/n) + * y = a * cosθ ^ (2/n) + * https://math.stackexchange.com/questions/2573746/superellipse-parametric-equation + * + * Define thetaJ as the θ at point J. */ Scalar ratio = {std::min(size / corner_radius, kMaxRatio)}; @@ -168,11 +174,9 @@ size_t DrawOctantSquareLikeSquircle(Point* output, Scalar frac = steps - left; Scalar n = lerp(1, left, frac); Scalar d = lerp(2, left, frac) * a; - Scalar theta = lerp(3, left, frac); + Scalar thetaJ = lerp(3, left, frac); Scalar R = (a - d - g) * sqrt(2); - Scalar xJ = d + R * sin(theta); - Scalar yJ = pow(pow(a, n) - pow(xJ, n), 1 / n); Point pointM(size / 2 - g, size / 2 - g); @@ -180,21 +184,18 @@ size_t DrawOctantSquareLikeSquircle(Point* output, // A *(next++) = Point(0, size / 2); // Superellipsoid arc BJ (B inclusive, J exclusive) - // https://math.stackexchange.com/questions/2573746/superellipse-parametric-equation { - const Scalar target_slope = yJ / xJ; Scalar angle = 0; - // The first point, B, should always be added, which happens to work well - // with do-while. - Scalar x = 0; - Scalar y = a; - do { + while (angle < thetaJ) { + Scalar x = a * pow(abs(sinf(angle)), 2 / n); + Scalar y = a * pow(abs(cosf(angle)), 2 / n); *(next++) = Point(x + s, y + s); angle += kAngleStep; - x = a * pow(abs(sinf(angle)), 2 / n); - y = a * pow(abs(cosf(angle)), 2 / n); - } while (y > target_slope * x); + } } + + Scalar xJ = a * pow(abs(sinf(thetaJ)), 2 / n); + Scalar yJ = a * pow(abs(cosf(thetaJ)), 2 / n); // Circular arc JM (B inclusive, M exclusive) next += DrawCircularArc(next, {xJ + s, yJ + s}, pointM, R); return next - output; @@ -372,8 +373,7 @@ bool RoundSuperellipseGeometry::CoversArea(const Matrix& transform, return false; } // Use the rectangle formed by the four 45deg points (point M) as a - // conservative estimate of the inner rectangle. The distance from M to either - // closer edge of the bounding box is `gap`. + // conservative estimate of the inner rectangle. Scalar g = gap(corner_radius_); Rect coverage = Rect::MakeLTRB(bounds_.GetLeft() + g, bounds_.GetTop() + g, From 69982334f5bbeef7217b446271baa0b3ed14c70f Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 2 Dec 2024 18:59:50 -0800 Subject: [PATCH 23/29] Dynamic angular step --- .../geometry/round_superellipse_geometry.cc | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 74363b6bf0eff..88779616c4053 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -63,7 +63,32 @@ Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); } -constexpr Scalar kAngleStep = kPi / 80; +// The max angular step that the algorithm will traverse a quadrant of the curve. +// +// This limits the max number of points of the curve. +constexpr Scalar kMaxQuadrantSteps = 40; + +// Calculates the angular step size for a smooth curve. +// +// Returns the angular step needed to ensure a curve appears smooth +// based on the smallest dimension of a shape. Smaller dimensions require +// larger steps as less detail is needed for smoothness. +// +// The `minDimension` is the smallest dimension (e.g., width or height) of the +// shape. +// +// The `fullAngle` is the total angular range to traverse. +Scalar CalculateStep(Scalar minDimension, Scalar fullAngle) { + constexpr Scalar kMinAngleStep = kPiOver2 / kMaxQuadrantSteps; + + // Assumes at least 1 point is needed per pixel to achieve sufficient + // smoothness. + constexpr Scalar pointsPerPixel = 1.0; + size_t pointsByDimension = (size_t)std::ceil(minDimension * pointsPerPixel); + Scalar angleByDimension = fullAngle / pointsByDimension; + + return std::min(kMinAngleStep, angleByDimension); +} // The distance from point M (the 45deg point) to either side of the closer // bounding box is defined as `gap`. @@ -103,11 +128,13 @@ size_t DrawCircularArc(Point* output, Point start, Point end, Scalar r) { Scalar angle_sce = asinf(distance_sm / r) * 2; Point c_to_s = start - c; + Scalar step = CalculateStep(std::abs(s_to_e.y), angle_sce); + Point* next = output; Scalar angle = 0; while (angle < angle_sce) { *(next++) = c_to_s.Rotate(Radians(-angle)) + c; - angle += kAngleStep; + angle += step; } return next - output; } @@ -180,25 +207,29 @@ size_t DrawOctantSquareLikeSquircle(Point* output, Point pointM(size / 2 - g, size / 2 - g); + Scalar xJ = a * pow(abs(sinf(thetaJ)), 2 / n); + Scalar yJ = a * pow(abs(cosf(thetaJ)), 2 / n); + Point* next = output; // A *(next++) = Point(0, size / 2); // Superellipsoid arc BJ (B inclusive, J exclusive) { + Scalar step = CalculateStep(a - yJ, thetaJ); Scalar angle = 0; while (angle < thetaJ) { Scalar x = a * pow(abs(sinf(angle)), 2 / n); Scalar y = a * pow(abs(cosf(angle)), 2 / n); *(next++) = Point(x + s, y + s); - angle += kAngleStep; + angle += step; } } - Scalar xJ = a * pow(abs(sinf(thetaJ)), 2 / n); - Scalar yJ = a * pow(abs(cosf(thetaJ)), 2 / n); // Circular arc JM (B inclusive, M exclusive) next += DrawCircularArc(next, {xJ + s, yJ + s}, pointM, R); - return next - output; + size_t number = next - output; + printf("Total number of points: %zu\n", number); + return number; } // Optionally `flip` the input points before offsetting it by `center`, and From 9e05fca1e226f0f98800c5821de34ca0e28c1cf0 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 2 Dec 2024 19:06:59 -0800 Subject: [PATCH 24/29] Add static assert --- impeller/display_list/canvas.cc | 4 +--- .../geometry/round_superellipse_geometry.cc | 17 +++++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/impeller/display_list/canvas.cc b/impeller/display_list/canvas.cc index 27d429a43979b..6c87e1ac54617 100644 --- a/impeller/display_list/canvas.cc +++ b/impeller/display_list/canvas.cc @@ -471,9 +471,7 @@ void Canvas::DrawRect(const Rect& rect, const Paint& paint) { entity.SetTransform(GetCurrentTransform()); entity.SetBlendMode(paint.blend_mode); - Scalar radius = std::min(rect.GetWidth(), rect.GetHeight()) / 3; - RoundSuperellipseGeometry geom(rect, radius); - // RectGeometry geom(rect); + RectGeometry geom(rect); AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint); } diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 88779616c4053..c2e055ddb2b7e 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -63,7 +63,8 @@ Scalar LimitRadius(Scalar corner_radius, const Rect& bounds) { std::min(bounds.GetWidth() / 2, bounds.GetHeight() / 2)); } -// The max angular step that the algorithm will traverse a quadrant of the curve. +// The max angular step that the algorithm will traverse a quadrant of the +// curve. // // This limits the max number of points of the curve. constexpr Scalar kMaxQuadrantSteps = 40; @@ -227,9 +228,7 @@ size_t DrawOctantSquareLikeSquircle(Point* output, // Circular arc JM (B inclusive, M exclusive) next += DrawCircularArc(next, {xJ + s, yJ + s}, pointM, R); - size_t number = next - output; - printf("Total number of points: %zu\n", number); - return number; + return next - output; } // Optionally `flip` the input points before offsetting it by `center`, and @@ -340,17 +339,23 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( // The cache is allocated as follows: // - // * The first chunk stores the first quadrant arc. + // * The first chunk stores the quadrant arc. // * The second chunk stores an octant arc before flipping and translation. Point* cache = renderer.GetTessellator().GetStrokePointCache().data(); + // The memory size (in units of Points) allocated to store the first chunk. + constexpr size_t kMaxQuadrantLength = kPointArenaSize / 4; + // Since the curve is traversed in steps bounded by kMaxQuadrantSteps, the + // curving part will have fewer points than kMaxQuadrantSteps. Multiply it by + // 2 for storing other sporatic points (an extremely conservative estimate). + static_assert(kMaxQuadrantLength > 2 * kMaxQuadrantSteps); + // Draw the first quadrant of the shape and store in `quadrant`, including // both ends. It will be mirrored to other quadrants later. Point* quadrant = cache; size_t quadrant_length; { Point* next = quadrant; - constexpr size_t kMaxQuadrantLength = kPointArenaSize / 4; Point* octant_cache = cache + kMaxQuadrantLength; size_t octant_length; From 8ba33818155973765e53546f5d33ec1b19548bae Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 2 Dec 2024 19:12:50 -0800 Subject: [PATCH 25/29] Remove unused imports --- impeller/entity/geometry/round_superellipse_geometry.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index c2e055ddb2b7e..4a9e78bf431ad 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -3,8 +3,6 @@ // found in the LICENSE file. #include -#include -#include #include "flutter/impeller/entity/geometry/round_superellipse_geometry.h" From f391cf54defaccaf958eb6dd7cdf23446e1499b4 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 2 Dec 2024 19:23:00 -0800 Subject: [PATCH 26/29] Simplify lerp --- .../geometry/round_superellipse_geometry.cc | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 4a9e78bf431ad..d208b7290ae5f 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -47,7 +47,19 @@ constexpr Scalar kMaxRatio = kPrecomputedVariables[kNumRecords - 1][0]; constexpr Scalar kRatioStep = kPrecomputedVariables[1][0] - kPrecomputedVariables[0][0]; -Scalar lerp(size_t column, size_t left, size_t frac) { +// Linear interpolation for `kPrecomputedVariables`. +// +// The `column` is a 0-based index that decides the target variable, where 1 +// corresponds to the 2nd element of each row, etc. +// +// The `ratio` corresponds to column 0, on which the lerp is calculated. +Scalar lerpPrecomputedVariable(size_t column, Scalar ratio) { + Scalar steps = + std::clamp((ratio - kMinRatio) / kRatioStep, 0, kNumRecords - 1); + size_t left = + std::clamp((size_t)std::floor(steps), 0, kNumRecords - 2); + Scalar frac = steps - left; + return (1 - frac) * kPrecomputedVariables[left][column] + frac * kPrecomputedVariables[left + 1][column]; } @@ -192,15 +204,9 @@ size_t DrawOctantSquareLikeSquircle(Point* output, Scalar s = size / 2 - a; Scalar g = gap(corner_radius); - // Use look up table to derive critical variables - Scalar steps = - std::clamp((ratio - kMinRatio) / kRatioStep, 0, kNumRecords - 1); - size_t left = - std::clamp((size_t)std::floor(steps), 0, kNumRecords - 2); - Scalar frac = steps - left; - Scalar n = lerp(1, left, frac); - Scalar d = lerp(2, left, frac) * a; - Scalar thetaJ = lerp(3, left, frac); + Scalar n = lerpPrecomputedVariable(1, ratio); + Scalar d = lerpPrecomputedVariable(2, ratio) * a; + Scalar thetaJ = lerpPrecomputedVariable(3, ratio); Scalar R = (a - d - g) * sqrt(2); From ee8fb989ce7158339427db4f57f84a3ec822f960 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 2 Dec 2024 19:25:11 -0800 Subject: [PATCH 27/29] Better func names --- .../geometry/round_superellipse_geometry.cc | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index d208b7290ae5f..b264092380554 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -53,7 +53,7 @@ constexpr Scalar kRatioStep = // corresponds to the 2nd element of each row, etc. // // The `ratio` corresponds to column 0, on which the lerp is calculated. -Scalar lerpPrecomputedVariable(size_t column, Scalar ratio) { +Scalar LerpPrecomputedVariable(size_t column, Scalar ratio) { Scalar steps = std::clamp((ratio - kMinRatio) / kRatioStep, 0, kNumRecords - 1); size_t left = @@ -102,8 +102,8 @@ Scalar CalculateStep(Scalar minDimension, Scalar fullAngle) { } // The distance from point M (the 45deg point) to either side of the closer -// bounding box is defined as `gap`. -constexpr Scalar gap(Scalar corner_radius) { +// bounding box is defined as `CalculateGap`. +constexpr Scalar CalculateGap(Scalar corner_radius) { // Heuristic formula derived from experimentation. return 0.2924066406 * corner_radius; } @@ -188,7 +188,7 @@ size_t DrawOctantSquareLikeSquircle(Point* output, * ← s → * ←------ size/2 ------→ * - * Define gap (g) as the distance between point M and the bounding box, + * Define CalculateGap (g) as the distance between point M and the bounding box, * therefore point M is at (size/2 - g, size/2 - g). * * The superellipsoid curve can be drawn with an implicit parameter θ: @@ -202,11 +202,11 @@ size_t DrawOctantSquareLikeSquircle(Point* output, Scalar ratio = {std::min(size / corner_radius, kMaxRatio)}; Scalar a = ratio * corner_radius / 2; Scalar s = size / 2 - a; - Scalar g = gap(corner_radius); + Scalar g = CalculateGap(corner_radius); - Scalar n = lerpPrecomputedVariable(1, ratio); - Scalar d = lerpPrecomputedVariable(2, ratio) * a; - Scalar thetaJ = lerpPrecomputedVariable(3, ratio); + Scalar n = LerpPrecomputedVariable(1, ratio); + Scalar d = LerpPrecomputedVariable(2, ratio) * a; + Scalar thetaJ = LerpPrecomputedVariable(3, ratio); Scalar R = (a - d - g) * sqrt(2); @@ -369,7 +369,7 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer( next += FlipAndOffset(next, octant_cache, octant_length, /*flip=*/false, Point(0, -c)); - *(next++) = Point(size / 2) - gap(corner_radius_); // Point M + *(next++) = Point(size / 2) - CalculateGap(corner_radius_); // Point M octant_length = DrawOctantSquareLikeSquircle(octant_cache, size.height, corner_radius_); @@ -414,7 +414,7 @@ bool RoundSuperellipseGeometry::CoversArea(const Matrix& transform, } // Use the rectangle formed by the four 45deg points (point M) as a // conservative estimate of the inner rectangle. - Scalar g = gap(corner_radius_); + Scalar g = CalculateGap(corner_radius_); Rect coverage = Rect::MakeLTRB(bounds_.GetLeft() + g, bounds_.GetTop() + g, bounds_.GetRight() - g, bounds_.GetBottom() - g) From 6662e20f5a24afb15290f1240a5f5f3cac8828ab Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 2 Dec 2024 19:29:24 -0800 Subject: [PATCH 28/29] Format --- impeller/entity/geometry/round_superellipse_geometry.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index b264092380554..35b42dedf8773 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -188,8 +188,8 @@ size_t DrawOctantSquareLikeSquircle(Point* output, * ← s → * ←------ size/2 ------→ * - * Define CalculateGap (g) as the distance between point M and the bounding box, - * therefore point M is at (size/2 - g, size/2 - g). + * Define CalculateGap (g) as the distance between point M and the bounding + * box, therefore point M is at (size/2 - g, size/2 - g). * * The superellipsoid curve can be drawn with an implicit parameter θ: * x = a * sinθ ^ (2/n) From 4db5d21985326c0ef0dd441bb3c957ba522c0897 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 2 Dec 2024 19:29:47 -0800 Subject: [PATCH 29/29] Format --- impeller/entity/geometry/round_superellipse_geometry.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/impeller/entity/geometry/round_superellipse_geometry.cc b/impeller/entity/geometry/round_superellipse_geometry.cc index 35b42dedf8773..b595b169f1738 100644 --- a/impeller/entity/geometry/round_superellipse_geometry.cc +++ b/impeller/entity/geometry/round_superellipse_geometry.cc @@ -188,8 +188,8 @@ size_t DrawOctantSquareLikeSquircle(Point* output, * ← s → * ←------ size/2 ------→ * - * Define CalculateGap (g) as the distance between point M and the bounding - * box, therefore point M is at (size/2 - g, size/2 - g). + * Define gap (g) as the distance between point M and the bounding box, + * therefore point M is at (size/2 - g, size/2 - g). * * The superellipsoid curve can be drawn with an implicit parameter θ: * x = a * sinθ ^ (2/n)