[Impeller] Add RoundSuperellipse class, containment check and stroking (#162826)
This PR: * Adds a `RoundSuperellipse` class, which mirrors the current `RoundRect` class. * Implements `RoundSuperellipse::Contains`, which checks if a point is contained by the RSE. * Adds `Path::AddSuperellipse`, which draws an RSE with Bezier approximation. * Adds a `RoundSuperellipseParam` class, which is the common computation shared by geometry drawing, stroking, and containment check. https://github.com/user-attachments/assets/883c7762-7b35-432e-9b31-d204db3bd6e1 This PR also updates the RSE algorithm according to my recent research, which uses one fewer precomputed variable (no more `d`), shares the same gap factor with RRect, and allows much better precision. The result shape is almost unchanged (~0.2% slimmer). > For reviewers: The `RoundSuperellipseParam` and `RoundSuperellipse::Contains` parts are repurposed from the abandoned https://github.com/flutter/flutter/pull/162349. This PR is a preparation for https://github.com/flutter/flutter/pull/160883. ## 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], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] 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/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
09fdf159b9
commit
ef927e85d5
@ -175,6 +175,8 @@
|
||||
../../../flutter/impeller/geometry/path_unittests.cc
|
||||
../../../flutter/impeller/geometry/rect_unittests.cc
|
||||
../../../flutter/impeller/geometry/round_rect_unittests.cc
|
||||
../../../flutter/impeller/geometry/round_superellipse_unittests.cc
|
||||
../../../flutter/impeller/geometry/rounding_radii_unittests.cc
|
||||
../../../flutter/impeller/geometry/rstransform_unittests.cc
|
||||
../../../flutter/impeller/geometry/saturated_math_unittests.cc
|
||||
../../../flutter/impeller/geometry/size_unittests.cc
|
||||
|
@ -41930,6 +41930,10 @@ ORIGIN: ../../../flutter/impeller/geometry/rect.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/geometry/rect.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/geometry/round_rect.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/geometry/round_rect.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/geometry/round_superellipse.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/geometry/round_superellipse.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/geometry/round_superellipse_param.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/geometry/round_superellipse_param.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/geometry/rounding_radii.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/geometry/rounding_radii.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/geometry/rstransform.cc + ../../../flutter/LICENSE
|
||||
@ -44897,6 +44901,10 @@ FILE: ../../../flutter/impeller/geometry/rect.cc
|
||||
FILE: ../../../flutter/impeller/geometry/rect.h
|
||||
FILE: ../../../flutter/impeller/geometry/round_rect.cc
|
||||
FILE: ../../../flutter/impeller/geometry/round_rect.h
|
||||
FILE: ../../../flutter/impeller/geometry/round_superellipse.cc
|
||||
FILE: ../../../flutter/impeller/geometry/round_superellipse.h
|
||||
FILE: ../../../flutter/impeller/geometry/round_superellipse_param.cc
|
||||
FILE: ../../../flutter/impeller/geometry/round_superellipse_param.h
|
||||
FILE: ../../../flutter/impeller/geometry/rounding_radii.cc
|
||||
FILE: ../../../flutter/impeller/geometry/rounding_radii.h
|
||||
FILE: ../../../flutter/impeller/geometry/rstransform.cc
|
||||
|
@ -69,6 +69,10 @@ namespace testing {
|
||||
using EntityTest = EntityPlayground;
|
||||
INSTANTIATE_PLAYGROUND_SUITE(EntityTest);
|
||||
|
||||
Rect RectMakeCenterSize(Point center, Size size) {
|
||||
return Rect::MakeSize(size).Shift(center - size / 2);
|
||||
}
|
||||
|
||||
TEST_P(EntityTest, CanCreateEntity) {
|
||||
Entity entity;
|
||||
ASSERT_TRUE(entity.GetTransform().IsIdentity());
|
||||
@ -2325,12 +2329,15 @@ TEST_P(EntityTest, DrawSuperEllipse) {
|
||||
TEST_P(EntityTest, DrawRoundSuperEllipse) {
|
||||
auto callback = [&](ContentContext& context, RenderPass& pass) -> bool {
|
||||
// UI state.
|
||||
static int style_index = 0;
|
||||
static float center[2] = {830, 830};
|
||||
static float size[2] = {600, 600};
|
||||
static bool horizontal_symmetry = true;
|
||||
static bool vertical_symmetry = true;
|
||||
static bool corner_symmetry = true;
|
||||
|
||||
const char* style_options[] = {"Fill", "Stroke"};
|
||||
|
||||
// Initially radius_tl[0] will be mirrored to all 8 values since all 3
|
||||
// symmetries are enabled.
|
||||
static std::array<float, 2> radius_tl = {200};
|
||||
@ -2366,6 +2373,8 @@ TEST_P(EntityTest, DrawRoundSuperEllipse) {
|
||||
|
||||
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
|
||||
{
|
||||
ImGui::Combo("Style", &style_index, style_options,
|
||||
sizeof(style_options) / sizeof(char*));
|
||||
ImGui::SliderFloat2("Center", center, 0, 1000);
|
||||
ImGui::SliderFloat2("Size", size, 0, 1000);
|
||||
ImGui::Checkbox("Symmetry: Horizontal", &horizontal_symmetry);
|
||||
@ -2402,11 +2411,25 @@ TEST_P(EntityTest, DrawRoundSuperEllipse) {
|
||||
.bottom_right = {radius_br[0], radius_br[1]},
|
||||
};
|
||||
|
||||
auto rse = RoundSuperellipse::MakeRectRadii(
|
||||
RectMakeCenterSize({center[0], center[1]}, {size[0], size[1]}), radii);
|
||||
|
||||
Path path;
|
||||
std::unique_ptr<Geometry> geom;
|
||||
if (style_index == 0) {
|
||||
geom = std::make_unique<RoundSuperellipseGeometry>(
|
||||
RectMakeCenterSize({center[0], center[1]}, {size[0], size[1]}),
|
||||
radii);
|
||||
} else {
|
||||
path = PathBuilder{}
|
||||
.SetConvexity(Convexity::kConvex)
|
||||
.AddRoundSuperellipse(rse)
|
||||
.SetBounds(rse.GetBounds())
|
||||
.TakePath();
|
||||
geom = Geometry::MakeStrokePath(path, /*stroke_width=*/2);
|
||||
}
|
||||
|
||||
auto contents = std::make_shared<SolidColorContents>();
|
||||
std::unique_ptr<RoundSuperellipseGeometry> geom =
|
||||
std::make_unique<RoundSuperellipseGeometry>(
|
||||
Rect::MakeOriginSize({center[0], center[1]}, {size[0], size[1]}),
|
||||
radii);
|
||||
contents->SetColor(Color::Red());
|
||||
contents->SetGeometry(geom.get());
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <variant>
|
||||
|
||||
#include "flutter/impeller/entity/geometry/round_superellipse_geometry.h"
|
||||
#include "flutter/impeller/geometry/round_superellipse_param.h"
|
||||
|
||||
#include "impeller/geometry/constants.h"
|
||||
|
||||
@ -13,6 +14,8 @@ namespace impeller {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kGapFactor = RoundSuperellipseParam::kGapFactor;
|
||||
|
||||
// An interface for classes that arranges a point list that forms a convex
|
||||
// contour into a triangle strip.
|
||||
class ConvexRearranger {
|
||||
@ -163,59 +166,6 @@ constexpr Matrix kFlip = Matrix(
|
||||
0.0f, 0.0f, 0.0f, 1.0f);
|
||||
// clang-format on
|
||||
|
||||
// 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>(static_cast<size_t>(std::floor(steps)), 0,
|
||||
kNumRecords - 2);
|
||||
Scalar frac = steps - left;
|
||||
|
||||
return (1 - frac) * kPrecomputedVariables[left][column] +
|
||||
frac * kPrecomputedVariables[left + 1][column];
|
||||
}
|
||||
|
||||
// The max angular step that the algorithm will traverse a quadrant of the
|
||||
// curve.
|
||||
//
|
||||
@ -245,82 +195,6 @@ Scalar CalculateStep(Scalar minDimension, Scalar fullAngle) {
|
||||
return std::min(kMinAngleStep, angleByDimension);
|
||||
}
|
||||
|
||||
// A factor used to calculate the "gap", defined as the distance from the
|
||||
// midpoint of the curved corners to the nearest sides of the bounding box.
|
||||
//
|
||||
// When the corner radius is symmetrical on both dimensions, the midpoint of the
|
||||
// corner is where the circular arc intersects its quadrant bisector. When the
|
||||
// corner radius is asymmetrical, since the corner can be considered "elongated"
|
||||
// from a symmetrical corner, the midpoint is transformed in the same way.
|
||||
//
|
||||
// Experiments indicate that the gap is linear with respect to the corner
|
||||
// radius on that dimension.
|
||||
//
|
||||
// The formula should be kept in sync with a few files, as documented in
|
||||
// `CalculateGap` in round_superellipse_geometry.cc.
|
||||
constexpr Scalar kGapFactor = 0.2924066406;
|
||||
|
||||
// Return the value that splits the range from `left` to `right` into two
|
||||
// portions whose ratio equals to `ratio_left` : `ratio_right`.
|
||||
static Scalar Split(Scalar left,
|
||||
Scalar right,
|
||||
Scalar ratio_left,
|
||||
Scalar ratio_right) {
|
||||
return (left * ratio_right + right * ratio_left) / (ratio_left + ratio_right);
|
||||
}
|
||||
|
||||
// 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. If `reverse` is true, then the curve
|
||||
// goes from `end` to `start` instead.
|
||||
//
|
||||
// The resulting points, after applying `transform`, are appended to `output`
|
||||
// and include the effective starting point but exclude the effective ending
|
||||
// point.
|
||||
//
|
||||
// Returns the number of generated points.
|
||||
size_t DrawCircularArc(Point* output,
|
||||
Point start,
|
||||
Point end,
|
||||
Scalar r,
|
||||
bool reverse,
|
||||
const Matrix& transform) {
|
||||
/* 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;
|
||||
Matrix full_transform = transform * Matrix::MakeTranslation(c);
|
||||
|
||||
Point* next = output;
|
||||
Scalar angle = reverse ? angle_sce : 0.0f;
|
||||
Scalar step =
|
||||
(reverse ? -1 : 1) * CalculateStep(std::abs(s_to_e.y), angle_sce);
|
||||
Scalar end_angle = reverse ? 0.0f : angle_sce;
|
||||
|
||||
while ((angle < end_angle) != reverse) {
|
||||
*(next++) = full_transform * c_to_s.Rotate(Radians(-angle));
|
||||
angle += step;
|
||||
}
|
||||
return next - output;
|
||||
}
|
||||
|
||||
// Draw a superellipsoid arc.
|
||||
//
|
||||
// The superellipse is centered at the origin and has degree `n` and both
|
||||
@ -328,10 +202,10 @@ size_t DrawCircularArc(Point* output,
|
||||
// to `max_theta` radiance clockwise if `reverse` is false, or from `max_theta`
|
||||
// to 0 otherwise.
|
||||
//
|
||||
// The resulting points, after applying `transform`, are appended to `output`
|
||||
// and include the starting point but exclude the ending point.
|
||||
// The resulting points, transformed by `transform`, are appended to `output`.
|
||||
// The starting point is included, but the ending point is excluded.
|
||||
//
|
||||
// Returns the number of generated points.
|
||||
// Returns the number of points generated.
|
||||
size_t DrawSuperellipsoidArc(Point* output,
|
||||
Scalar a,
|
||||
Scalar n,
|
||||
@ -353,25 +227,71 @@ size_t DrawSuperellipsoidArc(Point* output,
|
||||
return next - output;
|
||||
}
|
||||
|
||||
// Draws a circular arc centered at the origin with a radius of `r`, starting at
|
||||
// `start`, and spanning `max_angle` clockwise.
|
||||
//
|
||||
// If `reverse` is false, points are generated from `start` to `start +
|
||||
// max_angle`. If `reverse` is true, points are generated from `start +
|
||||
// max_angle` back to `start`.
|
||||
//
|
||||
// The generated points, transformed by `transform`, are appended to `output`.
|
||||
// The starting point is included, but the ending point is excluded.
|
||||
//
|
||||
// Returns the number of points generated.
|
||||
size_t DrawCircularArc(Point* output,
|
||||
Point start,
|
||||
Scalar max_angle,
|
||||
bool reverse,
|
||||
const Matrix& transform) {
|
||||
/* 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 end = start.Rotate(Radians(-max_angle));
|
||||
|
||||
Point* next = output;
|
||||
Scalar angle = reverse ? max_angle : 0.0f;
|
||||
Scalar step =
|
||||
(reverse ? -1 : 1) * CalculateStep(std::abs(start.y - end.y), max_angle);
|
||||
Scalar end_angle = reverse ? 0.0f : max_angle;
|
||||
|
||||
while ((angle < end_angle) != reverse) {
|
||||
*(next++) = transform * start.Rotate(Radians(-angle));
|
||||
angle += step;
|
||||
}
|
||||
return next - output;
|
||||
}
|
||||
|
||||
// Draws an arc representing the top 1/8 segment of a square-like rounded
|
||||
// superellipse centered at the origin.
|
||||
//
|
||||
// The square-like rounded superellipse that this arc belongs to 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.
|
||||
// If `reverse_and_flip` is false, the resulting arc spans from 0 (inclusive) to
|
||||
// pi/4 (exclusive), moving clockwise starting from the positive Y-axis. If
|
||||
// `reverse` is true, the curve spans from pi/4 (inclusive) to 0 (inclusive)
|
||||
// counterclockwise instead, and all points have their x and y coordinates
|
||||
// flipped.
|
||||
//
|
||||
// If `reverse` is false, the resulting arc spans from 0 (inclusive) to pi/4
|
||||
// (exclusive), moving clockwise starting from the positive Y-axis. If `reverse`
|
||||
// is true, the curve spans from pi/4 (inclusive) to 0 (inclusive)
|
||||
// counterclockwise instead.
|
||||
// Either way, each point is then transformed by `external_transform` and
|
||||
// appended to `output`.
|
||||
//
|
||||
// Returns the number of points generated.
|
||||
size_t DrawOctantSquareLikeSquircle(Point* output,
|
||||
Scalar size,
|
||||
Scalar corner_radius,
|
||||
bool reverse,
|
||||
const Matrix& transform) {
|
||||
const RoundSuperellipseParam::Octant& param,
|
||||
bool reverse_and_flip,
|
||||
const Matrix& external_transform) {
|
||||
Matrix transform = external_transform * Matrix::MakeTranslation(param.offset);
|
||||
if (reverse_and_flip) {
|
||||
transform = transform * kFlip;
|
||||
}
|
||||
|
||||
/* 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).
|
||||
@ -380,7 +300,7 @@ size_t DrawOctantSquareLikeSquircle(Point* output,
|
||||
* ↓ ↓
|
||||
* A B J circular arc
|
||||
* ---------...._ ↙
|
||||
* | | / `⟍ M
|
||||
* | | / `⟍ M (where y=x)
|
||||
* | | / ⟋ ⟍
|
||||
* | | / ⟋ \
|
||||
* | | / ⟋ |
|
||||
@ -392,56 +312,35 @@ size_t DrawOctantSquareLikeSquircle(Point* output,
|
||||
* O
|
||||
* ← 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 = kGapFactor * 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 pointA{0, size / 2};
|
||||
Point pointM{size / 2 - g, size / 2 - g};
|
||||
Point pointS{s, s};
|
||||
Point pointJ =
|
||||
Point{pow(abs(sinf(thetaJ)), 2 / n), pow(abs(cosf(thetaJ)), 2 / n)} * a +
|
||||
pointS;
|
||||
Matrix translationS = Matrix::MakeTranslation(pointS);
|
||||
|
||||
Point* next = output;
|
||||
if (!reverse) {
|
||||
if (!reverse_and_flip) {
|
||||
// Point A
|
||||
*(next++) = transform * pointA;
|
||||
*(next++) = transform * param.edge_mid;
|
||||
// Arc [B, J)
|
||||
next += DrawSuperellipsoidArc(next, a, n, thetaJ, reverse,
|
||||
transform * translationS);
|
||||
next += DrawSuperellipsoidArc(
|
||||
next, param.se_a, param.se_n, param.se_max_theta, reverse_and_flip,
|
||||
transform * Matrix::MakeTranslation(param.se_center));
|
||||
// Arc [J, M)
|
||||
next += DrawCircularArc(next, pointJ, pointM, R, reverse, transform);
|
||||
next += DrawCircularArc(
|
||||
next, param.circle_start - param.circle_center,
|
||||
param.circle_max_angle.radians, reverse_and_flip,
|
||||
transform * Matrix::MakeTranslation(param.circle_center));
|
||||
} else {
|
||||
// Arc [M, J)
|
||||
next += DrawCircularArc(next, pointJ, pointM, R, reverse, transform);
|
||||
next += DrawCircularArc(
|
||||
next, param.circle_start - param.circle_center,
|
||||
param.circle_max_angle.radians, reverse_and_flip,
|
||||
transform * Matrix::MakeTranslation(param.circle_center));
|
||||
// Arc [J, B)
|
||||
next += DrawSuperellipsoidArc(next, a, n, thetaJ, reverse,
|
||||
transform * translationS);
|
||||
next += DrawSuperellipsoidArc(
|
||||
next, param.se_a, param.se_n, param.se_max_theta, reverse_and_flip,
|
||||
transform * Matrix::MakeTranslation(param.se_center));
|
||||
// Point B
|
||||
*(next++) = transform * Point{s, size / 2};
|
||||
*(next++) = transform * (param.se_center + Point{0, param.se_a});
|
||||
// Point A
|
||||
*(next++) = transform * pointA;
|
||||
*(next++) = transform * param.edge_mid;
|
||||
}
|
||||
return next - output;
|
||||
}
|
||||
@ -449,47 +348,16 @@ size_t DrawOctantSquareLikeSquircle(Point* output,
|
||||
// Draw a quadrant curve, both ends included.
|
||||
//
|
||||
// Returns the number of points.
|
||||
//
|
||||
// The eact quadrant is specified by the direction of `outer` relative to
|
||||
// `center`. The curve goes from the X axis to the Y axis.
|
||||
static size_t DrawQuadrant(Point* output,
|
||||
Point center,
|
||||
Point outer,
|
||||
Size radii) {
|
||||
if (radii.width == 0 || radii.height == 0) {
|
||||
// Degrade to rectangle. (A zero radius causes error below.)
|
||||
output[0] = {center.x, outer.y};
|
||||
output[1] = outer;
|
||||
output[2] = {outer.x, center.y};
|
||||
return 3;
|
||||
}
|
||||
// Normalize sizes and radii into symmetrical radius by scaling the longer of
|
||||
// `radii` to the shorter. For example, to draw a RSE with size (200, 300)
|
||||
// and radii (20, 10), this function draws one with size (100, 300) and radii
|
||||
// (10, 10) and then scales it by (2x, 1x).
|
||||
Scalar norm_radius = radii.MinDimension();
|
||||
Size radius_scale = radii / norm_radius;
|
||||
Point signed_size = (outer - center) * 2;
|
||||
Point norm_size = signed_size.Abs() / radius_scale;
|
||||
Point signed_scale = signed_size / norm_size;
|
||||
|
||||
// Each quadrant curve is composed of two octant curves, each of which belongs
|
||||
// to a square-like rounded rectangle. When `norm_size`'s width != height, the
|
||||
// centers of such square-like rounded rectangles are offset from the origin
|
||||
// by a distance denoted as `c`.
|
||||
Scalar c = (norm_size.x - norm_size.y) / 2;
|
||||
|
||||
const RoundSuperellipseParam::Quadrant& param) {
|
||||
Point* next = output;
|
||||
auto transform = Matrix::MakeTranslateScale(param.signed_scale, param.offset);
|
||||
|
||||
next += DrawOctantSquareLikeSquircle(
|
||||
next, norm_size.x, norm_radius, /*reverse=*/false,
|
||||
Matrix::MakeTranslateScale(signed_scale, center) *
|
||||
Matrix::MakeTranslation(Size{0, -c}));
|
||||
next += DrawOctantSquareLikeSquircle(next, param.top,
|
||||
/*reverse_and_flip=*/false, transform);
|
||||
|
||||
next += DrawOctantSquareLikeSquircle(
|
||||
next, norm_size.y, norm_radius, /*reverse=*/true,
|
||||
Matrix::MakeTranslateScale(signed_scale, center) *
|
||||
Matrix::MakeTranslation(Size{c, 0}) * kFlip);
|
||||
next += DrawOctantSquareLikeSquircle(next, param.right,
|
||||
/*reverse_and_flip=*/true, transform);
|
||||
|
||||
return next - output;
|
||||
}
|
||||
@ -525,43 +393,26 @@ GeometryResult RoundSuperellipseGeometry::GetPositionBuffer(
|
||||
UnevenQuadrantsRearranger>
|
||||
rearranger_holder;
|
||||
|
||||
if (radii_.AreAllCornersSame()) {
|
||||
auto param = RoundSuperellipseParam::MakeBoundsRadii(bounds_, radii_);
|
||||
|
||||
if (param.all_corners_same) {
|
||||
rearranger_holder.emplace<MirroredQuadrantRearranger>(bounds_.GetCenter(),
|
||||
cache);
|
||||
auto& t = std::get<MirroredQuadrantRearranger>(rearranger_holder);
|
||||
rearranger = &t;
|
||||
|
||||
// The quadrant must be drawn at the origin so that it can be rotated later.
|
||||
t.QuadSize() = DrawQuadrant(cache, Point(),
|
||||
bounds_.GetRightTop() - bounds_.GetCenter(),
|
||||
radii_.top_right);
|
||||
param.top_right.offset = Point();
|
||||
t.QuadSize() = DrawQuadrant(cache, param.top_right);
|
||||
} else {
|
||||
rearranger_holder.emplace<UnevenQuadrantsRearranger>(cache, kMaxQuadSize);
|
||||
auto& t = std::get<UnevenQuadrantsRearranger>(rearranger_holder);
|
||||
rearranger = &t;
|
||||
|
||||
Scalar top_split = Split(bounds_.GetLeft(), bounds_.GetRight(),
|
||||
radii_.top_left.width, radii_.top_right.width);
|
||||
Scalar right_split =
|
||||
Split(bounds_.GetTop(), bounds_.GetBottom(), radii_.top_right.height,
|
||||
radii_.bottom_right.height);
|
||||
Scalar bottom_split =
|
||||
Split(bounds_.GetLeft(), bounds_.GetRight(), radii_.bottom_left.width,
|
||||
radii_.bottom_right.width);
|
||||
Scalar left_split =
|
||||
Split(bounds_.GetTop(), bounds_.GetBottom(), radii_.top_left.height,
|
||||
radii_.bottom_left.height);
|
||||
|
||||
t.QuadSize(0) = DrawQuadrant(t.QuadCache(0), Point{top_split, right_split},
|
||||
bounds_.GetRightTop(), radii_.top_right);
|
||||
t.QuadSize(1) =
|
||||
DrawQuadrant(t.QuadCache(1), Point{bottom_split, right_split},
|
||||
bounds_.GetRightBottom(), radii_.bottom_right);
|
||||
t.QuadSize(2) =
|
||||
DrawQuadrant(t.QuadCache(2), Point{bottom_split, left_split},
|
||||
bounds_.GetLeftBottom(), radii_.bottom_left);
|
||||
t.QuadSize(3) = DrawQuadrant(t.QuadCache(3), Point{top_split, left_split},
|
||||
bounds_.GetLeftTop(), radii_.top_left);
|
||||
t.QuadSize(0) = DrawQuadrant(t.QuadCache(0), param.top_right);
|
||||
t.QuadSize(1) = DrawQuadrant(t.QuadCache(1), param.bottom_right);
|
||||
t.QuadSize(2) = DrawQuadrant(t.QuadCache(2), param.bottom_left);
|
||||
t.QuadSize(3) = DrawQuadrant(t.QuadCache(3), param.top_left);
|
||||
}
|
||||
|
||||
size_t contour_length = rearranger->ContourLength();
|
||||
|
@ -31,6 +31,10 @@ impeller_component("geometry") {
|
||||
"rect.h",
|
||||
"round_rect.cc",
|
||||
"round_rect.h",
|
||||
"round_superellipse.cc",
|
||||
"round_superellipse.h",
|
||||
"round_superellipse_param.cc",
|
||||
"round_superellipse_param.h",
|
||||
"rounding_radii.cc",
|
||||
"rounding_radii.h",
|
||||
"rstransform.cc",
|
||||
@ -78,6 +82,8 @@ impeller_component("geometry_unittests") {
|
||||
"path_unittests.cc",
|
||||
"rect_unittests.cc",
|
||||
"round_rect_unittests.cc",
|
||||
"round_superellipse_unittests.cc",
|
||||
"rounding_radii_unittests.cc",
|
||||
"rstransform_unittests.cc",
|
||||
"saturated_math_unittests.cc",
|
||||
"size_unittests.cc",
|
||||
|
@ -33,6 +33,8 @@ Path CreateCubic(bool closed);
|
||||
Path CreateQuadratic(bool closed);
|
||||
/// Create a rounded rect.
|
||||
Path CreateRRect();
|
||||
/// Create a rounded superellipse.
|
||||
Path CreateRSuperellipse();
|
||||
} // namespace
|
||||
|
||||
static TessellatorLibtess tess;
|
||||
@ -141,6 +143,12 @@ MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Bevel, );
|
||||
MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Miter, );
|
||||
MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Round, );
|
||||
|
||||
// Same as RRect
|
||||
BENCHMARK_CAPTURE(BM_Convex, rse_convex, CreateRSuperellipse(), true);
|
||||
MAKE_STROKE_BENCHMARK_CAPTURE(RSuperellipse, Butt, Bevel, );
|
||||
MAKE_STROKE_BENCHMARK_CAPTURE(RSuperellipse, Butt, Miter, );
|
||||
MAKE_STROKE_BENCHMARK_CAPTURE(RSuperellipse, Butt, Round, );
|
||||
|
||||
namespace {
|
||||
|
||||
Path CreateRRect() {
|
||||
@ -150,6 +158,13 @@ Path CreateRRect() {
|
||||
.TakePath();
|
||||
}
|
||||
|
||||
Path CreateRSuperellipse() {
|
||||
return PathBuilder{}
|
||||
.AddRoundSuperellipse(
|
||||
RoundSuperellipse::MakeRectXY(Rect::MakeLTRB(0, 0, 400, 400), 16, 16))
|
||||
.TakePath();
|
||||
}
|
||||
|
||||
Path CreateCubic(bool closed) {
|
||||
auto builder = PathBuilder{};
|
||||
builder //
|
||||
|
@ -4,12 +4,170 @@
|
||||
|
||||
#include "path_builder.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
#include "impeller/geometry/path_component.h"
|
||||
#include "impeller/geometry/round_superellipse_param.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
namespace {
|
||||
|
||||
// Utility functions used to build a rounded superellipse.
|
||||
class RoundSuperellipseBuilder {
|
||||
public:
|
||||
typedef std::function<
|
||||
void(const Point&, const Point&, const Point&, const Point&)>
|
||||
CubicAdder;
|
||||
|
||||
// Create a builder.
|
||||
//
|
||||
// The resulting curves, which consists of cubic curves, are added by calling
|
||||
// `cubic_adder`.
|
||||
explicit RoundSuperellipseBuilder(CubicAdder cubic_adder)
|
||||
: cubic_adder_(std::move(cubic_adder)) {}
|
||||
|
||||
// Draws an arc representing 1/4 of a rounded superellipse.
|
||||
//
|
||||
// If `reverse` is false, the resulting arc spans from 0 to pi/2, moving
|
||||
// clockwise starting from the positive Y-axis. Otherwise it moves from pi/2
|
||||
// to 0.
|
||||
void AddQuadrant(const RoundSuperellipseParam::Quadrant& param,
|
||||
bool reverse) {
|
||||
auto transform =
|
||||
Matrix::MakeTranslateScale(param.signed_scale, param.offset);
|
||||
if (!reverse) {
|
||||
AddOctant(param.top, /*reverse=*/false, /*flip=*/false, transform);
|
||||
AddOctant(param.right, /*reverse=*/true, /*flip=*/true, transform);
|
||||
} else {
|
||||
AddOctant(param.right, /*reverse=*/false, /*flip=*/true, transform);
|
||||
AddOctant(param.top, /*reverse=*/true, /*flip=*/false, transform);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<Point, 4> SuperellipseArcPoints(
|
||||
const RoundSuperellipseParam::Octant& param) {
|
||||
Point start = {param.se_center.x, param.edge_mid.y};
|
||||
const Point& end = param.circle_start;
|
||||
constexpr Point start_tangent = {1, 0};
|
||||
Point circle_start_vector = param.circle_start - param.circle_center;
|
||||
Point end_tangent =
|
||||
Point{-circle_start_vector.y, circle_start_vector.x}.Normalize();
|
||||
|
||||
std::array<Scalar, 2> factors = SuperellipseBezierFactors(param.se_n);
|
||||
|
||||
return std::array<Point, 4>{
|
||||
start, start + start_tangent * factors[0] * param.se_a,
|
||||
end + end_tangent * factors[1] * param.se_a, end};
|
||||
};
|
||||
|
||||
std::array<Point, 4> CircularArcPoints(
|
||||
const RoundSuperellipseParam::Octant& param) {
|
||||
Point start_vector = param.circle_start - param.circle_center;
|
||||
Point end_vector =
|
||||
start_vector.Rotate(Radians(-param.circle_max_angle.radians));
|
||||
Point circle_end = param.circle_center + end_vector;
|
||||
Point start_tangent = Point{start_vector.y, -start_vector.x}.Normalize();
|
||||
Point end_tangent = Point{-end_vector.y, end_vector.x}.Normalize();
|
||||
Scalar bezier_factor = std::tan(param.circle_max_angle.radians / 4) * 4 / 3;
|
||||
Scalar radius = start_vector.GetLength();
|
||||
|
||||
return std::array<Point, 4>{
|
||||
param.circle_start,
|
||||
param.circle_start + start_tangent * bezier_factor * radius,
|
||||
circle_end + end_tangent * bezier_factor * radius, circle_end};
|
||||
};
|
||||
|
||||
// Draws an arc representing 1/8 of a rounded superellipse.
|
||||
//
|
||||
// If `reverse` is false, the resulting arc spans from 0 to pi/4, moving
|
||||
// clockwise starting from the positive Y-axis. Otherwise it moves from pi/4
|
||||
// to 0.
|
||||
//
|
||||
// If `flip` is true, all points have their X and Y coordinates swapped,
|
||||
// effectively mirrowing each point by the y=x line.
|
||||
//
|
||||
// All points are transformed by `external_transform` after the optional
|
||||
// flipping before being used as control points for the cubic curves.
|
||||
void AddOctant(const RoundSuperellipseParam::Octant& param,
|
||||
bool reverse,
|
||||
bool flip,
|
||||
const Matrix& external_transform) {
|
||||
Matrix transform =
|
||||
external_transform * Matrix::MakeTranslation(param.offset);
|
||||
if (flip) {
|
||||
transform = transform * kFlip;
|
||||
}
|
||||
|
||||
auto circle_points = CircularArcPoints(param);
|
||||
auto se_points = SuperellipseArcPoints(param);
|
||||
|
||||
if (!reverse) {
|
||||
cubic_adder_(transform * se_points[0], transform * se_points[1],
|
||||
transform * se_points[2], transform * se_points[3]);
|
||||
cubic_adder_(transform * circle_points[0], transform * circle_points[1],
|
||||
transform * circle_points[2], transform * circle_points[3]);
|
||||
} else {
|
||||
cubic_adder_(transform * circle_points[3], transform * circle_points[2],
|
||||
transform * circle_points[1], transform * circle_points[0]);
|
||||
cubic_adder_(transform * se_points[3], transform * se_points[2],
|
||||
transform * se_points[1], transform * se_points[0]);
|
||||
}
|
||||
};
|
||||
|
||||
// Get the Bezier factor for the superellipse arc in a rounded superellipse.
|
||||
//
|
||||
// The result will be assigned to output, where [0] will be the factor for the
|
||||
// starting tangent and [1] for the ending tangent.
|
||||
//
|
||||
// These values are computed by brute-force searching for the minimal distance
|
||||
// on a rounded superellipse and are not for general purpose superellipses.
|
||||
std::array<Scalar, 2> SuperellipseBezierFactors(Scalar n) {
|
||||
constexpr Scalar kPrecomputedVariables[][2] = {
|
||||
/*n=2.000*/ {0.02927797, 0.05200645},
|
||||
/*n=2.050*/ {0.02927797, 0.05200645},
|
||||
/*n=2.100*/ {0.03288032, 0.06051731},
|
||||
/*n=2.150*/ {0.03719241, 0.06818433},
|
||||
/*n=2.200*/ {0.04009513, 0.07196947},
|
||||
/*n=2.250*/ {0.04504750, 0.07860258},
|
||||
/*n=2.300*/ {0.05038706, 0.08498836},
|
||||
/*n=2.350*/ {0.05580771, 0.09071105},
|
||||
/*n=2.400*/ {0.06002306, 0.09363976},
|
||||
/*n=2.450*/ {0.06630048, 0.09946086},
|
||||
/*n=2.500*/ {0.07200351, 0.10384857}};
|
||||
constexpr Scalar kNStepInverse = 20; // = 1 / 0.05
|
||||
constexpr size_t kNumRecords =
|
||||
sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]);
|
||||
constexpr Scalar kMinN = 2.00f;
|
||||
|
||||
Scalar steps =
|
||||
std::clamp<Scalar>((n - kMinN) * kNStepInverse, 0, kNumRecords - 1);
|
||||
size_t left = std::clamp<size_t>(static_cast<size_t>(std::floor(steps)), 0,
|
||||
kNumRecords - 2);
|
||||
Scalar frac = steps - left;
|
||||
|
||||
return std::array<Scalar, 2>{(1 - frac) * kPrecomputedVariables[left][0] +
|
||||
frac * kPrecomputedVariables[left + 1][0],
|
||||
(1 - frac) * kPrecomputedVariables[left][1] +
|
||||
frac * kPrecomputedVariables[left + 1][1]};
|
||||
}
|
||||
|
||||
CubicAdder cubic_adder_;
|
||||
|
||||
// A matrix that swaps the coordinates of a point.
|
||||
// clang-format off
|
||||
static constexpr Matrix kFlip = Matrix(
|
||||
0.0f, 1.0f, 0.0f, 0.0f,
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
PathBuilder::PathBuilder() {
|
||||
AddContourComponent({});
|
||||
}
|
||||
@ -219,6 +377,46 @@ PathBuilder& PathBuilder::AddRoundRect(RoundRect round_rect) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::AddRoundSuperellipse(RoundSuperellipse rse) {
|
||||
if (rse.IsRect()) {
|
||||
return AddRect(rse.GetBounds());
|
||||
}
|
||||
|
||||
RoundSuperellipseBuilder builder(
|
||||
[this](const Point& a, const Point& b, const Point& c, const Point& d) {
|
||||
AddCubicComponent(a, b, c, d);
|
||||
});
|
||||
|
||||
auto param =
|
||||
RoundSuperellipseParam::MakeBoundsRadii(rse.GetBounds(), rse.GetRadii());
|
||||
Point start = param.top_right.offset +
|
||||
param.top_right.signed_scale *
|
||||
(param.top_right.top.offset + param.top_right.top.edge_mid);
|
||||
MoveTo(start);
|
||||
|
||||
if (param.all_corners_same) {
|
||||
auto* quadrant = ¶m.top_right;
|
||||
builder.AddQuadrant(*quadrant, /*reverse=*/false);
|
||||
quadrant->signed_scale.y *= -1;
|
||||
builder.AddQuadrant(*quadrant, /*reverse=*/true);
|
||||
quadrant->signed_scale.x *= -1;
|
||||
builder.AddQuadrant(*quadrant, /*reverse=*/false);
|
||||
quadrant->signed_scale.y *= -1;
|
||||
builder.AddQuadrant(*quadrant, /*reverse=*/true);
|
||||
} else {
|
||||
builder.AddQuadrant(param.top_right, /*reverse=*/false);
|
||||
builder.AddQuadrant(param.bottom_right, /*reverse=*/true);
|
||||
builder.AddQuadrant(param.bottom_left, /*reverse=*/false);
|
||||
builder.AddQuadrant(param.top_left, /*reverse=*/true);
|
||||
}
|
||||
|
||||
LineTo(start);
|
||||
|
||||
Close();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::AddRoundedRectTopLeft(Rect rect,
|
||||
RoundingRadii radii) {
|
||||
const auto magic_top_left = radii.top_left * kArcApproximationMagic;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "impeller/geometry/path.h"
|
||||
#include "impeller/geometry/rect.h"
|
||||
#include "impeller/geometry/round_rect.h"
|
||||
#include "impeller/geometry/round_superellipse.h"
|
||||
#include "impeller/geometry/scalar.h"
|
||||
|
||||
namespace impeller {
|
||||
@ -105,6 +106,8 @@ class PathBuilder {
|
||||
|
||||
PathBuilder& AddRoundRect(RoundRect rect);
|
||||
|
||||
PathBuilder& AddRoundSuperellipse(RoundSuperellipse rse);
|
||||
|
||||
PathBuilder& AddPath(const Path& path);
|
||||
|
||||
private:
|
||||
|
@ -80,6 +80,15 @@ TEST(PathTest, PathSingleContour) {
|
||||
EXPECT_TRUE(path.IsSingleContour());
|
||||
}
|
||||
|
||||
{
|
||||
Path path = PathBuilder{}
|
||||
.AddRoundSuperellipse(RoundSuperellipse::MakeRectRadius(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), 10))
|
||||
.TakePath();
|
||||
|
||||
EXPECT_TRUE(path.IsSingleContour());
|
||||
}
|
||||
|
||||
// Open shapes.
|
||||
{
|
||||
Point p(100, 100);
|
||||
@ -156,6 +165,28 @@ TEST(PathTest, PathSingleContourDoubleShapes) {
|
||||
EXPECT_FALSE(path.IsSingleContour());
|
||||
}
|
||||
|
||||
{
|
||||
Path path = PathBuilder{}
|
||||
.AddRoundSuperellipse(RoundSuperellipse::MakeRectRadius(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), 10))
|
||||
.AddRoundSuperellipse(RoundSuperellipse::MakeRectRadius(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), 10))
|
||||
.TakePath();
|
||||
|
||||
EXPECT_FALSE(path.IsSingleContour());
|
||||
}
|
||||
|
||||
{
|
||||
Path path = PathBuilder{}
|
||||
.AddRoundSuperellipse(RoundSuperellipse::MakeRectXY(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20)))
|
||||
.AddRoundSuperellipse(RoundSuperellipse::MakeRectXY(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20)))
|
||||
.TakePath();
|
||||
|
||||
EXPECT_FALSE(path.IsSingleContour());
|
||||
}
|
||||
|
||||
// Open shapes.
|
||||
{
|
||||
Point p(100, 100);
|
||||
@ -236,6 +267,28 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
|
||||
EXPECT_TRUE(contour.IsClosed());
|
||||
}
|
||||
|
||||
{
|
||||
Path path = PathBuilder{}
|
||||
.AddRoundSuperellipse(RoundSuperellipse::MakeRectRadius(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), 10))
|
||||
.TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
EXPECT_POINT_NEAR(contour.destination, Point(150, 100));
|
||||
EXPECT_TRUE(contour.IsClosed());
|
||||
}
|
||||
|
||||
{
|
||||
Path path = PathBuilder{}
|
||||
.AddRoundSuperellipse(RoundSuperellipse::MakeRectXY(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20)))
|
||||
.TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
EXPECT_POINT_NEAR(contour.destination, Point(150, 100));
|
||||
EXPECT_TRUE(contour.IsClosed());
|
||||
}
|
||||
|
||||
// Open shapes.
|
||||
{
|
||||
Point p(100, 100);
|
||||
|
@ -11,312 +11,6 @@
|
||||
namespace impeller {
|
||||
namespace testing {
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiEmptyDeclaration) {
|
||||
RoundingRadii radii;
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size());
|
||||
EXPECT_EQ(radii.top_right, Size());
|
||||
EXPECT_EQ(radii.bottom_left, Size());
|
||||
EXPECT_EQ(radii.bottom_right, Size());
|
||||
EXPECT_EQ(radii.top_left.width, 0.0f);
|
||||
EXPECT_EQ(radii.top_left.height, 0.0f);
|
||||
EXPECT_EQ(radii.top_right.width, 0.0f);
|
||||
EXPECT_EQ(radii.top_right.height, 0.0f);
|
||||
EXPECT_EQ(radii.bottom_left.width, 0.0f);
|
||||
EXPECT_EQ(radii.bottom_left.height, 0.0f);
|
||||
EXPECT_EQ(radii.bottom_right.width, 0.0f);
|
||||
EXPECT_EQ(radii.bottom_right.height, 0.0f);
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiDefaultConstructor) {
|
||||
RoundingRadii radii = RoundingRadii();
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size());
|
||||
EXPECT_EQ(radii.top_right, Size());
|
||||
EXPECT_EQ(radii.bottom_left, Size());
|
||||
EXPECT_EQ(radii.bottom_right, Size());
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiScalarConstructor) {
|
||||
RoundingRadii radii = RoundingRadii::MakeRadius(5.0f);
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(radii.top_right, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(5.0f, 5.0f));
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiEmptyScalarConstructor) {
|
||||
RoundingRadii radii = RoundingRadii::MakeRadius(-5.0f);
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(-5.0f, -5.0f));
|
||||
EXPECT_EQ(radii.top_right, Size(-5.0f, -5.0f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(-5.0f, -5.0f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(-5.0f, -5.0f));
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiSizeConstructor) {
|
||||
RoundingRadii radii = RoundingRadii::MakeRadii(Size(5.0f, 6.0f));
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.top_right, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(5.0f, 6.0f));
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiEmptySizeConstructor) {
|
||||
{
|
||||
RoundingRadii radii = RoundingRadii::MakeRadii(Size(-5.0f, 6.0f));
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(-5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.top_right, Size(-5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(-5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(-5.0f, 6.0f));
|
||||
}
|
||||
|
||||
{
|
||||
RoundingRadii radii = RoundingRadii::MakeRadii(Size(5.0f, -6.0f));
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(5.0f, -6.0f));
|
||||
EXPECT_EQ(radii.top_right, Size(5.0f, -6.0f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(5.0f, -6.0f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(5.0f, -6.0f));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiNamedSizesConstructor) {
|
||||
RoundingRadii radii = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(5.0f, 5.5f));
|
||||
EXPECT_EQ(radii.top_right, Size(6.0f, 6.5f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(7.0f, 7.5f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(8.0f, 8.5f));
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiPartialNamedSizesConstructor) {
|
||||
{
|
||||
RoundingRadii radii = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(5.0f, 5.5f));
|
||||
EXPECT_EQ(radii.top_right, Size());
|
||||
EXPECT_EQ(radii.bottom_left, Size());
|
||||
EXPECT_EQ(radii.bottom_right, Size());
|
||||
}
|
||||
|
||||
{
|
||||
RoundingRadii radii = {
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size());
|
||||
EXPECT_EQ(radii.top_right, Size(6.0f, 6.5f));
|
||||
EXPECT_EQ(radii.bottom_left, Size());
|
||||
EXPECT_EQ(radii.bottom_right, Size());
|
||||
}
|
||||
|
||||
{
|
||||
RoundingRadii radii = {
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size());
|
||||
EXPECT_EQ(radii.top_right, Size());
|
||||
EXPECT_EQ(radii.bottom_left, Size(7.0f, 7.5f));
|
||||
EXPECT_EQ(radii.bottom_right, Size());
|
||||
}
|
||||
|
||||
{
|
||||
RoundingRadii radii = {
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size());
|
||||
EXPECT_EQ(radii.top_right, Size());
|
||||
EXPECT_EQ(radii.bottom_left, Size());
|
||||
EXPECT_EQ(radii.bottom_right, Size(8.0f, 8.5f));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiMultiply) {
|
||||
RoundingRadii radii = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
RoundingRadii doubled = radii * 2.0f;
|
||||
|
||||
EXPECT_FALSE(doubled.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(doubled.AreAllCornersSame());
|
||||
EXPECT_TRUE(doubled.IsFinite());
|
||||
EXPECT_EQ(doubled.top_left, Size(10.0f, 11.0f));
|
||||
EXPECT_EQ(doubled.top_right, Size(12.0f, 13.0f));
|
||||
EXPECT_EQ(doubled.bottom_left, Size(14.0f, 15.0f));
|
||||
EXPECT_EQ(doubled.bottom_right, Size(16.0f, 17.0f));
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiEquals) {
|
||||
RoundingRadii radii = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
RoundingRadii other = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
|
||||
EXPECT_EQ(radii, other);
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiNotEquals) {
|
||||
const RoundingRadii radii = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_left.width = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_left.height = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_right.width = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_right.height = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_left.width = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_left.height = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_right.width = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_right.height = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, RoundingRadiiCornersSameTolerance) {
|
||||
RoundingRadii radii{
|
||||
.top_left = {10, 20},
|
||||
.top_right = {10.01, 20.01},
|
||||
.bottom_left = {9.99, 19.99},
|
||||
.bottom_right = {9.99, 20.01},
|
||||
};
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersSame(.02));
|
||||
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_left.width = 10.03;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_left.height = 20.03;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_right.width = 10.03;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_right.height = 20.03;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_left.width = 9.97;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_left.height = 19.97;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_right.width = 9.97;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_right.height = 20.03;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RoundRectTest, EmptyDeclaration) {
|
||||
RoundRect round_rect;
|
||||
|
||||
|
33
engine/src/flutter/impeller/geometry/round_superellipse.cc
Normal file
33
engine/src/flutter/impeller/geometry/round_superellipse.cc
Normal file
@ -0,0 +1,33 @@
|
||||
// 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 "flutter/impeller/geometry/round_superellipse.h"
|
||||
|
||||
#include "flutter/impeller/geometry/round_superellipse_param.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
RoundSuperellipse RoundSuperellipse::MakeRectRadii(
|
||||
const Rect& in_bounds,
|
||||
const RoundingRadii& in_radii) {
|
||||
if (!in_bounds.IsFinite()) {
|
||||
return {};
|
||||
}
|
||||
Rect bounds = in_bounds.GetPositive();
|
||||
// RoundingRadii::Scaled might return an empty radii if bounds or in_radii is
|
||||
// empty, which is expected. Pass along the bounds even if the radii is empty
|
||||
// as it would still have a valid location and/or 1-dimensional size which
|
||||
// might appear when stroked
|
||||
return RoundSuperellipse(bounds, in_radii.Scaled(bounds));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool RoundSuperellipse::Contains(const Point& p) const {
|
||||
if (!bounds_.Contains(p)) {
|
||||
return false;
|
||||
}
|
||||
auto param = RoundSuperellipseParam::MakeBoundsRadii(bounds_, radii_);
|
||||
return param.Contains(p);
|
||||
}
|
||||
|
||||
} // namespace impeller
|
150
engine/src/flutter/impeller/geometry/round_superellipse.h
Normal file
150
engine/src/flutter/impeller/geometry/round_superellipse.h
Normal file
@ -0,0 +1,150 @@
|
||||
// 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_GEOMETRY_ROUND_SUPERELLIPSE_H_
|
||||
#define FLUTTER_IMPELLER_GEOMETRY_ROUND_SUPERELLIPSE_H_
|
||||
|
||||
#include "flutter/impeller/geometry/point.h"
|
||||
#include "flutter/impeller/geometry/rect.h"
|
||||
#include "flutter/impeller/geometry/rounding_radii.h"
|
||||
#include "flutter/impeller/geometry/size.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
struct RoundSuperellipse {
|
||||
RoundSuperellipse() = default;
|
||||
|
||||
constexpr static RoundSuperellipse MakeRect(const Rect& rect) {
|
||||
return MakeRectRadii(rect, RoundingRadii());
|
||||
}
|
||||
|
||||
constexpr static RoundSuperellipse MakeOval(const Rect& rect) {
|
||||
return MakeRectRadii(rect, RoundingRadii::MakeRadii(rect.GetSize() * 0.5f));
|
||||
}
|
||||
|
||||
constexpr static RoundSuperellipse MakeRectRadius(const Rect& rect,
|
||||
Scalar radius) {
|
||||
return MakeRectRadii(rect, RoundingRadii::MakeRadius(radius));
|
||||
}
|
||||
|
||||
constexpr static RoundSuperellipse MakeRectXY(const Rect& rect,
|
||||
Scalar x_radius,
|
||||
Scalar y_radius) {
|
||||
return MakeRectRadii(rect,
|
||||
RoundingRadii::MakeRadii(Size(x_radius, y_radius)));
|
||||
}
|
||||
|
||||
constexpr static RoundSuperellipse MakeRectXY(const Rect& rect,
|
||||
Size corner_radii) {
|
||||
return MakeRectRadii(rect, RoundingRadii::MakeRadii(corner_radii));
|
||||
}
|
||||
|
||||
static RoundSuperellipse MakeRectRadii(const Rect& rect,
|
||||
const RoundingRadii& radii);
|
||||
|
||||
constexpr const Rect& GetBounds() const { return bounds_; }
|
||||
constexpr const RoundingRadii& GetRadii() const { return radii_; }
|
||||
|
||||
[[nodiscard]] constexpr bool IsFinite() const {
|
||||
return bounds_.IsFinite() && //
|
||||
radii_.top_left.IsFinite() && //
|
||||
radii_.top_right.IsFinite() && //
|
||||
radii_.bottom_left.IsFinite() && //
|
||||
radii_.bottom_right.IsFinite();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool IsEmpty() const { return bounds_.IsEmpty(); }
|
||||
|
||||
[[nodiscard]] constexpr bool IsRect() const {
|
||||
return !bounds_.IsEmpty() && radii_.AreAllCornersEmpty();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool IsOval() const {
|
||||
return !bounds_.IsEmpty() && radii_.AreAllCornersSame() &&
|
||||
ScalarNearlyEqual(radii_.top_left.width,
|
||||
bounds_.GetWidth() * 0.5f) &&
|
||||
ScalarNearlyEqual(radii_.top_left.height,
|
||||
bounds_.GetHeight() * 0.5f);
|
||||
}
|
||||
|
||||
/// @brief Returns true iff the provided point |p| is inside the
|
||||
/// half-open interior of this rectangle.
|
||||
///
|
||||
/// For purposes of containment, a rectangle contains points
|
||||
/// along the top and left edges but not points along the
|
||||
/// right and bottom edges so that a point is only ever
|
||||
/// considered inside one of two abutting rectangles.
|
||||
[[nodiscard]] bool Contains(const Point& p) const;
|
||||
|
||||
/// @brief Returns a new round rectangle translated by the given offset.
|
||||
[[nodiscard]] constexpr RoundSuperellipse Shift(Scalar dx, Scalar dy) const {
|
||||
// Just in case, use the factory rather than the internal constructor
|
||||
// as shifting the rectangle may increase/decrease its bit precision
|
||||
// so we should re-validate the radii to the newly located rectangle.
|
||||
return MakeRectRadii(bounds_.Shift(dx, dy), radii_);
|
||||
}
|
||||
|
||||
/// @brief Returns a round rectangle with expanded edges. Negative expansion
|
||||
/// results in shrinking.
|
||||
[[nodiscard]] constexpr RoundSuperellipse Expand(Scalar left,
|
||||
Scalar top,
|
||||
Scalar right,
|
||||
Scalar bottom) const {
|
||||
// Use the factory rather than the internal constructor as the changing
|
||||
// size of the rectangle requires that we re-validate the radii to the
|
||||
// newly sized rectangle.
|
||||
return MakeRectRadii(bounds_.Expand(left, top, right, bottom), radii_);
|
||||
}
|
||||
|
||||
/// @brief Returns a round rectangle with expanded edges. Negative expansion
|
||||
/// results in shrinking.
|
||||
[[nodiscard]] constexpr RoundSuperellipse Expand(Scalar horizontal,
|
||||
Scalar vertical) const {
|
||||
// Use the factory rather than the internal constructor as the changing
|
||||
// size of the rectangle requires that we re-validate the radii to the
|
||||
// newly sized rectangle.
|
||||
return MakeRectRadii(bounds_.Expand(horizontal, vertical), radii_);
|
||||
}
|
||||
|
||||
/// @brief Returns a round rectangle with expanded edges. Negative expansion
|
||||
/// results in shrinking.
|
||||
[[nodiscard]] constexpr RoundSuperellipse Expand(Scalar amount) const {
|
||||
// Use the factory rather than the internal constructor as the changing
|
||||
// size of the rectangle requires that we re-validate the radii to the
|
||||
// newly sized rectangle.
|
||||
return MakeRectRadii(bounds_.Expand(amount), radii_);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator==(const RoundSuperellipse& rr) const {
|
||||
return bounds_ == rr.bounds_ && radii_ == rr.radii_;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator!=(const RoundSuperellipse& r) const {
|
||||
return !(*this == r);
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr RoundSuperellipse(const Rect& bounds, const RoundingRadii& radii)
|
||||
: bounds_(bounds), radii_(radii) {}
|
||||
|
||||
Rect bounds_;
|
||||
RoundingRadii radii_;
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
namespace std {
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out,
|
||||
const impeller::RoundSuperellipse& rr) {
|
||||
out << "(" //
|
||||
<< "rect: " << rr.GetBounds() << ", " //
|
||||
<< "radii: " << rr.GetRadii();
|
||||
out << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif // FLUTTER_IMPELLER_GEOMETRY_ROUND_SUPERELLIPSE_H_
|
340
engine/src/flutter/impeller/geometry/round_superellipse_param.cc
Normal file
340
engine/src/flutter/impeller/geometry/round_superellipse_param.cc
Normal file
@ -0,0 +1,340 @@
|
||||
// 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 "flutter/impeller/geometry/round_superellipse_param.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
namespace {
|
||||
|
||||
// Return the value that splits the range from `left` to `right` into two
|
||||
// portions whose ratio equals to `ratio_left` : `ratio_right`.
|
||||
Scalar Split(Scalar left, Scalar right, Scalar ratio_left, Scalar ratio_right) {
|
||||
if (ratio_left == 0 && ratio_right == 0) {
|
||||
return (left + right) / 2;
|
||||
}
|
||||
return (left * ratio_right + right * ratio_left) / (ratio_left + ratio_right);
|
||||
}
|
||||
|
||||
// Return the same Point, but each NaN coordinate is replaced by 1.
|
||||
inline Point ReplanceNaNWithOne(Point in) {
|
||||
return Point{std::isnan(in.x) ? 1 : in.x, std::isnan(in.y) ? 1 : in.y};
|
||||
}
|
||||
|
||||
// Swap the x and y coordinate of a point.
|
||||
//
|
||||
// Effectively mirrors the point by the y=x line.
|
||||
inline Point Flip(Point a) {
|
||||
return Point{a.y, a.x};
|
||||
}
|
||||
|
||||
// A look up table with precomputed variables.
|
||||
//
|
||||
// The columns represent the following variabls respectively:
|
||||
//
|
||||
// * n
|
||||
// * sin(thetaJ)
|
||||
//
|
||||
// For definition of the variables, see ComputeOctant.
|
||||
constexpr Scalar kPrecomputedVariables[][2] = {
|
||||
/*ratio=2.00*/ {2.00000000, 0.117205737},
|
||||
/*ratio=2.02*/ {2.03999083, 0.117205737},
|
||||
/*ratio=2.04*/ {2.07976152, 0.119418745},
|
||||
/*ratio=2.06*/ {2.11195967, 0.136274515},
|
||||
/*ratio=2.08*/ {2.14721808, 0.141289310},
|
||||
/*ratio=2.10*/ {2.18349805, 0.143410679},
|
||||
/*ratio=2.12*/ {2.21858213, 0.146668334},
|
||||
/*ratio=2.14*/ {2.24861661, 0.154985392},
|
||||
/*ratio=2.16*/ {2.28146030, 0.158932848},
|
||||
/*ratio=2.18*/ {2.30842385, 0.168182439},
|
||||
/*ratio=2.20*/ {2.33888662, 0.172911853},
|
||||
/*ratio=2.22*/ {2.36937163, 0.177039959},
|
||||
/*ratio=2.24*/ {2.40317673, 0.177839181},
|
||||
/*ratio=2.26*/ {2.42840031, 0.185615110},
|
||||
/*ratio=2.28*/ {2.45838300, 0.188905374},
|
||||
/*ratio=2.30*/ {2.48660575, 0.193273145}};
|
||||
constexpr Scalar kRatioStepInverse = 50; // = 1 / 0.02
|
||||
|
||||
constexpr size_t kNumRecords =
|
||||
sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]);
|
||||
constexpr Scalar kMinRatio = 2.00f;
|
||||
constexpr Scalar kMaxRatio = kMinRatio + (kNumRecords - 1) / kRatioStepInverse;
|
||||
|
||||
// Linear interpolation for `kPrecomputedVariables`.
|
||||
//
|
||||
// The `column` is a 0-based index that decides the target variable.
|
||||
Scalar LerpPrecomputedVariable(size_t column, Scalar ratio) {
|
||||
Scalar steps = std::clamp<Scalar>((ratio - kMinRatio) * kRatioStepInverse, 0,
|
||||
kNumRecords - 1);
|
||||
size_t left = std::clamp<size_t>(static_cast<size_t>(std::floor(steps)), 0,
|
||||
kNumRecords - 2);
|
||||
Scalar frac = steps - left;
|
||||
|
||||
return (1 - frac) * kPrecomputedVariables[left][column] +
|
||||
frac * kPrecomputedVariables[left + 1][column];
|
||||
}
|
||||
|
||||
// Find the center of the circle that passes the given two points and have the
|
||||
// given radius.
|
||||
Point FindCircleCenter(Point a, Point b, Scalar r) {
|
||||
/* Denote the middle point of A and B as M. The key is to find the center of
|
||||
* the circle.
|
||||
* A --__
|
||||
* / ⟍ `、
|
||||
* / M ⟍\
|
||||
* / ⟋ B
|
||||
* / ⟋ ↗
|
||||
* / ⟋
|
||||
* / ⟋ r
|
||||
* C ᜱ ↙
|
||||
*/
|
||||
|
||||
Point a_to_b = b - a;
|
||||
Point m = (a + b) / 2;
|
||||
Point c_to_m = Point(-a_to_b.y, a_to_b.x);
|
||||
Scalar distance_am = a_to_b.GetLength() / 2;
|
||||
Scalar distance_cm = sqrt(r * r - distance_am * distance_am);
|
||||
return m - distance_cm * c_to_m.Normalize();
|
||||
}
|
||||
|
||||
// Compute parameters for a square-like rounded superellipse with a symmetrical
|
||||
// radius.
|
||||
RoundSuperellipseParam::Octant ComputeOctant(Point center,
|
||||
Scalar half_size,
|
||||
Scalar 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 |
|
||||
* s | | |
|
||||
* ↓ +----+---------------| A'
|
||||
* O
|
||||
* ← s →
|
||||
* ←---- half_size -----→
|
||||
*/
|
||||
|
||||
Scalar ratio =
|
||||
radius == 0 ? kMaxRatio : std::min(half_size * 2 / radius, kMaxRatio);
|
||||
Scalar a = ratio * radius / 2;
|
||||
Scalar s = half_size - a;
|
||||
Scalar g = RoundSuperellipseParam::kGapFactor * radius;
|
||||
|
||||
Scalar n = LerpPrecomputedVariable(0, ratio);
|
||||
Scalar sin_thetaJ = radius == 0 ? 0 : LerpPrecomputedVariable(1, ratio);
|
||||
|
||||
Scalar sin_thetaJ_sq = sin_thetaJ * sin_thetaJ;
|
||||
Scalar cos_thetaJ_sq = 1 - sin_thetaJ_sq;
|
||||
Scalar tan_thetaJ_sq = sin_thetaJ_sq / cos_thetaJ_sq;
|
||||
|
||||
Scalar xJ = a * pow(sin_thetaJ_sq, 1 / n);
|
||||
Scalar yJ = a * pow(cos_thetaJ_sq, 1 / n);
|
||||
Scalar tan_phiJ = pow(tan_thetaJ_sq, (n - 1) / n);
|
||||
Scalar d = (xJ - tan_phiJ * yJ) / (1 - tan_phiJ);
|
||||
Scalar R = (a - d - g) * sqrt(2);
|
||||
|
||||
Point pointA{0, half_size};
|
||||
Point pointM{half_size - g, half_size - g};
|
||||
Point pointS{s, s};
|
||||
Point pointJ = Point{xJ, yJ} + pointS;
|
||||
Point circle_center =
|
||||
radius == 0 ? pointM : FindCircleCenter(pointJ, pointM, R);
|
||||
Radians circle_max_angle =
|
||||
radius == 0 ? Radians(0)
|
||||
: (pointM - circle_center).AngleTo(pointJ - circle_center);
|
||||
|
||||
return RoundSuperellipseParam::Octant{
|
||||
.offset = center,
|
||||
|
||||
.edge_mid = pointA,
|
||||
|
||||
.se_center = pointS,
|
||||
.se_a = a,
|
||||
.se_n = n,
|
||||
.se_max_theta = asin(sin_thetaJ),
|
||||
|
||||
.ratio = ratio,
|
||||
|
||||
.circle_start = pointJ,
|
||||
.circle_center = circle_center,
|
||||
.circle_max_angle = circle_max_angle,
|
||||
};
|
||||
}
|
||||
|
||||
// Compute parameters for a quadrant of a rounded superellipse with asymmetrical
|
||||
// radii.
|
||||
//
|
||||
// The `corner` is the coordinate of the corner point in the same coordinate
|
||||
// space as `center`, which specifies both the half size of the bounding box and
|
||||
// which quadrant the curve should be.
|
||||
RoundSuperellipseParam::Quadrant ComputeQuadrant(Point center,
|
||||
Point corner,
|
||||
Size in_radii) {
|
||||
Point corner_vector = corner - center;
|
||||
Size radii = in_radii.Abs();
|
||||
|
||||
// The prefix "norm" is short for "normalized".
|
||||
//
|
||||
// Be extra careful to avoid NaNs in cases that some coordinates of `in_radii`
|
||||
// or `corner_vector` are zero.
|
||||
Scalar norm_radius = radii.MinDimension();
|
||||
Size forward_scale = norm_radius == 0 ? Size{1, 1} : radii / norm_radius;
|
||||
Point norm_half_size = corner_vector.Abs() / forward_scale;
|
||||
Point signed_scale = ReplanceNaNWithOne(corner_vector / norm_half_size);
|
||||
|
||||
// Each quadrant curve is composed of two octant curves, each of which belongs
|
||||
// to a square-like rounded rectangle. For the two octants to connect at the
|
||||
// circular arc, the centers these two square-like rounded rectangle must be
|
||||
// offset from the quadrant center by a same distance in different directions.
|
||||
// The distance is denoted as `c`.
|
||||
Scalar c = norm_half_size.x - norm_half_size.y;
|
||||
|
||||
return RoundSuperellipseParam::Quadrant{
|
||||
.offset = center,
|
||||
.signed_scale = signed_scale,
|
||||
.top = ComputeOctant(Point{0, -c}, norm_half_size.x, norm_radius),
|
||||
.right = ComputeOctant(Point{c, 0}, norm_half_size.y, norm_radius),
|
||||
};
|
||||
}
|
||||
|
||||
// Checks whether the given point is contained in the first octant of the given
|
||||
// square-like rounded superellipse.
|
||||
//
|
||||
// The first octant refers to the region that spans from 0 to pi/4 starting from
|
||||
// positive Y axis clockwise.
|
||||
//
|
||||
// If the point is not within this octant at all, then this function always
|
||||
// returns true. Otherwise this function returns whether the point is contained
|
||||
// within the rounded superellipse.
|
||||
//
|
||||
// The `param.offset` is ignored. The input point should have been transformed
|
||||
// to the coordinate space where the rounded superellipse is centered at the
|
||||
// origin.
|
||||
bool OctantContains(const RoundSuperellipseParam::Octant& param,
|
||||
const Point& p) {
|
||||
// Check whether the point is within the octant.
|
||||
if (p.x < 0 || p.y < 0 || p.y < p.x) {
|
||||
return true;
|
||||
}
|
||||
// Check if the point is within the stretch segment.
|
||||
if (p.x <= param.se_center.x) {
|
||||
return p.y <= param.edge_mid.y;
|
||||
}
|
||||
// Check if the point is within the superellipsoid segment.
|
||||
if (p.x <= param.circle_start.x) {
|
||||
Point p_se = (p - param.se_center) / param.se_a;
|
||||
return powf(p_se.x, param.se_n) + powf(p_se.y, param.se_n) <= 1;
|
||||
}
|
||||
Scalar circle_radius =
|
||||
param.circle_start.GetDistanceSquared(param.circle_center);
|
||||
Point p_circle = p - param.circle_center;
|
||||
return p_circle.GetDistanceSquared(Point()) < circle_radius;
|
||||
}
|
||||
|
||||
// Determine if p is inside the corner curve defined by the indicated corner
|
||||
// param.
|
||||
//
|
||||
// The coordinates of p should be within the same coordinate space with
|
||||
// `param.offset`.
|
||||
//
|
||||
// If `check_quadrant` is true, then this function first checks if the point is
|
||||
// within the quadrant of given corner. If not, this function returns true,
|
||||
// otherwise this method continues to check whether the point is contained in
|
||||
// the rounded superellipse.
|
||||
//
|
||||
// If `check_quadrant` is false, then the first step above is skipped, and the
|
||||
// function checks whether the absolute (relative to the center) coordinate of p
|
||||
// is contained in the rounded superellipse.
|
||||
bool CornerContains(const RoundSuperellipseParam::Quadrant& param,
|
||||
const Point& p,
|
||||
bool check_quadrant = true) {
|
||||
Point norm_point = (p - param.offset) / param.signed_scale;
|
||||
if (check_quadrant) {
|
||||
if (norm_point.x < 0 || norm_point.y < 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
norm_point = norm_point.Abs();
|
||||
}
|
||||
return OctantContains(param.top, norm_point - param.top.offset) &&
|
||||
OctantContains(param.right, Flip(norm_point - param.right.offset));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RoundSuperellipseParam RoundSuperellipseParam::MakeBoundsRadii(
|
||||
const Rect& bounds_,
|
||||
const RoundingRadii& radii_) {
|
||||
if (radii_.AreAllCornersSame()) {
|
||||
return RoundSuperellipseParam{
|
||||
.top_right = ComputeQuadrant(bounds_.GetCenter(), bounds_.GetRightTop(),
|
||||
radii_.top_right),
|
||||
.all_corners_same = true,
|
||||
};
|
||||
}
|
||||
Scalar top_split = Split(bounds_.GetLeft(), bounds_.GetRight(),
|
||||
radii_.top_left.width, radii_.top_right.width);
|
||||
Scalar right_split =
|
||||
Split(bounds_.GetTop(), bounds_.GetBottom(), radii_.top_right.height,
|
||||
radii_.bottom_right.height);
|
||||
Scalar bottom_split =
|
||||
Split(bounds_.GetLeft(), bounds_.GetRight(), radii_.bottom_left.width,
|
||||
radii_.bottom_right.width);
|
||||
Scalar left_split = Split(bounds_.GetTop(), bounds_.GetBottom(),
|
||||
radii_.top_left.height, radii_.bottom_left.height);
|
||||
|
||||
return RoundSuperellipseParam{
|
||||
.top_right = ComputeQuadrant(Point{top_split, right_split},
|
||||
bounds_.GetRightTop(), radii_.top_right),
|
||||
.bottom_right =
|
||||
ComputeQuadrant(Point{bottom_split, right_split},
|
||||
bounds_.GetRightBottom(), radii_.bottom_right),
|
||||
.bottom_left =
|
||||
ComputeQuadrant(Point{bottom_split, left_split},
|
||||
bounds_.GetLeftBottom(), radii_.bottom_left),
|
||||
.top_left = ComputeQuadrant(Point{top_split, left_split},
|
||||
bounds_.GetLeftTop(), radii_.top_left),
|
||||
.all_corners_same = false,
|
||||
};
|
||||
}
|
||||
|
||||
bool RoundSuperellipseParam::Contains(const Point& point) const {
|
||||
if (all_corners_same) {
|
||||
return CornerContains(top_right, point, /*check_quadrant=*/false);
|
||||
}
|
||||
return CornerContains(top_right, point) &&
|
||||
CornerContains(bottom_right, point) &&
|
||||
CornerContains(bottom_left, point) && CornerContains(top_left, point);
|
||||
}
|
||||
|
||||
void RoundSuperellipseParam::SuperellipseBezierArc(
|
||||
Point* output,
|
||||
const RoundSuperellipseParam::Octant& param) {
|
||||
Point start = {param.se_center.x, param.edge_mid.y};
|
||||
const Point& end = param.circle_start;
|
||||
constexpr Point start_tangent = {1, 0};
|
||||
Point circle_start_vector = param.circle_start - param.circle_center;
|
||||
Point end_tangent =
|
||||
Point{-circle_start_vector.y, circle_start_vector.x}.Normalize();
|
||||
|
||||
Scalar start_factor = LerpPrecomputedVariable(0, param.ratio);
|
||||
Scalar end_factor = LerpPrecomputedVariable(1, param.ratio);
|
||||
|
||||
output[0] = start;
|
||||
output[1] = start + start_tangent * start_factor * param.se_a;
|
||||
output[2] = end + end_tangent * end_factor * param.se_a;
|
||||
output[3] = end;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
134
engine/src/flutter/impeller/geometry/round_superellipse_param.h
Normal file
134
engine/src/flutter/impeller/geometry/round_superellipse_param.h
Normal file
@ -0,0 +1,134 @@
|
||||
// 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_GEOMETRY_ROUND_SUPERELLIPSE_PARAM_H_
|
||||
#define FLUTTER_IMPELLER_GEOMETRY_ROUND_SUPERELLIPSE_PARAM_H_
|
||||
|
||||
#include "flutter/impeller/geometry/point.h"
|
||||
#include "flutter/impeller/geometry/rect.h"
|
||||
#include "flutter/impeller/geometry/rounding_radii.h"
|
||||
#include "flutter/impeller/geometry/size.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
// A utility struct that expands input parameters for a rounded superellipse to
|
||||
// drawing variables.
|
||||
struct RoundSuperellipseParam {
|
||||
// Parameters for drawing a square-like rounded superellipse.
|
||||
//
|
||||
// This structure is used to define an octant of an arbitrary rounded
|
||||
// superellipse.
|
||||
struct Octant {
|
||||
// The offset of the square-like rounded superellipse's center from the
|
||||
// origin.
|
||||
//
|
||||
// All other coordinates in this structure are relative to this point.
|
||||
Point offset;
|
||||
|
||||
// The coordinate of the midpoint of the top edge, relative to the `offset`
|
||||
// point.
|
||||
//
|
||||
// This is the starting point of the octant curve.
|
||||
Point edge_mid;
|
||||
|
||||
// The coordinate of the superellipse's center, relative to the `offset`
|
||||
// point.
|
||||
Point se_center;
|
||||
// The semi-axis length of the superellipse.
|
||||
Scalar se_a;
|
||||
// The degree of the superellipse.
|
||||
Scalar se_n;
|
||||
// The range of the parameter "theta" used to define the superellipse curve.
|
||||
//
|
||||
// The "theta" is not the angle of the curve but the implicit parameter
|
||||
// used in the curve's parametric equation.
|
||||
Scalar se_max_theta;
|
||||
|
||||
Scalar ratio;
|
||||
|
||||
// The coordinate of the top left end of the circular arc, relative to the
|
||||
// `offset` point.
|
||||
Point circle_start;
|
||||
// The center of the circular arc, relative to the `offset` point.
|
||||
Point circle_center;
|
||||
// The angular span of the circular arc, measured in radians.
|
||||
Radians circle_max_angle;
|
||||
};
|
||||
|
||||
// Parameters for drawing a rounded superellipse with equal radius size for
|
||||
// all corners.
|
||||
//
|
||||
// This structure is used to define a quadrant of an arbitrary rounded
|
||||
// superellipse.
|
||||
struct Quadrant {
|
||||
// The offset of the rounded superellipse's center from the origin.
|
||||
//
|
||||
// All other coordinates in this structure are relative to this point.
|
||||
Point offset;
|
||||
|
||||
// The scaling factor used to transform a normalized rounded superellipse
|
||||
// back to its original, unnormalized shape.
|
||||
//
|
||||
// Normalization refers to adjusting the original curve, which may have
|
||||
// asymmetrical corner sizes, into a symmetrical one by reducing the longer
|
||||
// radius to match the shorter one. For instance, to draw a rounded
|
||||
// superellipse with size (200, 300) and radii (20, 10), the function first
|
||||
// draws a normalized RSE with size (100, 300) and radii (10, 10), then
|
||||
// scales it by (2x, 1x) to restore the original proportions.
|
||||
//
|
||||
// Normalization also flips the curve to the first quadrant (positive x and
|
||||
// y) if it originally resides in another quadrant. This affects the signs
|
||||
// of `signed_scale`.
|
||||
Point signed_scale;
|
||||
|
||||
// The parameters for the two octants that make up this quadrant after
|
||||
// normalization.
|
||||
Octant top;
|
||||
Octant right;
|
||||
};
|
||||
|
||||
// The parameters for the four quadrants that make up the full contour.
|
||||
//
|
||||
// If `all_corners_same` is true, then only `top_right` is popularized.
|
||||
Quadrant top_right;
|
||||
Quadrant bottom_right;
|
||||
Quadrant bottom_left;
|
||||
Quadrant top_left;
|
||||
|
||||
// If true, all corners are the same and only `top_right` is popularized.
|
||||
bool all_corners_same;
|
||||
|
||||
// Create a param for a rounded superellipse with the specific bounds and
|
||||
// radii.
|
||||
[[nodiscard]] static RoundSuperellipseParam MakeBoundsRadii(
|
||||
const Rect& bounds,
|
||||
const RoundingRadii& radii);
|
||||
|
||||
// Returns whether this rounded superellipse contains the point.
|
||||
//
|
||||
// This method does not perform any prescreening such as comparing the point
|
||||
// with the bounds, which is recommended for callers.
|
||||
bool Contains(const Point& point) const;
|
||||
|
||||
// A factor used to calculate the "gap", defined as the distance from the
|
||||
// midpoint of the curved corners to the nearest sides of the bounding box.
|
||||
//
|
||||
// When the corner radius is symmetrical on both dimensions, the midpoint of
|
||||
// the corner is where the circular arc intersects its quadrant bisector. When
|
||||
// the corner radius is asymmetrical, since the corner can be considered
|
||||
// "elongated" from a symmetrical corner, the midpoint is transformed in the
|
||||
// same way.
|
||||
//
|
||||
// Experiments indicate that the gap is linear with respect to the corner
|
||||
// radius on that dimension.
|
||||
static constexpr Scalar kGapFactor = 0.29289321881f; // 1-cos(pi/4)
|
||||
|
||||
static void SuperellipseBezierArc(
|
||||
Point* output,
|
||||
const RoundSuperellipseParam::Octant& param);
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
#endif // FLUTTER_IMPELLER_GEOMETRY_ROUND_SUPERELLIPSE_PARAM_H_
|
@ -0,0 +1,694 @@
|
||||
// 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 "gtest/gtest.h"
|
||||
|
||||
#include "flutter/impeller/geometry/round_superellipse.h"
|
||||
|
||||
#include "flutter/impeller/geometry/geometry_asserts.h"
|
||||
|
||||
#define CHECK_POINT_WITH_OFFSET(rr, p, outward_offset) \
|
||||
EXPECT_TRUE(rr.Contains(p)); \
|
||||
EXPECT_FALSE(rr.Contains(p + outward_offset));
|
||||
|
||||
namespace impeller {
|
||||
namespace testing {
|
||||
|
||||
TEST(RoundSuperellipseTest, EmptyDeclaration) {
|
||||
RoundSuperellipse rse;
|
||||
|
||||
EXPECT_TRUE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_TRUE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect());
|
||||
EXPECT_EQ(rse.GetBounds().GetLeft(), 0.0f);
|
||||
EXPECT_EQ(rse.GetBounds().GetTop(), 0.0f);
|
||||
EXPECT_EQ(rse.GetBounds().GetRight(), 0.0f);
|
||||
EXPECT_EQ(rse.GetBounds().GetBottom(), 0.0f);
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size());
|
||||
EXPECT_EQ(rse.GetRadii().top_left.width, 0.0f);
|
||||
EXPECT_EQ(rse.GetRadii().top_left.height, 0.0f);
|
||||
EXPECT_EQ(rse.GetRadii().top_right.width, 0.0f);
|
||||
EXPECT_EQ(rse.GetRadii().top_right.height, 0.0f);
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left.width, 0.0f);
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left.height, 0.0f);
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right.width, 0.0f);
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right.height, 0.0f);
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, DefaultConstructor) {
|
||||
RoundSuperellipse rse = RoundSuperellipse();
|
||||
|
||||
EXPECT_TRUE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_TRUE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect());
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size());
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, EmptyRectConstruction) {
|
||||
RoundSuperellipse rse =
|
||||
RoundSuperellipse::MakeRect(Rect::MakeLTRB(20.0f, 20.0f, 20.0f, 20.0f));
|
||||
|
||||
EXPECT_TRUE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_TRUE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(20.0f, 20.0f, 20.0f, 20.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size());
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, RectConstructor) {
|
||||
RoundSuperellipse rse =
|
||||
RoundSuperellipse::MakeRect(Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
|
||||
|
||||
EXPECT_FALSE(rse.IsEmpty());
|
||||
EXPECT_TRUE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_FALSE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size());
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, InvertedRectConstruction) {
|
||||
RoundSuperellipse rse =
|
||||
RoundSuperellipse::MakeRect(Rect::MakeLTRB(20.0f, 20.0f, 10.0f, 10.0f));
|
||||
|
||||
EXPECT_FALSE(rse.IsEmpty());
|
||||
EXPECT_TRUE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_FALSE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size());
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, EmptyOvalConstruction) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectXY(
|
||||
Rect::MakeLTRB(20.0f, 20.0f, 20.0f, 20.0f), 10.0f, 10.0f);
|
||||
|
||||
EXPECT_TRUE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_TRUE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(20.0f, 20.0f, 20.0f, 20.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size());
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size());
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, OvalConstructor) {
|
||||
RoundSuperellipse rse =
|
||||
RoundSuperellipse::MakeOval(Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
|
||||
|
||||
EXPECT_FALSE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_TRUE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_FALSE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size(5.0f, 5.0f));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, InvertedOvalConstruction) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectXY(
|
||||
Rect::MakeLTRB(20.0f, 20.0f, 10.0f, 10.0f), 10.0f, 10.0f);
|
||||
|
||||
EXPECT_FALSE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_TRUE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_FALSE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size(5.0f, 5.0f));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, RectRadiusConstructor) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadius(
|
||||
Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f), 2.0f);
|
||||
|
||||
EXPECT_FALSE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_FALSE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size(2.0f, 2.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size(2.0f, 2.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size(2.0f, 2.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size(2.0f, 2.0f));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, RectXYConstructor) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectXY(
|
||||
Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f), 2.0f, 3.0f);
|
||||
|
||||
EXPECT_FALSE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_FALSE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size(2.0f, 3.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size(2.0f, 3.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size(2.0f, 3.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size(2.0f, 3.0f));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, RectSizeConstructor) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectXY(
|
||||
Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f), Size(2.0f, 3.0f));
|
||||
|
||||
EXPECT_FALSE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_FALSE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size(2.0f, 3.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size(2.0f, 3.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size(2.0f, 3.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size(2.0f, 3.0f));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, RectRadiiConstructor) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f),
|
||||
{
|
||||
.top_left = Size(1.0, 1.5),
|
||||
.top_right = Size(2.0, 2.5f),
|
||||
.bottom_left = Size(3.0, 3.5f),
|
||||
.bottom_right = Size(4.0, 4.5f),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_FALSE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size(1.0f, 1.5f));
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size(2.0f, 2.5f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size(3.0f, 3.5f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size(4.0f, 4.5f));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, RectRadiiOverflowWidthConstructor) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(10.0f, 10.0f, 6.0f, 30.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
});
|
||||
// Largest sum of paired radii widths is the bottom edge which sums to 12
|
||||
// Rect is only 6 wide so all radii are scaled by half
|
||||
// Rect is 30 tall so no scaling should happen due to radii heights
|
||||
|
||||
EXPECT_FALSE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_FALSE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 16.0f, 40.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size(0.5f, 1.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size(1.5f, 2.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size(2.5f, 3.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size(3.5f, 4.0f));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, RectRadiiOverflowHeightConstructor) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 6.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
});
|
||||
// Largest sum of paired radii heights is the right edge which sums to 12
|
||||
// Rect is only 6 tall so all radii are scaled by half
|
||||
// Rect is 30 wide so no scaling should happen due to radii widths
|
||||
|
||||
EXPECT_FALSE(rse.IsEmpty());
|
||||
EXPECT_FALSE(rse.IsRect());
|
||||
EXPECT_FALSE(rse.IsOval());
|
||||
EXPECT_TRUE(rse.IsFinite());
|
||||
EXPECT_FALSE(rse.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 40.0f, 16.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_left, Size(0.5f, 1.0f));
|
||||
EXPECT_EQ(rse.GetRadii().top_right, Size(1.5f, 2.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_left, Size(2.5f, 3.0f));
|
||||
EXPECT_EQ(rse.GetRadii().bottom_right, Size(3.5f, 4.0f));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, Shift) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
});
|
||||
RoundSuperellipse shifted = rse.Shift(5.0, 6.0);
|
||||
|
||||
EXPECT_FALSE(shifted.IsEmpty());
|
||||
EXPECT_FALSE(shifted.IsRect());
|
||||
EXPECT_FALSE(shifted.IsOval());
|
||||
EXPECT_TRUE(shifted.IsFinite());
|
||||
EXPECT_FALSE(shifted.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(shifted.GetBounds(), Rect::MakeLTRB(15.0f, 16.0f, 45.0f, 46.0f));
|
||||
EXPECT_EQ(shifted.GetRadii().top_left, Size(1.0f, 2.0f));
|
||||
EXPECT_EQ(shifted.GetRadii().top_right, Size(3.0f, 4.0f));
|
||||
EXPECT_EQ(shifted.GetRadii().bottom_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(shifted.GetRadii().bottom_right, Size(7.0f, 8.0f));
|
||||
|
||||
EXPECT_EQ(shifted, RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(15.0f, 16.0f, 30.0f, 30.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, ExpandScalar) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
});
|
||||
RoundSuperellipse expanded = rse.Expand(5.0);
|
||||
|
||||
EXPECT_FALSE(expanded.IsEmpty());
|
||||
EXPECT_FALSE(expanded.IsRect());
|
||||
EXPECT_FALSE(expanded.IsOval());
|
||||
EXPECT_TRUE(expanded.IsFinite());
|
||||
EXPECT_FALSE(expanded.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(5.0f, 5.0f, 45.0f, 45.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
|
||||
|
||||
EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(5.0f, 5.0f, 40.0f, 40.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, ExpandTwoScalars) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
});
|
||||
RoundSuperellipse expanded = rse.Expand(5.0, 6.0);
|
||||
|
||||
EXPECT_FALSE(expanded.IsEmpty());
|
||||
EXPECT_FALSE(expanded.IsRect());
|
||||
EXPECT_FALSE(expanded.IsOval());
|
||||
EXPECT_TRUE(expanded.IsFinite());
|
||||
EXPECT_FALSE(expanded.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(5.0f, 4.0f, 45.0f, 46.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
|
||||
|
||||
EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(5.0f, 4.0f, 40.0f, 42.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, ExpandFourScalars) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
});
|
||||
RoundSuperellipse expanded = rse.Expand(5.0, 6.0, 7.0, 8.0);
|
||||
|
||||
EXPECT_FALSE(expanded.IsEmpty());
|
||||
EXPECT_FALSE(expanded.IsRect());
|
||||
EXPECT_FALSE(expanded.IsOval());
|
||||
EXPECT_TRUE(expanded.IsFinite());
|
||||
EXPECT_FALSE(expanded.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(5.0f, 4.0f, 47.0f, 48.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
|
||||
|
||||
EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(5.0f, 4.0f, 42.0f, 44.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, ContractScalar) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
});
|
||||
RoundSuperellipse expanded = rse.Expand(-2.0);
|
||||
|
||||
EXPECT_FALSE(expanded.IsEmpty());
|
||||
EXPECT_FALSE(expanded.IsRect());
|
||||
EXPECT_FALSE(expanded.IsOval());
|
||||
EXPECT_TRUE(expanded.IsFinite());
|
||||
EXPECT_FALSE(expanded.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(12.0f, 12.0f, 38.0f, 38.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
|
||||
|
||||
EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(12.0f, 12.0f, 26.0f, 26.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, ContractTwoScalars) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
});
|
||||
RoundSuperellipse expanded = rse.Expand(-1.0, -2.0);
|
||||
|
||||
EXPECT_FALSE(expanded.IsEmpty());
|
||||
EXPECT_FALSE(expanded.IsRect());
|
||||
EXPECT_FALSE(expanded.IsOval());
|
||||
EXPECT_TRUE(expanded.IsFinite());
|
||||
EXPECT_FALSE(expanded.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(11.0f, 12.0f, 39.0f, 38.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
|
||||
|
||||
EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(11.0f, 12.0f, 28.0f, 26.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, ContractFourScalars) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
});
|
||||
RoundSuperellipse expanded = rse.Expand(-1.0, -1.5, -2.0, -2.5);
|
||||
|
||||
EXPECT_FALSE(expanded.IsEmpty());
|
||||
EXPECT_FALSE(expanded.IsRect());
|
||||
EXPECT_FALSE(expanded.IsOval());
|
||||
EXPECT_TRUE(expanded.IsFinite());
|
||||
EXPECT_FALSE(expanded.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(11.0f, 11.5f, 38.0f, 37.5f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
|
||||
|
||||
EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(11.0f, 11.5f, 27.0f, 26.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, ContractAndRequireRadiiAdjustment) {
|
||||
RoundSuperellipse rse = RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
});
|
||||
RoundSuperellipse expanded = rse.Expand(-12.0);
|
||||
// Largest sum of paired radii sizes are the bottom and right edges
|
||||
// both of which sum to 12
|
||||
// Rect was 30x30 reduced by 12 on all sides leaving only 6x6, so all
|
||||
// radii are scaled by half to avoid overflowing the contracted rect
|
||||
|
||||
EXPECT_FALSE(expanded.IsEmpty());
|
||||
EXPECT_FALSE(expanded.IsRect());
|
||||
EXPECT_FALSE(expanded.IsOval());
|
||||
EXPECT_TRUE(expanded.IsFinite());
|
||||
EXPECT_FALSE(expanded.GetBounds().IsEmpty());
|
||||
EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(22.0f, 22.0f, 28.0f, 28.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_left, Size(0.5f, 1.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().top_right, Size(1.5f, 2.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_left, Size(2.5f, 3.0f));
|
||||
EXPECT_EQ(expanded.GetRadii().bottom_right, Size(3.5f, 4.0f));
|
||||
|
||||
// In this test, the MakeRectRadii constructor will make the same
|
||||
// adjustment to the radii that the Expand method applied.
|
||||
EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(22.0f, 22.0f, 6.0f, 6.0f),
|
||||
{
|
||||
.top_left = Size(1.0f, 2.0f),
|
||||
.top_right = Size(3.0f, 4.0f),
|
||||
.bottom_left = Size(5.0f, 6.0f),
|
||||
.bottom_right = Size(7.0f, 8.0f),
|
||||
}));
|
||||
|
||||
// In this test, the arguments to the constructor supply the correctly
|
||||
// adjusted radii (though there is no real way to tell other than
|
||||
// the result is the same).
|
||||
EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
|
||||
Rect::MakeXYWH(22.0f, 22.0f, 6.0f, 6.0f),
|
||||
{
|
||||
.top_left = Size(0.5f, 1.0f),
|
||||
.top_right = Size(1.5f, 2.0f),
|
||||
.bottom_left = Size(2.5f, 3.0f),
|
||||
.bottom_right = Size(3.5f, 4.0f),
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, NoCornerRoundSuperellipseContains) {
|
||||
Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
|
||||
// RRect of bounds with no corners contains corners just barely
|
||||
auto no_corners = RoundSuperellipse::MakeRectRadii(
|
||||
bounds, RoundingRadii::MakeRadii({0.0f, 0.0f}));
|
||||
|
||||
EXPECT_TRUE(no_corners.Contains({-50, -50}));
|
||||
// Rectangles have half-in, half-out containment so we need
|
||||
// to be careful about testing containment of right/bottom corners.
|
||||
EXPECT_TRUE(no_corners.Contains({-50, 49.99}));
|
||||
EXPECT_TRUE(no_corners.Contains({49.99, -50}));
|
||||
EXPECT_TRUE(no_corners.Contains({49.99, 49.99}));
|
||||
EXPECT_FALSE(no_corners.Contains({-50.01, -50}));
|
||||
EXPECT_FALSE(no_corners.Contains({-50, -50.01}));
|
||||
EXPECT_FALSE(no_corners.Contains({-50.01, 50}));
|
||||
EXPECT_FALSE(no_corners.Contains({-50, 50.01}));
|
||||
EXPECT_FALSE(no_corners.Contains({50.01, -50}));
|
||||
EXPECT_FALSE(no_corners.Contains({50, -50.01}));
|
||||
EXPECT_FALSE(no_corners.Contains({50.01, 50}));
|
||||
EXPECT_FALSE(no_corners.Contains({50, 50.01}));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, TinyCornerContains) {
|
||||
Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
|
||||
// RRect of bounds with even the tiniest corners does not contain corners
|
||||
auto tiny_corners = RoundSuperellipse::MakeRectRadii(
|
||||
bounds, RoundingRadii::MakeRadii({0.01f, 0.01f}));
|
||||
|
||||
EXPECT_FALSE(tiny_corners.Contains({-50, -50}));
|
||||
EXPECT_FALSE(tiny_corners.Contains({-50, 50}));
|
||||
EXPECT_FALSE(tiny_corners.Contains({50, -50}));
|
||||
EXPECT_FALSE(tiny_corners.Contains({50, 50}));
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, UniformSquareContains) {
|
||||
Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
|
||||
auto rr = RoundSuperellipse::MakeRectRadii(
|
||||
bounds, RoundingRadii::MakeRadii({5.0f, 5.0f}));
|
||||
|
||||
#define CHECK_POINT_AND_MIRRORS(p) \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p), Point(0.02, 0.02)); \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p) * Point(1, -1), Point(0.02, -0.02)); \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, 1), Point(-0.02, 0.02)); \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, -1), Point(-0.02, -0.02));
|
||||
|
||||
CHECK_POINT_AND_MIRRORS(Point(0, 49.995)); // Top
|
||||
CHECK_POINT_AND_MIRRORS(Point(44.245, 49.995)); // Top stretch end
|
||||
CHECK_POINT_AND_MIRRORS(Point(45.72, 49.92)); // Top joint
|
||||
CHECK_POINT_AND_MIRRORS(Point(48.53, 48.53)); // Circular arc mid
|
||||
CHECK_POINT_AND_MIRRORS(Point(49.92, 45.72)); // Right joint
|
||||
CHECK_POINT_AND_MIRRORS(Point(49.995, 44.245)); // Right stretch end
|
||||
CHECK_POINT_AND_MIRRORS(Point(49.995, 0)); // Right
|
||||
#undef CHECK_POINT_AND_MIRRORS
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, UniformEllipticalContains) {
|
||||
Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
|
||||
auto rr = RoundSuperellipse::MakeRectRadii(
|
||||
bounds, RoundingRadii::MakeRadii({5.0f, 10.0f}));
|
||||
|
||||
#define CHECK_POINT_AND_MIRRORS(p) \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p), Point(0.02, 0.02)); \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p) * Point(1, -1), Point(0.02, -0.02)); \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, 1), Point(-0.02, 0.02)); \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, -1), Point(-0.02, -0.02));
|
||||
|
||||
CHECK_POINT_AND_MIRRORS(Point(0, 49.995)); // Top
|
||||
CHECK_POINT_AND_MIRRORS(Point(44.245, 49.995)); // Top stretch end
|
||||
CHECK_POINT_AND_MIRRORS(Point(45.72, 49.84)); // Top joint
|
||||
CHECK_POINT_AND_MIRRORS(Point(48.51, 47.07)); // Circular arc mid
|
||||
CHECK_POINT_AND_MIRRORS(Point(49.92, 41.44)); // Right joint
|
||||
CHECK_POINT_AND_MIRRORS(Point(49.995, 38.49)); // Right stretch end
|
||||
CHECK_POINT_AND_MIRRORS(Point(49.995, 0)); // Right
|
||||
#undef CHECK_POINT_AND_MIRRORS
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, UniformRectangularContains) {
|
||||
// The bounds is not centered at the origin and has unequal height and width.
|
||||
Rect bounds = Rect::MakeLTRB(0.0f, 0.0f, 50.0f, 100.0f);
|
||||
auto rr = RoundSuperellipse::MakeRectRadii(
|
||||
bounds, RoundingRadii::MakeRadii({23.0f, 30.0f}));
|
||||
|
||||
Point center = bounds.GetCenter();
|
||||
#define CHECK_POINT_AND_MIRRORS(p) \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p - center) * Point(1, 1) + center, \
|
||||
Point(0.02, 0.02)); \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p - center) * Point(1, -1) + center, \
|
||||
Point(0.02, -0.02)); \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p - center) * Point(-1, 1) + center, \
|
||||
Point(-0.02, 0.02)); \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p - center) * Point(-1, -1) + center, \
|
||||
Point(-0.02, -0.02));
|
||||
|
||||
CHECK_POINT_AND_MIRRORS(Point(24.99, 99.99)); // Bottom mid edge
|
||||
CHECK_POINT_AND_MIRRORS(Point(29.99, 99.64));
|
||||
CHECK_POINT_AND_MIRRORS(Point(34.99, 98.06));
|
||||
CHECK_POINT_AND_MIRRORS(Point(39.99, 94.73));
|
||||
CHECK_POINT_AND_MIRRORS(Point(44.13, 89.99));
|
||||
CHECK_POINT_AND_MIRRORS(Point(48.60, 79.99));
|
||||
CHECK_POINT_AND_MIRRORS(Point(49.93, 69.99));
|
||||
CHECK_POINT_AND_MIRRORS(Point(49.99, 59.99));
|
||||
CHECK_POINT_AND_MIRRORS(Point(49.99, 49.99)); // Right mid edge
|
||||
|
||||
#undef CHECK_POINT_AND_MIRRORS
|
||||
}
|
||||
|
||||
TEST(RoundSuperellipseTest, SlimDiagnalContains) {
|
||||
// This shape has large radii on one diagnal and tiny radii on the other,
|
||||
// resulting in a almond-like shape placed diagnally (NW to SE).
|
||||
Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
|
||||
auto rr = RoundSuperellipse::MakeRectRadii(
|
||||
bounds, {
|
||||
.top_left = Size(1.0, 1.0),
|
||||
.top_right = Size(99.0, 99.0),
|
||||
.bottom_left = Size(99.0, 99.0),
|
||||
.bottom_right = Size(1.0, 1.0),
|
||||
});
|
||||
|
||||
EXPECT_TRUE(rr.Contains(Point{0, 0}));
|
||||
EXPECT_FALSE(rr.Contains(Point{-49.999, -49.999}));
|
||||
EXPECT_FALSE(rr.Contains(Point{-49.999, 49.999}));
|
||||
EXPECT_FALSE(rr.Contains(Point{49.999, 49.999}));
|
||||
EXPECT_FALSE(rr.Contains(Point{49.999, -49.999}));
|
||||
|
||||
// The pointy ends at the NE and SW corners
|
||||
CHECK_POINT_WITH_OFFSET(rr, Point(-49.70, -49.70), Point(-0.02, -0.02));
|
||||
CHECK_POINT_WITH_OFFSET(rr, Point(49.70, 49.70), Point(0.02, 0.02));
|
||||
|
||||
// Checks two points symmetrical to the origin.
|
||||
#define CHECK_DIAGNAL_POINTS(p) \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p), Point(0.02, -0.02)); \
|
||||
CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, -1), Point(-0.02, 0.02));
|
||||
|
||||
// A few other points along the edge
|
||||
CHECK_DIAGNAL_POINTS(Point(-40.0, -49.59));
|
||||
CHECK_DIAGNAL_POINTS(Point(-20.0, -45.64));
|
||||
CHECK_DIAGNAL_POINTS(Point(0.0, -37.01));
|
||||
CHECK_DIAGNAL_POINTS(Point(20.0, -21.96));
|
||||
CHECK_DIAGNAL_POINTS(Point(21.05, -20.92));
|
||||
CHECK_DIAGNAL_POINTS(Point(40.0, 5.68));
|
||||
#undef CHECK_POINT_AND_MIRRORS
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
321
engine/src/flutter/impeller/geometry/rounding_radii_unittests.cc
Normal file
321
engine/src/flutter/impeller/geometry/rounding_radii_unittests.cc
Normal file
@ -0,0 +1,321 @@
|
||||
// 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 "gtest/gtest.h"
|
||||
|
||||
#include "flutter/impeller/geometry/rounding_radii.h"
|
||||
|
||||
#include "flutter/impeller/geometry/geometry_asserts.h"
|
||||
|
||||
namespace impeller {
|
||||
namespace testing {
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiEmptyDeclaration) {
|
||||
RoundingRadii radii;
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size());
|
||||
EXPECT_EQ(radii.top_right, Size());
|
||||
EXPECT_EQ(radii.bottom_left, Size());
|
||||
EXPECT_EQ(radii.bottom_right, Size());
|
||||
EXPECT_EQ(radii.top_left.width, 0.0f);
|
||||
EXPECT_EQ(radii.top_left.height, 0.0f);
|
||||
EXPECT_EQ(radii.top_right.width, 0.0f);
|
||||
EXPECT_EQ(radii.top_right.height, 0.0f);
|
||||
EXPECT_EQ(radii.bottom_left.width, 0.0f);
|
||||
EXPECT_EQ(radii.bottom_left.height, 0.0f);
|
||||
EXPECT_EQ(radii.bottom_right.width, 0.0f);
|
||||
EXPECT_EQ(radii.bottom_right.height, 0.0f);
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiDefaultConstructor) {
|
||||
RoundingRadii radii = RoundingRadii();
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size());
|
||||
EXPECT_EQ(radii.top_right, Size());
|
||||
EXPECT_EQ(radii.bottom_left, Size());
|
||||
EXPECT_EQ(radii.bottom_right, Size());
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiScalarConstructor) {
|
||||
RoundingRadii radii = RoundingRadii::MakeRadius(5.0f);
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(radii.top_right, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(5.0f, 5.0f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(5.0f, 5.0f));
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiEmptyScalarConstructor) {
|
||||
RoundingRadii radii = RoundingRadii::MakeRadius(-5.0f);
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(-5.0f, -5.0f));
|
||||
EXPECT_EQ(radii.top_right, Size(-5.0f, -5.0f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(-5.0f, -5.0f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(-5.0f, -5.0f));
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiSizeConstructor) {
|
||||
RoundingRadii radii = RoundingRadii::MakeRadii(Size(5.0f, 6.0f));
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.top_right, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(5.0f, 6.0f));
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiEmptySizeConstructor) {
|
||||
{
|
||||
RoundingRadii radii = RoundingRadii::MakeRadii(Size(-5.0f, 6.0f));
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(-5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.top_right, Size(-5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(-5.0f, 6.0f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(-5.0f, 6.0f));
|
||||
}
|
||||
|
||||
{
|
||||
RoundingRadii radii = RoundingRadii::MakeRadii(Size(5.0f, -6.0f));
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersEmpty());
|
||||
EXPECT_TRUE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(5.0f, -6.0f));
|
||||
EXPECT_EQ(radii.top_right, Size(5.0f, -6.0f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(5.0f, -6.0f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(5.0f, -6.0f));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiNamedSizesConstructor) {
|
||||
RoundingRadii radii = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(5.0f, 5.5f));
|
||||
EXPECT_EQ(radii.top_right, Size(6.0f, 6.5f));
|
||||
EXPECT_EQ(radii.bottom_left, Size(7.0f, 7.5f));
|
||||
EXPECT_EQ(radii.bottom_right, Size(8.0f, 8.5f));
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiPartialNamedSizesConstructor) {
|
||||
{
|
||||
RoundingRadii radii = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size(5.0f, 5.5f));
|
||||
EXPECT_EQ(radii.top_right, Size());
|
||||
EXPECT_EQ(radii.bottom_left, Size());
|
||||
EXPECT_EQ(radii.bottom_right, Size());
|
||||
}
|
||||
|
||||
{
|
||||
RoundingRadii radii = {
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size());
|
||||
EXPECT_EQ(radii.top_right, Size(6.0f, 6.5f));
|
||||
EXPECT_EQ(radii.bottom_left, Size());
|
||||
EXPECT_EQ(radii.bottom_right, Size());
|
||||
}
|
||||
|
||||
{
|
||||
RoundingRadii radii = {
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size());
|
||||
EXPECT_EQ(radii.top_right, Size());
|
||||
EXPECT_EQ(radii.bottom_left, Size(7.0f, 7.5f));
|
||||
EXPECT_EQ(radii.bottom_right, Size());
|
||||
}
|
||||
|
||||
{
|
||||
RoundingRadii radii = {
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(radii.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(radii.AreAllCornersSame());
|
||||
EXPECT_TRUE(radii.IsFinite());
|
||||
EXPECT_EQ(radii.top_left, Size());
|
||||
EXPECT_EQ(radii.top_right, Size());
|
||||
EXPECT_EQ(radii.bottom_left, Size());
|
||||
EXPECT_EQ(radii.bottom_right, Size(8.0f, 8.5f));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiMultiply) {
|
||||
RoundingRadii radii = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
RoundingRadii doubled = radii * 2.0f;
|
||||
|
||||
EXPECT_FALSE(doubled.AreAllCornersEmpty());
|
||||
EXPECT_FALSE(doubled.AreAllCornersSame());
|
||||
EXPECT_TRUE(doubled.IsFinite());
|
||||
EXPECT_EQ(doubled.top_left, Size(10.0f, 11.0f));
|
||||
EXPECT_EQ(doubled.top_right, Size(12.0f, 13.0f));
|
||||
EXPECT_EQ(doubled.bottom_left, Size(14.0f, 15.0f));
|
||||
EXPECT_EQ(doubled.bottom_right, Size(16.0f, 17.0f));
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiEquals) {
|
||||
RoundingRadii radii = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
RoundingRadii other = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
|
||||
EXPECT_EQ(radii, other);
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiNotEquals) {
|
||||
const RoundingRadii radii = {
|
||||
.top_left = Size(5.0f, 5.5f),
|
||||
.top_right = Size(6.0f, 6.5f),
|
||||
.bottom_left = Size(7.0f, 7.5f),
|
||||
.bottom_right = Size(8.0f, 8.5f),
|
||||
};
|
||||
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_left.width = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_left.height = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_right.width = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_right.height = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_left.width = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_left.height = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_right.width = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_right.height = 100.0f;
|
||||
EXPECT_NE(different, radii);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RoudingRadiiTest, RoundingRadiiCornersSameTolerance) {
|
||||
RoundingRadii radii{
|
||||
.top_left = {10, 20},
|
||||
.top_right = {10.01, 20.01},
|
||||
.bottom_left = {9.99, 19.99},
|
||||
.bottom_right = {9.99, 20.01},
|
||||
};
|
||||
|
||||
EXPECT_TRUE(radii.AreAllCornersSame(.02));
|
||||
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_left.width = 10.03;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_left.height = 20.03;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_right.width = 10.03;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.top_right.height = 20.03;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_left.width = 9.97;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_left.height = 19.97;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_right.width = 9.97;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
{
|
||||
RoundingRadii different = radii;
|
||||
different.bottom_right.height = 20.03;
|
||||
EXPECT_FALSE(different.AreAllCornersSame(.02));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
Loading…
x
Reference in New Issue
Block a user