[Impeller] Add rounded superellipse (flutter/engine#56726)
Support rounded superellipse. Part of https://github.com/flutter/flutter/issues/139321 and https://github.com/flutter/flutter/issues/13914, also related to https://github.com/flutter/flutter/issues/91523. ### Open questions * Alternative names: * Round**ed**Superellipse * Squircle * ContinuousBorderRectangle (or something like this...) * I chose rounded superellipse because this name, albeit its length, precisely describe this shape. "Squircle" is not strictly defined but generally refers any shape intermediate between a rectangle and a circle. * Alternative definition for `corner_radius`: * Currently the `corner_radius` corresponds to SwiftUI parameters. To make the shape definition more generalized, we can instead define the `corner_radius` to be the radius of the corner circles, and make the framework do a look up table. * The down side is, not only the work to re-calculate the table, but also that it doesn't completely eliminates the relationship with SwiftUI, since currently the degree of the superellipse (`n`) is also mapped from the SwiftUI `cornerRadius`, which is not necessary for the shape per se. * To some extent it boils down to the question of whether we'd like this shape to support anything beyond SwiftUI. ### Demo https://github.com/user-attachments/assets/806ac0e9-d62f-4b04-ab6a-83436a11f6f3 Low ratio: (900, 900, 445) <img width="520" alt="image" src="https://github.com/user-attachments/assets/54087467-85cd-4021-91cc-a948866ab5a8"> Mid ratio: (900, 650, 180) <img width="508" alt="image" src="https://github.com/user-attachments/assets/460a4927-0396-462b-948d-0846a781c92c"> High ratio: (900, 650, 17) <img width="490" alt="image" src="https://github.com/user-attachments/assets/8d7f625d-8a3b-4aba-b3f9-f292b874b606"> ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I signed the [CLA]. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
This commit is contained in:
parent
4931dd81fb
commit
f5325bf597
@ -43161,6 +43161,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
|
||||
@ -46088,6 +46090,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
|
||||
|
@ -37,6 +37,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"
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
@ -2321,6 +2322,41 @@ 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 = 900;
|
||||
static float height = 900;
|
||||
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, 0, 1000);
|
||||
ImGui::SliderFloat("Height", &height, 0, 1000);
|
||||
ImGui::SliderFloat("Corner radius", &corner_radius, 0, 500);
|
||||
ImGui::End();
|
||||
|
||||
auto contents = std::make_shared<SolidColorContents>();
|
||||
std::unique_ptr<RoundSuperellipseGeometry> geom =
|
||||
std::make_unique<RoundSuperellipseGeometry>(
|
||||
Rect::MakeOriginSize({center_x, center_y}, {width, height}),
|
||||
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));
|
||||
|
@ -0,0 +1,429 @@
|
||||
// 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 <cmath>
|
||||
|
||||
#include "flutter/impeller/entity/geometry/round_superellipse_geometry.h"
|
||||
|
||||
#include "impeller/geometry/constants.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
namespace {
|
||||
// A look up table with precomputed variables.
|
||||
//
|
||||
// The columns represent the following variabls respectively:
|
||||
//
|
||||
// * ratio = size / a
|
||||
// * n
|
||||
// * d / a
|
||||
// * thetaJ
|
||||
//
|
||||
// For definition of the variables, see DrawOctantSquareLikeSquircle.
|
||||
constexpr Scalar kPrecomputedVariables[][4] = {
|
||||
{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 =
|
||||
sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]);
|
||||
constexpr Scalar kMinRatio = kPrecomputedVariables[0][0];
|
||||
constexpr Scalar kMaxRatio = kPrecomputedVariables[kNumRecords - 1][0];
|
||||
constexpr Scalar kRatioStep =
|
||||
kPrecomputedVariables[1][0] - kPrecomputedVariables[0][0];
|
||||
|
||||
// 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<Scalar>((ratio - kMinRatio) / kRatioStep, 0, kNumRecords - 1);
|
||||
size_t left =
|
||||
std::clamp<size_t>((size_t)std::floor(steps), 0, kNumRecords - 2);
|
||||
Scalar frac = steps - left;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// 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 `CalculateGap`.
|
||||
constexpr Scalar CalculateGap(Scalar corner_radius) {
|
||||
// Heuristic formula derived from experimentation.
|
||||
return 0.2924066406 * corner_radius;
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// The resulting points are appended to `output` and include the starting point
|
||||
// but exclude the ending point.
|
||||
//
|
||||
// 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 --__
|
||||
* / ⟍ `、
|
||||
* / M ⟍\
|
||||
* / ⟋ E
|
||||
* / ⟋ ↗
|
||||
* / ⟋
|
||||
* / ⟋ r
|
||||
* 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 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 += step;
|
||||
}
|
||||
return next - output;
|
||||
}
|
||||
|
||||
// Draws an arc representing the top 1/8 segment of a square-like rounded
|
||||
// superellipse.
|
||||
//
|
||||
// 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).
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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).
|
||||
*
|
||||
* straight superelipse
|
||||
* ↓ ↓
|
||||
* A B J circular arc
|
||||
* ---------...._ ↙
|
||||
* | | / `⟍ M
|
||||
* | | / ⟋ ⟍
|
||||
* | | / ⟋ \
|
||||
* | | / ⟋ |
|
||||
* | | ᜱ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)};
|
||||
Scalar a = ratio * corner_radius / 2;
|
||||
Scalar s = size / 2 - a;
|
||||
Scalar g = CalculateGap(corner_radius);
|
||||
|
||||
Scalar n = LerpPrecomputedVariable(1, ratio);
|
||||
Scalar d = LerpPrecomputedVariable(2, ratio) * a;
|
||||
Scalar thetaJ = LerpPrecomputedVariable(3, ratio);
|
||||
|
||||
Scalar R = (a - d - g) * sqrt(2);
|
||||
|
||||
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 += step;
|
||||
}
|
||||
}
|
||||
|
||||
// Circular arc JM (B inclusive, M exclusive)
|
||||
next += DrawCircularArc(next, {xJ + s, yJ + s}, pointM, R);
|
||||
return next - output;
|
||||
}
|
||||
|
||||
// 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 (size_t i = 0; i < input_length; i++) {
|
||||
output[i] = input[i] + center;
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
// 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 - arc_length;
|
||||
if (i < arc_length) {
|
||||
return quad[arc_length - i] * kReflection[1];
|
||||
}
|
||||
i = i - arc_length;
|
||||
if (i < arc_length) {
|
||||
return quad[i] * kReflection[2];
|
||||
}
|
||||
i = i - arc_length;
|
||||
if (i < arc_length) {
|
||||
return quad[arc_length - i] * kReflection[3];
|
||||
} else {
|
||||
// Unreachable
|
||||
return Point();
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
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 {
|
||||
const Size size = bounds_.GetSize();
|
||||
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 (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
|
||||
// height-aligned one have the same offset in different directions.
|
||||
const Scalar c = (size.width - size.height) / 2;
|
||||
|
||||
// The cache is allocated as follows:
|
||||
//
|
||||
// * 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;
|
||||
|
||||
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));
|
||||
|
||||
*(next++) = Point(size / 2) - CalculateGap(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 * (quadrant_length - 1);
|
||||
BufferView vertex_buffer = renderer.GetTransientsBuffer().Emplace(
|
||||
nullptr, sizeof(Point) * contour_length, alignof(Point));
|
||||
Point* vertex_data =
|
||||
reinterpret_cast<Point*>(vertex_buffer.GetBuffer()->OnGetContents() +
|
||||
vertex_buffer.GetRange().offset);
|
||||
|
||||
MirrorIntoTriangleStrip(quadrant, quadrant_length, center, vertex_data);
|
||||
|
||||
return GeometryResult{
|
||||
.type = PrimitiveType::kTriangleStrip,
|
||||
.vertex_buffer =
|
||||
{
|
||||
.vertex_buffer = vertex_buffer,
|
||||
.vertex_count = contour_length,
|
||||
.index_type = IndexType::kNone,
|
||||
},
|
||||
.transform = entity.GetShaderTransform(pass),
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<Rect> RoundSuperellipseGeometry::GetCoverage(
|
||||
const Matrix& transform) const {
|
||||
return bounds_.TransformBounds(transform);
|
||||
}
|
||||
|
||||
bool RoundSuperellipseGeometry::CoversArea(const Matrix& transform,
|
||||
const Rect& rect) const {
|
||||
if (!transform.IsTranslationScaleOnly()) {
|
||||
return false;
|
||||
}
|
||||
// Use the rectangle formed by the four 45deg points (point M) as a
|
||||
// conservative estimate of the inner rectangle.
|
||||
Scalar g = CalculateGap(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 {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
@ -0,0 +1,58 @@
|
||||
// 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
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// 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);
|
||||
|
||||
~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<Rect> GetCoverage(const Matrix& transform) const override;
|
||||
|
||||
const Rect bounds_;
|
||||
double corner_radius_;
|
||||
|
||||
RoundSuperellipseGeometry(const RoundSuperellipseGeometry&) = delete;
|
||||
|
||||
RoundSuperellipseGeometry& operator=(const RoundSuperellipseGeometry&) =
|
||||
delete;
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
#endif // FLUTTER_IMPELLER_ENTITY_GEOMETRY_ROUND_SUPERELLIPSE_GEOMETRY_H_
|
Loading…
x
Reference in New Issue
Block a user